Import old svn repository.
This commit is contained in:
parent
f81b32b830
commit
432db441a8
|
@ -0,0 +1,48 @@
|
||||||
|
/* lw-support/src/docs/Exceptions.txt
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \page exceptions Exception Hierarchy
|
||||||
|
|
||||||
|
LW Support provides a comprehensive exception subsystem. There are two
|
||||||
|
main categories of exception: errors (these are derived from the
|
||||||
|
lw::Exception class, the most important of which is probably lw::SystemError)
|
||||||
|
and program exceptions (which are derived from the lw::ProgramException
|
||||||
|
class).
|
||||||
|
|
||||||
|
Errors will likely require user intervention, or at least some form of
|
||||||
|
error handling in higher level code. These exceptions are therefore
|
||||||
|
quite comprehensive, containing a human-readable message and also copies
|
||||||
|
of data that might be required to solve the error.
|
||||||
|
|
||||||
|
Program exceptions are exceptional conditions which arise from time to
|
||||||
|
time but are seen as normal functioning of the program. Since these do
|
||||||
|
not denote errors, they are not intended to be propagated back to users
|
||||||
|
or higher level code, and therefore do not contain the same
|
||||||
|
comprehensive facilities as error classes.
|
||||||
|
|
||||||
|
All LW Support exceptions are derived from the \a std::exception class,
|
||||||
|
which means they all support the \a what() member function, which
|
||||||
|
returns a human-readable string describing the error message. Since LW
|
||||||
|
Support uses UCS-4 strings, the message is converted into UTF-8 first.
|
||||||
|
The Exception base class also provides functions to explicitly get a
|
||||||
|
UTF-8 or UCS-4 string: lw::Exception::toUtf8() and lw::Exception::toString().
|
||||||
|
|
||||||
|
Error classes, which are all derived from lw::Exception (and may possibly be
|
||||||
|
part of a larger hierarchy themselves), also expose additional
|
||||||
|
functionality -- you can often request additional information about the
|
||||||
|
error.
|
||||||
|
|
||||||
|
Program exceptions don't have a nice error message (they just use a
|
||||||
|
stock one telling you that you shouldn't see the error) and don't
|
||||||
|
generally have any further information.
|
||||||
|
|
||||||
|
A useful feature is building up a chain of code through which the error
|
||||||
|
has propagated. This can be done with the lw::Exception::chain() member.
|
||||||
|
This feature should allow a user to narrow down the root cause of an
|
||||||
|
error. It is mainly of use when an exception has to propagate through
|
||||||
|
several layers of indirection and cannot be dealt with automatically.
|
||||||
|
|
||||||
|
*/
|
|
@ -1,15 +1,22 @@
|
||||||
/* lw-support/src/docs/MainPage.dox
|
/* lw-support/src/docs/MainPage.txt
|
||||||
*
|
*
|
||||||
* (c)2006, Laurence Withers, <l@lwithers.me.uk>.
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
* Released under the GNU GPLv2. See file COPYING or
|
* COPYING for more information / terms of license.
|
||||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*! \mainpage
|
/*! \mainpage LW Support
|
||||||
|
|
||||||
|
\section intro Introduction
|
||||||
|
|
||||||
|
LW Support is a C++ library intended to provide additional functionality
|
||||||
|
to C++. For the most part, it simply exposes C library functionality in
|
||||||
|
an object-oriented manner. Some general pages about the various concepts
|
||||||
|
LW Support provides are listed here, but the majority of the
|
||||||
|
documentation is to be found in the class listings.
|
||||||
|
|
||||||
|
\section concepts Concepts
|
||||||
|
|
||||||
|
\li \ref exceptions LW Support's exception hierarchy.
|
||||||
|
\li \ref timedate Time and Date classes.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* options for text editors
|
|
||||||
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
|
||||||
vim: expandtab:ts=4:sw=4
|
|
||||||
*/
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* lw-support/src/docs/TimeDate.txt
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \page timedate Time and Date Classes
|
||||||
|
|
||||||
|
LW Support has extensive support for time and date representation and
|
||||||
|
manipulation. It uses <a href="http://en.wikipedia.org/wiki/ISO_8601">
|
||||||
|
ISO8601</a> to portably convert to/from a human-readable string
|
||||||
|
representation.
|
||||||
|
|
||||||
|
The time support classes are split into two: the elapsed time classes
|
||||||
|
(which are used to represent a period of time), and the day time classes
|
||||||
|
(which are used to represent specific points in time).
|
||||||
|
|
||||||
|
\section elapsed Elapsed Time
|
||||||
|
|
||||||
|
The class used to represent an elapsed time period is lw::ElapsedTime.
|
||||||
|
It uses a nanosecond resolution, and can only represent positive time
|
||||||
|
periods (since negative time periods don't make sense). It is
|
||||||
|
supplemented by the stopwatch class, lw::StopWatch.
|
||||||
|
|
||||||
|
\section calendar Calendar Time
|
||||||
|
|
||||||
|
LW Support allows you to represent and manipulate calendar times (i.e.
|
||||||
|
times in the 24-hour clock and/or Gregorian dates). The 24-hour clock
|
||||||
|
is represented by lw::DayTime, with Gregorian dates stored in
|
||||||
|
lw::Date. These two classes are combined in the convenience class
|
||||||
|
lw::DateTime.
|
||||||
|
|
||||||
|
*/
|
|
@ -0,0 +1,3 @@
|
||||||
|
src/docs/MainPage.txt
|
||||||
|
src/docs/Exceptions.txt
|
||||||
|
src/docs/TimeDate.txt
|
|
@ -1,13 +1,18 @@
|
||||||
/* lw-support/src/liblw-support/BottomHeader.h
|
/* lw-support/src/lib/BottomHeader.h
|
||||||
*
|
*
|
||||||
* (c)2006, Laurence Withers, <l@lwithers.me.uk>.
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
* Released under the GNU GPLv2. See file COPYING or
|
* COPYING for more information / terms of license.
|
||||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// deprecated classes go here
|
||||||
|
namespace lw {
|
||||||
|
#ifndef DOXYGEN
|
||||||
|
#define DEPRECATED(className) \
|
||||||
|
typedef class className __attribute__((deprecated)) className
|
||||||
|
#undef DEPRECATED
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of include guard
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* options for text editors
|
|
||||||
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
|
||||||
vim: expandtab:ts=4:sw=4
|
|
||||||
*/
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* lw-support/src/lib/Enums/IOMode.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
/*! \brief I/O open modes.
|
||||||
|
|
||||||
|
This enumeration represents what a device is opened for; reading,
|
||||||
|
writing, both or neither. It also has a member to report that the
|
||||||
|
device isn't open at all.
|
||||||
|
|
||||||
|
*/
|
||||||
|
enum IOMode {
|
||||||
|
/// Device isn't open.
|
||||||
|
IOClosed,
|
||||||
|
|
||||||
|
/// Device was opened in non-read, non-write mode.
|
||||||
|
IONone,
|
||||||
|
|
||||||
|
/// Device is read only.
|
||||||
|
IORead,
|
||||||
|
|
||||||
|
/// Device is write only.
|
||||||
|
IOWrite,
|
||||||
|
|
||||||
|
/// Device is read/write.
|
||||||
|
IOReadWrite
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/* lw-support/src/lib/Enums/IOTimeoutMode.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
/*! \brief I/O timeout modes.
|
||||||
|
|
||||||
|
There are a few different ways for I/O to time out, depending on
|
||||||
|
how you want the program to behave. There can either be a "hard
|
||||||
|
timeout", which specifies the absolute maximum waiting time, or a "soft
|
||||||
|
timeout", which can be overruled by chunks of incoming data. Similarly,
|
||||||
|
it might be acceptable to return with only some data transferred, or it
|
||||||
|
might not.
|
||||||
|
|
||||||
|
*/
|
||||||
|
enum IOTimeoutMode {
|
||||||
|
/*! \brief Hard timeout, partial transfer OK.
|
||||||
|
|
||||||
|
This mode has a hard timeout, but partial data transfers are OK. In
|
||||||
|
this mode, the read is guaranteed to return within the timeout with
|
||||||
|
whatever data is available, but it doesn't guarantee to transfer the
|
||||||
|
full amount of data. No data will be lost. An IOTimeout exception
|
||||||
|
will only be thrown if no data is transferred at all during the
|
||||||
|
specified timeout.
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOTimeoutHardPartial,
|
||||||
|
|
||||||
|
/*! \brief Hard timeout, full data only.
|
||||||
|
|
||||||
|
This mode has a hard timeout during which all data must be
|
||||||
|
transferred. If only partial data transfer occurs, an IOTimeout
|
||||||
|
exception is thrown at the end of the timeout period.
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOTimeoutHardFull,
|
||||||
|
|
||||||
|
/*! \brief Soft timeout, partial transfer OK.
|
||||||
|
|
||||||
|
This mode has a soft timeout, which is to say that the timeout
|
||||||
|
period is restarted every time a partial transfer of data takes
|
||||||
|
place. An IOTimeout exception will only be thrown if no data is
|
||||||
|
transferred at all during the specified timeout.
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOTimeoutSoftPartial,
|
||||||
|
|
||||||
|
/*! \brief Soft timeout, full data only.
|
||||||
|
|
||||||
|
This mode has a soft timeout, which is to say that the timeout
|
||||||
|
period is restarted every time a partial transfer of data takes
|
||||||
|
place. However, upon reaching the end of the timeout period, an
|
||||||
|
exception will be thrown if all the data has not been transferred.
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOTimeoutSoftFull,
|
||||||
|
|
||||||
|
/*! \brief One-shot data transfer.
|
||||||
|
|
||||||
|
This mode is used when you want to perform all available I/O and
|
||||||
|
then return immediately (for instance, you might be listening for
|
||||||
|
commands on a network connection, but not know the size of each
|
||||||
|
command in advance).
|
||||||
|
|
||||||
|
It will always return after it has completed as much data transfer
|
||||||
|
as possible. It will only throw an IOTimeout exception if no data is
|
||||||
|
transferred within the specified time limit.
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOTimeoutOneShot
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* lw-support/src/lib/Enums/ISO8601Format.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Denotes the different formats accepted for ISO8601 dates.
|
||||||
|
enum ISO8601Format {
|
||||||
|
ISO8601FormatDetect, ///< Used to autodetect format when reading.
|
||||||
|
ISO8601FormatCalendar, ///< yyyymmdd
|
||||||
|
ISO8601FormatOrdinal, ///< yyyyddd
|
||||||
|
ISO8601FormatWeekDate ///< yyyyWwwd
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Allows you to specify precision when converting to ISO8601 format.
|
||||||
|
enum ISO8601Precision {
|
||||||
|
ISO8601PrecisionFull, ///< Converts everything.
|
||||||
|
ISO8601PrecisionYear, ///< Only converts year.
|
||||||
|
ISO8601PrecisionWeek, ///< Converts year and week.
|
||||||
|
ISO8601PrecisionMonth, ///< Converts year and month.
|
||||||
|
ISO8601PrecisionDay, ///< Converts year, month and day.
|
||||||
|
ISO8601PrecisionHour, ///< Converts date and hour.
|
||||||
|
ISO8601PrecisionMinute, ///< Converts date, hour and minute.
|
||||||
|
ISO8601PrecisionSecond ///< Converts date and time.
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* lw-support/src/lib/Events/CallbackIO.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// I/O event callback object.
|
||||||
|
class EventCallbackIO {
|
||||||
|
public:
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~EventCallbackIO()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/*! \brief I/O ready callback.
|
||||||
|
|
||||||
|
\param flags A bitfield of events that have occurred.
|
||||||
|
|
||||||
|
This function is called when the device it is associated with becomes ready for I/O. The details
|
||||||
|
will be indicated through the \a flags argument, which is a bitfield of:
|
||||||
|
- IOEventRead
|
||||||
|
- IOEventWrite
|
||||||
|
- IOEventUrgent
|
||||||
|
- IOEventError
|
||||||
|
- IOEventHUP
|
||||||
|
|
||||||
|
Note that you cannot call any lw::EventManager functions within this callback, with the
|
||||||
|
exception of modifyDevice().
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void ioReady(uint32_t flags) throw() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* lw-support/src/lib/Events/CallbackTimer.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Timer event callback object.
|
||||||
|
class EventCallbackTimer {
|
||||||
|
public:
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~EventCallbackTimer()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Timer event callback.
|
||||||
|
|
||||||
|
\param key The key of the timer event.
|
||||||
|
|
||||||
|
This function is called whenever a timer event expires. It must not throw any exceptions. The
|
||||||
|
value of \a key is the value returned when you initially registered the timer.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void timer(Key key) throw() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,22 @@
|
||||||
|
/* lw-support/src/lib/Events/CallbackUpdate.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EventCallbackUpdate::~EventCallbackUpdate()
|
||||||
|
{
|
||||||
|
for(uint i = 0, end = managers.size(); i < end; i++) managers[i]->ignoreUpdate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* lw-support/src/lib/Events/CallbackUpdate.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Update event callback object.
|
||||||
|
|
||||||
|
Using lw::EventManager's update mechanism, objects can (in the event loop) register themselves as
|
||||||
|
requiring an update. After the event loop has completed, all objects needing an update will then be
|
||||||
|
processed. During this processing stage, it is safe for objects to delete themselves or other
|
||||||
|
objects (which may also be on the update list). The update function will be called at most once
|
||||||
|
per event loop, and only if the object has requested that it be added to the update list.
|
||||||
|
|
||||||
|
It is possible for an object to be registered with more than one lw::EventManager's update list at
|
||||||
|
a time; but it will only be called a maximum of once per call to lw::EventManager::processEvents(),
|
||||||
|
regardless of how many times it is registered. Deleting an object which is registered with an
|
||||||
|
lw::EventManager will cause the object to become unregisterd.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class EventCallbackUpdate {
|
||||||
|
public:
|
||||||
|
/// Destructor. Unregisters the device from any update lists it may be in.
|
||||||
|
virtual ~EventCallbackUpdate();
|
||||||
|
|
||||||
|
/// Update callback. The object can safely delete itself in here.
|
||||||
|
virtual void update(void) throw() = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class EventManager;
|
||||||
|
std::vector<lw::EventManager*> managers;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,385 @@
|
||||||
|
/* lw-support/src/lib/Events/EventManager.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const uint32_t IOEventRead = EPOLLIN;
|
||||||
|
const uint32_t IOEventWrite = EPOLLOUT;
|
||||||
|
const uint32_t IOEventHUP = EPOLLHUP;
|
||||||
|
const uint32_t IOEventUrgent = EPOLLPRI;
|
||||||
|
const uint32_t IOEventError = EPOLLERR;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct EventManager::pimpl {
|
||||||
|
pimpl()
|
||||||
|
: epollFd(-1), epollEvents(0), numEvents(0), shutdownFlag(false), processing(false),
|
||||||
|
last_ns(now_ns())
|
||||||
|
{
|
||||||
|
pipeFd[0] = -1;
|
||||||
|
pipeFd[1] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
~pimpl()
|
||||||
|
{
|
||||||
|
delete [] epollEvents;
|
||||||
|
close(epollFd);
|
||||||
|
close(pipeFd[0]);
|
||||||
|
close(pipeFd[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// epoll-related structures
|
||||||
|
int epollFd;
|
||||||
|
struct epoll_event* epollEvents;
|
||||||
|
int numEvents;
|
||||||
|
|
||||||
|
// pipe for signals/shutdown
|
||||||
|
int pipeFd[2];
|
||||||
|
|
||||||
|
// operational status
|
||||||
|
bool shutdownFlag, processing;
|
||||||
|
class ModifiedWhileProcessing : public lw::ProgramException { };
|
||||||
|
void processingCheck(const wchar_t* chain)
|
||||||
|
{
|
||||||
|
if(processing) throw ModifiedWhileProcessing().chain(chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// timer events
|
||||||
|
uint64_t last_ns;
|
||||||
|
static uint64_t now_ns()
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
uint64_t ret = tv.tv_sec;
|
||||||
|
ret *= tenExp9;
|
||||||
|
ret += tv.tv_usec * tenExp3;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimerEntry {
|
||||||
|
EventCallbackTimer* timerCallback;
|
||||||
|
ElapsedTime remainingPeriod, recurringPeriod;
|
||||||
|
Key key;
|
||||||
|
|
||||||
|
TimerEntry(EventCallbackTimer* timerCallback, ElapsedTime initialPeriod,
|
||||||
|
ElapsedTime recurringPeriod)
|
||||||
|
: timerCallback(timerCallback), remainingPeriod(initialPeriod),
|
||||||
|
recurringPeriod(recurringPeriod), key(Key::get())
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TimerCmp {
|
||||||
|
bool operator()(const struct TimerEntry& a, const struct TimerEntry& b)
|
||||||
|
{
|
||||||
|
return a.remainingPeriod < b.remainingPeriod;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TimerEntry> timers; // note -- this is a sorted list!
|
||||||
|
|
||||||
|
enum InternalEvent {
|
||||||
|
InternalEventShutdown
|
||||||
|
};
|
||||||
|
|
||||||
|
void dieHorribly()
|
||||||
|
{
|
||||||
|
processing = false;
|
||||||
|
shutdownFlag = true;
|
||||||
|
throw lw::ProgramException()
|
||||||
|
.chain(L"EventManager::pimpl::dieHorribly()")
|
||||||
|
.chain(L"EventManager::processEvents()");
|
||||||
|
}
|
||||||
|
|
||||||
|
void processInternalEvents()
|
||||||
|
{
|
||||||
|
InternalEvent ev;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
ssize_t ret = read(pipeFd[pipeRead], (char*)&ev, sizeof(ev));
|
||||||
|
if(ret == -1 && errno == EAGAIN) break;
|
||||||
|
if(ret != sizeof(ev)) dieHorribly();
|
||||||
|
|
||||||
|
switch(ev) {
|
||||||
|
case InternalEventShutdown:
|
||||||
|
shutdownFlag = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dieHorribly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// objects registered for updates (sorted list)
|
||||||
|
std::vector<EventCallbackUpdate*> updateList;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EventManager::EventManager(int numDevicesHint, int numEvents)
|
||||||
|
: p(new pimpl)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if(numDevicesHint < 0) throw BadArgument(L"numDevicesHint", L">= 0", numDevicesHint);
|
||||||
|
if(numEvents < 1) throw BadArgument(L"numEvents", L">= 1", numEvents);
|
||||||
|
|
||||||
|
// create epoll structure
|
||||||
|
p->numEvents = numEvents;
|
||||||
|
p->epollEvents = new struct epoll_event[numEvents];
|
||||||
|
p->epollFd = epoll_create(numDevicesHint + 1); // for the pipe
|
||||||
|
if(p->epollFd < 0) throw SystemError().chain(L"epoll_create()");
|
||||||
|
|
||||||
|
// set up pipes (non-blocking mode required)
|
||||||
|
if(pipe(p->pipeFd) == -1) throw SystemError().chain(L"pipe()");
|
||||||
|
int pipeFdFlags;
|
||||||
|
if( (pipeFdFlags = fcntl(p->pipeFd[0], F_GETFL, 0)) == -1 ||
|
||||||
|
fcntl(p->pipeFd[0], F_SETFL, pipeFdFlags | O_NONBLOCK) ||
|
||||||
|
(pipeFdFlags = fcntl(p->pipeFd[1], F_GETFL, 0)) == -1 ||
|
||||||
|
fcntl(p->pipeFd[1], F_SETFL, pipeFdFlags | O_NONBLOCK))
|
||||||
|
{
|
||||||
|
throw SystemError().chain(L"fcntl()");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct epoll_event ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.events = EPOLLIN | EPOLLERR | EPOLLET;
|
||||||
|
if(epoll_ctl(p->epollFd, EPOLL_CTL_ADD, p->pipeFd[pipeRead], &ev))
|
||||||
|
throw SystemError().chain(L"epoll_ctl(EPOLL_CTL_ADD)");
|
||||||
|
}
|
||||||
|
catch(lw::Exception& e) {
|
||||||
|
delete p;
|
||||||
|
e.chain(L"EventManager::EventManager()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EventManager::~EventManager()
|
||||||
|
{
|
||||||
|
delete p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::processEvents(int userTimeout)
|
||||||
|
{
|
||||||
|
if(p->shutdownFlag) throw Shutdown();
|
||||||
|
|
||||||
|
// find the shortest required timeout
|
||||||
|
int timerTimeout = p->timers.empty() ? -1 : (int)(p->timers[0].remainingPeriod.to_ms());
|
||||||
|
int timeout = std::min(userTimeout, timerTimeout);
|
||||||
|
if(timeout < 0) {
|
||||||
|
if(userTimeout >= 0) timeout = userTimeout;
|
||||||
|
else if(timerTimeout >= 0) timeout = timerTimeout;
|
||||||
|
else timeout = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the event loop
|
||||||
|
int ret = epoll_wait(p->epollFd, p->epollEvents, p->numEvents, timeout);
|
||||||
|
|
||||||
|
if(ret == -1) {
|
||||||
|
if(errno == EINTR) return;
|
||||||
|
throw SystemError().chain(L"epoll_wait()").chain(L"EventManager::processEvents()");
|
||||||
|
}
|
||||||
|
if(!ret && timerTimeout == -1) throw Timeout();
|
||||||
|
|
||||||
|
// process events
|
||||||
|
for(int i = 0; i < ret; ++i) {
|
||||||
|
EventCallbackIO* cb = (EventCallbackIO*)(p->epollEvents[i].data.ptr);
|
||||||
|
if(cb) cb->ioReady(p->epollEvents[i].events);
|
||||||
|
else p->processInternalEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
// evaluate timers
|
||||||
|
uint64_t now_ns = p->now_ns(), ns_elapsed = now_ns - p->last_ns;
|
||||||
|
p->last_ns = now_ns;
|
||||||
|
ElapsedTime elapsedTime = ElapsedTime::from_ns(ns_elapsed);
|
||||||
|
|
||||||
|
bool timerFired = false;
|
||||||
|
int timerErased = 0;
|
||||||
|
for(uint i = 0, end = p->timers.size(); i < end; ++i) {
|
||||||
|
if(p->timers[i].remainingPeriod <= elapsedTime) {
|
||||||
|
// fire timer
|
||||||
|
timerFired = true;
|
||||||
|
|
||||||
|
if(!p->timers[i].recurringPeriod) {
|
||||||
|
p->timers[i].timerCallback->timer(p->timers[i].key);
|
||||||
|
p->timers[i].remainingPeriod.set_ns(~(uint64_t)0);
|
||||||
|
++timerErased;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t remaining = ns_elapsed,
|
||||||
|
oldPeriod = p->timers[i].remainingPeriod.to_ns(),
|
||||||
|
recur = p->timers[i].recurringPeriod.to_ns();
|
||||||
|
while(true) {
|
||||||
|
p->timers[i].timerCallback->timer(p->timers[i].key);
|
||||||
|
oldPeriod += recur;
|
||||||
|
if(remaining < oldPeriod) break;
|
||||||
|
remaining -= recur;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldPeriod -= remaining;
|
||||||
|
p->timers[i].remainingPeriod.set_ns(oldPeriod);
|
||||||
|
} else {
|
||||||
|
p->timers[i].remainingPeriod -= elapsedTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resort list if appropriate
|
||||||
|
if(timerFired) {
|
||||||
|
std::stable_sort(p->timers.begin(), p->timers.end(), pimpl::TimerCmp());
|
||||||
|
if(timerErased) p->timers.erase(p->timers.end() - timerErased, p->timers.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// deal with all the objects that need to be updated
|
||||||
|
int x;
|
||||||
|
while((x = p->updateList.size())) {
|
||||||
|
--x;
|
||||||
|
p->updateList[x]->update();
|
||||||
|
ignoreUpdate(p->updateList[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::registerDevice(IODevice* ioDevice, EventCallbackIO* eventCallback,
|
||||||
|
uint32_t eventFlags)
|
||||||
|
{
|
||||||
|
p->processingCheck(L"EventManager::registerDevice()");
|
||||||
|
NullPointer::check(ioDevice, L"ioDevice", L"EventManager::registerDevice()");
|
||||||
|
NullPointer::check(eventCallback, L"eventCallback", L"EventManager::registerDevice()");
|
||||||
|
|
||||||
|
struct epoll_event ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.data.ptr = eventCallback;
|
||||||
|
ev.events = (eventFlags & (EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLERR | EPOLLHUP)) | EPOLLET;
|
||||||
|
if(epoll_ctl(p->epollFd, EPOLL_CTL_ADD, ioDevice->getListenFd(), &ev)) throw SystemError()
|
||||||
|
.chain(L"epoll_ctl(EPOLL_CTL_ADD)").chain(L"EventManager::registerDevice()");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::modifyDevice(IODevice* ioDevice, EventCallbackIO* eventCallback,
|
||||||
|
uint32_t eventFlags)
|
||||||
|
{
|
||||||
|
NullPointer::check(ioDevice, L"ioDevice", L"EventManager::modifyDevice()");
|
||||||
|
NullPointer::check(eventCallback, L"eventCallback", L"EventManager::modifyDevice()");
|
||||||
|
|
||||||
|
struct epoll_event ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.data.ptr = eventCallback;
|
||||||
|
ev.events = (eventFlags & (EPOLLIN | EPOLLOUT | EPOLLPRI | EPOLLERR | EPOLLHUP)) | EPOLLET;
|
||||||
|
if(epoll_ctl(p->epollFd, EPOLL_CTL_MOD, ioDevice->getListenFd(), &ev)) throw SystemError()
|
||||||
|
.chain(L"epoll_ctl(EPOLL_CTL_ADD)").chain(L"EventManager::modifyDevice()");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::ignoreDevice(IODevice* ioDevice)
|
||||||
|
{
|
||||||
|
p->processingCheck(L"EventManager::ignoreDevice()");
|
||||||
|
NullPointer::check(ioDevice, L"ioDevice", L"EventManager::modifyDevice()");
|
||||||
|
|
||||||
|
if(epoll_ctl(p->epollFd, EPOLL_CTL_DEL, ioDevice->getListenFd(), 0)) throw SystemError()
|
||||||
|
.chain(L"epoll_ctl(EPOLL_CTL_DEL)").chain(L"EventManager::ignoreDevice()");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Key EventManager::registerTimer(EventCallbackTimer* timerCallback, ElapsedTime initialPeriod,
|
||||||
|
ElapsedTime recurringPeriod)
|
||||||
|
{
|
||||||
|
p->processingCheck(L"EventManager::registerTimer()");
|
||||||
|
NullPointer::check(timerCallback, L"timerCallback", L"EventManager::registerTimer()");
|
||||||
|
if(!recurringPeriod) throw BadArgument(L"recurringPeriod", L"non-zero", L"zero");
|
||||||
|
|
||||||
|
pimpl::TimerEntry t(timerCallback, initialPeriod, recurringPeriod);
|
||||||
|
p->timers.insert(std::upper_bound(p->timers.begin(), p->timers.end(), t, pimpl::TimerCmp()), t);
|
||||||
|
return t.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Key EventManager::registerTimer(EventCallbackTimer* timerCallback, ElapsedTime period)
|
||||||
|
{
|
||||||
|
p->processingCheck(L"EventManager::registerTimer()");
|
||||||
|
NullPointer::check(timerCallback, L"timerCallback", L"EventManager::registerTimer()");
|
||||||
|
|
||||||
|
pimpl::TimerEntry t(timerCallback, period, lw::ElapsedTime());
|
||||||
|
p->timers.insert(std::upper_bound(p->timers.begin(), p->timers.end(), t, pimpl::TimerCmp()), t);
|
||||||
|
return t.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::ignoreTimer(Key key)
|
||||||
|
{
|
||||||
|
p->processingCheck(L"EventManager::ignoreTimer()");
|
||||||
|
for(uint i = 0, end = p->timers.size(); i < end; ++i) {
|
||||||
|
if(p->timers[i].key == key) {
|
||||||
|
p->timers.erase(p->timers.begin() + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::registerUpdate(lw::EventCallbackUpdate* updateCallback)
|
||||||
|
{
|
||||||
|
// search for it in the list, in case it's already registered
|
||||||
|
std::vector<EventCallbackUpdate*>::iterator iter =
|
||||||
|
std::lower_bound(p->updateList.begin(), p->updateList.end(), updateCallback);
|
||||||
|
if(*iter == updateCallback) return;
|
||||||
|
|
||||||
|
// do a sorted insert
|
||||||
|
p->updateList.insert(iter, updateCallback);
|
||||||
|
|
||||||
|
// register ourselves with it, in case it's deleted
|
||||||
|
updateCallback->managers.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::ignoreUpdate(lw::EventCallbackUpdate* updateCallback)
|
||||||
|
{
|
||||||
|
// search for it in the list, return if not found
|
||||||
|
std::vector<EventCallbackUpdate*>::iterator iter =
|
||||||
|
std::lower_bound(p->updateList.begin(), p->updateList.end(), updateCallback);
|
||||||
|
if(*iter != updateCallback) return;
|
||||||
|
|
||||||
|
// remove it, unregister with it
|
||||||
|
std::vector<EventManager*>& evlist = (*iter)->managers;
|
||||||
|
p->updateList.erase(iter);
|
||||||
|
|
||||||
|
for(uint i = 0, end = evlist.size(); i < end; ++i) {
|
||||||
|
if(evlist[i] == this) {
|
||||||
|
evlist.erase(evlist.begin() + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void EventManager::shutdown()
|
||||||
|
{
|
||||||
|
pimpl::InternalEvent ev = pimpl::InternalEventShutdown;
|
||||||
|
write(p->pipeFd[pipeWrite], (char*)&ev, sizeof(ev));
|
||||||
|
// TODO: yes, we ignore error handling here -- but what can we do?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,265 @@
|
||||||
|
/* lw-support/src/lib/Events/EventManager.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Flag to denote I/O device has input available.
|
||||||
|
extern const uint32_t IOEventRead;
|
||||||
|
|
||||||
|
/// Flag to denote I/O device is ready for output.
|
||||||
|
extern const uint32_t IOEventWrite;
|
||||||
|
|
||||||
|
/// Flag to denote I/O device has urgent input available.
|
||||||
|
extern const uint32_t IOEventUrgent;
|
||||||
|
|
||||||
|
/// Flag to denote I/O device has developed an error condition.
|
||||||
|
extern const uint32_t IOEventError;
|
||||||
|
|
||||||
|
/// Flag to denote I/O device has signalled HUP.
|
||||||
|
extern const uint32_t IOEventHUP;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Event manager.
|
||||||
|
|
||||||
|
This object is responsible for driving an event-driven application. It collects system events (I/O,
|
||||||
|
timers and signals) and dispatches them appropriately. This class uses \c epoll() (which is a
|
||||||
|
\c select() replacement) to implement fast and efficient I/O event notification. To use it,
|
||||||
|
interested objects must register themselves through the various registration functions.
|
||||||
|
|
||||||
|
To poll for events, call the processEvents() function. This will process any events in the queue or,
|
||||||
|
if the queue is empty, will wait (possibly only for a limited time) for more. Object processing is
|
||||||
|
split into two phases within this function: first, all of the I/O, timer and signal callbacks are
|
||||||
|
processed, and then the update callbacks.
|
||||||
|
|
||||||
|
During the first phase of callbacks, it is not legal for objects to delete themselves, delete other
|
||||||
|
objects registered with the event manager, or to call any of the event manager functions (other than
|
||||||
|
modifyDevice() and registerUpdate()). During the second phase (the update callbacks), these
|
||||||
|
restrictions no longer apply. It is not possible to throw exceptions from callback functions.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class EventManager : public Uncopyable {
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor. Sets up event manager.
|
||||||
|
|
||||||
|
\param numDevicesHint The maximum likely number of devices that will be polled.
|
||||||
|
\param numEvents The number of events to process in a single loop.
|
||||||
|
\throws SystemError if an error occurs.
|
||||||
|
|
||||||
|
The constructor creates and sets up the initial event manager. It expects two parameters: a
|
||||||
|
hint as to how many devices you might be interested in (the kernel uses this to pre-allocate
|
||||||
|
space, but the buffer size is dynamic anyway), and the number of events to process at once. This
|
||||||
|
parameter is actually the size of the buffer the kernel uses to pass events to userspace; a
|
||||||
|
larger buffer means more events to process per system call, but at the expense of memory usage.
|
||||||
|
A reasonable value is to match \a numDevicesHint.
|
||||||
|
|
||||||
|
*/
|
||||||
|
EventManager(int numDevicesHint, int numEvents);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Destructor. Cleans up.
|
||||||
|
~EventManager();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Register interest in a device's I/O readiness.
|
||||||
|
|
||||||
|
\param ioDevice The I/O device in which you are interested.
|
||||||
|
\param eventCallback The event sink object.
|
||||||
|
\param eventFlags A bitfield of flags signifying which events you are interested in receiving.
|
||||||
|
You may pass \c ~0 if you are interested in all events.
|
||||||
|
\throws SystemError on error.
|
||||||
|
\throws NullPointer if \a ioDevice or \a eventCallback are null.
|
||||||
|
|
||||||
|
This function will register interest in receiving readiness notification for an I/O device. You
|
||||||
|
specify the event sink object, which receives a callback for each readiness change, and the
|
||||||
|
eventFlags object, which is a bitfield comprising the events you are interested in, and may
|
||||||
|
include:
|
||||||
|
- lw::IOEventRead
|
||||||
|
- lw::IOEventWrite
|
||||||
|
- lw::IOEventUrgent
|
||||||
|
- lw::IOEventError
|
||||||
|
- lw::IOEventHUP
|
||||||
|
|
||||||
|
Once registered, you may cancel the callback by closing the device (once the kernel detects
|
||||||
|
the file descriptor is closed, it will automatically remove it from the list of devices we are
|
||||||
|
interested in), or explicitly by calling ignoreDevice() with the same parameters. If you do
|
||||||
|
close the device's file descriptor, you should not call ignoreDevice().
|
||||||
|
|
||||||
|
Note that it is your responsibility to free the EventCallbackIO object after calling
|
||||||
|
ignoreDevice() or closing the file descriptor.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void registerDevice(lw::IODevice* ioDevice, lw::EventCallbackIO* eventCallback,
|
||||||
|
uint32_t eventFlags);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Modify device.
|
||||||
|
|
||||||
|
\param ioDevice The I/O device in which you are interested.
|
||||||
|
\param eventCallback The event sink object.
|
||||||
|
\param eventFlags A bitfield of flags signifying which events you are interested in receiving.
|
||||||
|
You may pass \c ~0 if you are interested in all events.
|
||||||
|
\throws SystemError on error.
|
||||||
|
\throws NullPointer if \a ioDevice or \a eventCallback are null.
|
||||||
|
|
||||||
|
This is like registerDevice(), only it modifies the information for a device you have already
|
||||||
|
added. It can be used to change the callback object or to change the events in which you are
|
||||||
|
interested. It can be called at any time, even from within a callback.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void modifyDevice(lw::IODevice* ioDevice, lw::EventCallbackIO* eventCallback,
|
||||||
|
uint32_t eventFlags);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Stop listening to a device.
|
||||||
|
|
||||||
|
\param ioDevice The I/O device in which you are no longer interested.
|
||||||
|
\throws SystemError on error.
|
||||||
|
\throws NullPointer if \a ioDevice is null.
|
||||||
|
|
||||||
|
This stops reporting events for a particular device. It does not delete the event callback
|
||||||
|
object or close the device. If you have already closed the device, you do not need to call this
|
||||||
|
function, since devices are automatically removed from the epoll set when their file descriptors
|
||||||
|
are closed.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void ignoreDevice(lw::IODevice* ioDevice);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Register a timer callback.
|
||||||
|
|
||||||
|
\param timerCallback The callback object.
|
||||||
|
\param initialPeriod The initial time to wait before calling the callback.
|
||||||
|
\param recurringPeriod After the initial callback, this specifies the period at which the event
|
||||||
|
will be repeated.
|
||||||
|
\throws NullPointer if \a timerCallback is null.
|
||||||
|
\throws BadArgument if \a initialPeriod or \a recurringPeriod are zero.
|
||||||
|
\returns A unique key.
|
||||||
|
|
||||||
|
This function allows you to register a timer object for regular callback events. It returns a
|
||||||
|
unique key that can be used with \a ignoreTimer (and which is also passed to the
|
||||||
|
EventCallbackTimer::timer() callback function).
|
||||||
|
|
||||||
|
This system is limited by the timing resolution of the operating system, which is typically on
|
||||||
|
the order of 1-10 milliseconds. However, the average frequency over time will match what you
|
||||||
|
request; the events will simply be bunched up.
|
||||||
|
|
||||||
|
*/
|
||||||
|
lw::Key registerTimer(lw::EventCallbackTimer* timerCallback, lw::ElapsedTime initialPeriod,
|
||||||
|
lw::ElapsedTime recurringPeriod);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Register a one-shot timer callback.
|
||||||
|
|
||||||
|
\param timerCallback The callback object.
|
||||||
|
\param period The time to wait before calling the callback.
|
||||||
|
\throws NullPointer if \a timerCallback is null.
|
||||||
|
\throws BadArgument if \a period is zero.
|
||||||
|
\returns A unique key.
|
||||||
|
|
||||||
|
This function allows you to register a timer object for a single callback event. It returns a
|
||||||
|
unique key that can be used with \a ignoreTimer (and which is also passed to the
|
||||||
|
EventCallbackTimer::timer() callback function).
|
||||||
|
|
||||||
|
This system is limited by the timing resolution of the operating system, which is typically on
|
||||||
|
the order of 1-10 milliseconds.
|
||||||
|
|
||||||
|
*/
|
||||||
|
lw::Key registerTimer(lw::EventCallbackTimer* timerCallback, lw::ElapsedTime period);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Stop a timer event.
|
||||||
|
|
||||||
|
\param key The unique key returned from registerTimer().
|
||||||
|
|
||||||
|
Calling this function will stop a timer event from occuring. If the value of \a key you
|
||||||
|
specified does not exist, it will be ignored.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void ignoreTimer(lw::Key key);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Register an object for updating.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void registerUpdate(lw::EventCallbackUpdate* updateCallback);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Remove an object from the update list.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void ignoreUpdate(lw::EventCallbackUpdate* updateCallback);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Process events.
|
||||||
|
|
||||||
|
\param timeout The timeout, in milliseconds. Passing 0 means the
|
||||||
|
function will time out or return immediately; passing a negative
|
||||||
|
value means it will never time out.
|
||||||
|
\throws SystemError if an error occurred. This could either be an
|
||||||
|
error from the epoll_wait() function used internally, or an
|
||||||
|
asynchronous error from the backing thread. In either case, it
|
||||||
|
is only safe to delete the object and propagate the error.
|
||||||
|
\throws EventManager::Shutdown if the object has been shut down.
|
||||||
|
\throws Timeout if no events are processed during the specified
|
||||||
|
timeout period.
|
||||||
|
|
||||||
|
This function will wait until there are some events to process. It
|
||||||
|
will then call the relevant callbacks and return. See the
|
||||||
|
registerDevice() function for more information about callbacks.
|
||||||
|
|
||||||
|
This function is not reentrant. It should only ever be called by one
|
||||||
|
thread at once.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void processEvents(int timeout);
|
||||||
|
|
||||||
|
/// Thrown when an EventManager times out.
|
||||||
|
class Timeout : public ProgramException { };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Shut down the object.
|
||||||
|
|
||||||
|
\throws SystemError on error.
|
||||||
|
|
||||||
|
This function marks the object as shut down. Any thread currently
|
||||||
|
waiting in processEvents(), or that calls processEvents() from now
|
||||||
|
on, will be
|
||||||
|
|
||||||
|
This function is thread safe and reentrant.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/// Thrown when an EventManager is shut down.
|
||||||
|
class Shutdown : public ProgramException { };
|
||||||
|
|
||||||
|
private:
|
||||||
|
// use pimpl here, since this class will only be created infrequently
|
||||||
|
struct pimpl;
|
||||||
|
pimpl* p;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/BadArgument.cpp
|
||||||
|
*
|
||||||
|
* (c)2006, Laurence Withers. Released under the GNU GPL. See file COPYING for more information
|
||||||
|
* and terms of license, or read http://www.gnu.org/copyleft/gpl.html .
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BadArgument::BadArgument(const std::wstring& argName, const std::wstring& acceptableRange,
|
||||||
|
const std::wstring& value)
|
||||||
|
: Exception(format(argName, acceptableRange, value)), argName(argName),
|
||||||
|
acceptableRange(acceptableRange), value(value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring BadArgument::format(const std::wstring& argName, const std::wstring& acceptableRange,
|
||||||
|
const std::wstring& value)
|
||||||
|
{
|
||||||
|
std::wostringstream ostr;
|
||||||
|
ostr << "Invalid argument to function."
|
||||||
|
"\n Argument name : " << argName <<
|
||||||
|
"\n Acceptable range: " << acceptableRange <<
|
||||||
|
"\n Actual value : " << value;
|
||||||
|
return ostr.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,78 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/BadArgument.h
|
||||||
|
*
|
||||||
|
* (c)2006, Laurence Withers. Released under the GNU GPL. See file COPYING for more information
|
||||||
|
* and terms of license, or read http://www.gnu.org/copyleft/gpl.html .
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Exception class: bad argument to function.
|
||||||
|
|
||||||
|
This exception class is used when an invalid argument is passed to a function (for instance, a
|
||||||
|
negative size in bytes, etc.). It stores some useful details for tracking down the bad argument.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class BadArgument : public lw::Exception {
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param argName Name of the invalid argument.
|
||||||
|
\param acceptableRange Human-understandable range of valid arguments.
|
||||||
|
\param value Actual value passed to function.
|
||||||
|
|
||||||
|
*/
|
||||||
|
BadArgument(const std::wstring& argName, const std::wstring& acceptableRange,
|
||||||
|
const std::wstring& value);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param argName Name of the invalid argument.
|
||||||
|
\param acceptableRange Human-understandable range of valid arguments.
|
||||||
|
\param value Actual value passed to function.
|
||||||
|
|
||||||
|
*/
|
||||||
|
template<class T> BadArgument(const std::wstring& argName, const std::wstring& acceptableRange,
|
||||||
|
const T& value)
|
||||||
|
: Exception(format(argName, acceptableRange, mkValue(value))), argName(argName),
|
||||||
|
acceptableRange(acceptableRange), value(mkValue(value))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Destructor (does nothing).
|
||||||
|
virtual ~BadArgument() throw() { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Name of the invalid argument.
|
||||||
|
const std::wstring argName;
|
||||||
|
|
||||||
|
/// Human-understandable range of valid arguments.
|
||||||
|
const std::wstring acceptableRange;
|
||||||
|
|
||||||
|
/// Actual value passed to function.
|
||||||
|
const std::wstring value;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class T> std::wstring mkValue(const T& value)
|
||||||
|
{
|
||||||
|
std::wostringstream ostr;
|
||||||
|
ostr << value;
|
||||||
|
return ostr.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring format(const std::wstring& argName, const std::wstring& acceptableRange,
|
||||||
|
const std::wstring& value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/BadString.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring BadSourceChar::buildDesc(const char* seqStart,
|
||||||
|
const char* seqEnd, int seqOffset, const std::wstring& encodingName,
|
||||||
|
const std::wstring& reason)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << L"Error parsing a string. Details:\n"
|
||||||
|
L" Encoding : " << encodingName << L"\n"
|
||||||
|
L" Reason : " << reason << L"\n"
|
||||||
|
L" Byte offset : " << seqOffset << L"\n"
|
||||||
|
L" Bad sequence: ";
|
||||||
|
o << std::hex << std::setfill(L'0');
|
||||||
|
for(; seqStart != seqEnd; ++seqStart)
|
||||||
|
o << std::setw(2) << (uint)(uint8_t)(*seqStart);
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BadSourceChar::BadSourceChar(const char* seqStart, const char* seqEnd,
|
||||||
|
int seqOffset, const std::wstring& encodingName, const std::wstring& reason)
|
||||||
|
: BadString(buildDesc(
|
||||||
|
seqStart, seqEnd, seqOffset, encodingName, reason)),
|
||||||
|
byteSeq(seqStart, seqEnd), seqOffset(seqOffset),
|
||||||
|
encoding(encodingName), reason(reason)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring CannotConvertChar::buildDesc(wchar_t srcChar, int seqOffset,
|
||||||
|
const std::wstring& srcEnc, const std::wstring& destEnc)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << L"Error converting a string. Details:\n"
|
||||||
|
L" Source encoding : " << srcEnc << L"\n"
|
||||||
|
L" Destination encoding: " << destEnc << L"\n"
|
||||||
|
L" Byte offset : " << seqOffset << L"\n"
|
||||||
|
L" Source character : 0x" << std::hex << uint(srcChar)
|
||||||
|
<< L", ``" << srcChar << L"''.";
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CannotConvertChar::CannotConvertChar(wchar_t srcChar, int seqOffset,
|
||||||
|
const std::wstring& srcEnc, const std::wstring& destEnc)
|
||||||
|
: BadString(buildDesc(srcChar, seqOffset, srcEnc, destEnc)),
|
||||||
|
srcChar(srcChar), seqOffset(seqOffset), srcEnc(srcEnc), destEnc(destEnc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/BadString.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Base exception class for string-related errors.
|
||||||
|
|
||||||
|
This simply serves as the base class for string- and character-related
|
||||||
|
errors.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class BadString : public Exception {
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param desc A description of the error.
|
||||||
|
|
||||||
|
*/
|
||||||
|
BadString(const std::wstring& desc)
|
||||||
|
: Exception(desc)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~BadString() throw() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Cannot represent source character.
|
||||||
|
|
||||||
|
This exception is thrown when a source character in a given encoding
|
||||||
|
cannot be represented in the destination encoding (e.g. ASCII cannot
|
||||||
|
represent arbitrary Unicode characters).
|
||||||
|
|
||||||
|
*/
|
||||||
|
class CannotConvertChar : public BadString {
|
||||||
|
private:
|
||||||
|
wchar_t srcChar;
|
||||||
|
int seqOffset;
|
||||||
|
std::wstring srcEnc, destEnc;
|
||||||
|
|
||||||
|
static std::wstring buildDesc(wchar_t srcChar, int seqOffset,
|
||||||
|
const std::wstring& srcEnc, const std::wstring& destEnc);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param srcChar The (Unicode) code point that cannot be represented.
|
||||||
|
\param seqOffset How far in the source string the conversion process
|
||||||
|
reached before encountering the code point.
|
||||||
|
\param srcEnc The name of the source encoding, e.g. "UCS-4".
|
||||||
|
\param destEnc The name of the destination encoding, e.g. "ASCII".
|
||||||
|
|
||||||
|
*/
|
||||||
|
CannotConvertChar(wchar_t srcChar, int seqOffset,
|
||||||
|
const std::wstring& srcEnc, const std::wstring& destEnc);
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~CannotConvertChar() throw()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Find the source character (Unicode).
|
||||||
|
wchar_t getSourceChar() const
|
||||||
|
{ return srcChar; }
|
||||||
|
|
||||||
|
/// Find the offset in the source sequence.
|
||||||
|
int getSeqOffset() const
|
||||||
|
{ return seqOffset; }
|
||||||
|
|
||||||
|
/// Find the source encoding.
|
||||||
|
const std::wstring& getSourceEncoding()
|
||||||
|
{ return srcEnc; }
|
||||||
|
|
||||||
|
/// Find the destination encoding.
|
||||||
|
const std::wstring& getDestEncoding()
|
||||||
|
{ return destEnc; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Bad source character.
|
||||||
|
|
||||||
|
This exception is thrown when converting a string from one encoding to
|
||||||
|
another, and a byte sequence in the source string is not a valid
|
||||||
|
character. This can happen with UTF-8 for example.
|
||||||
|
|
||||||
|
The exception provides some context about where in the conversion
|
||||||
|
process the error occurred, potentially allowing a continuation of the
|
||||||
|
process, but certainly providing a detailed human-readable description
|
||||||
|
of the error.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class BadSourceChar : public BadString {
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> byteSeq; // the invalid byte sequence
|
||||||
|
int seqOffset;
|
||||||
|
std::wstring encoding; // the source encoding name
|
||||||
|
std::wstring reason; // a specific reason (if there is one)
|
||||||
|
|
||||||
|
static std::wstring buildDesc(const char* seqStart, const char* seqEnd,
|
||||||
|
int seqOffset, const std::wstring& encodingName,
|
||||||
|
const std::wstring& reason);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Construct from a byte sequence.
|
||||||
|
|
||||||
|
\param seqStart Pointer to the start of the invalid byte sequence.
|
||||||
|
\param seqEnd Pointer to one-past-the-end byte of the invalid byte
|
||||||
|
sequence.
|
||||||
|
\param seqOffset Offset (in bytes) of the invalid sequence from the
|
||||||
|
start of the string. This could potentially be used to restart a
|
||||||
|
conversion.
|
||||||
|
\param encodingName Name of the encoding (in UCS-4).
|
||||||
|
\param reason A specific reason for the character being bad, if one
|
||||||
|
can be ascertained.
|
||||||
|
|
||||||
|
*/
|
||||||
|
BadSourceChar(const char* seqStart, const char* seqEnd,
|
||||||
|
int seqOffset, const std::wstring& encodingName,
|
||||||
|
const std::wstring& reason = std::wstring());
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~BadSourceChar() throw() { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Query the invalid byte sequence.
|
||||||
|
const std::vector<uint8_t>& getByteSeq() const
|
||||||
|
{ return byteSeq; }
|
||||||
|
|
||||||
|
/// Query the error offset.
|
||||||
|
int getSeqOffset() const
|
||||||
|
{ return seqOffset; }
|
||||||
|
|
||||||
|
/// Query the encoding.
|
||||||
|
const std::wstring& getEncoding() const
|
||||||
|
{ return encoding; }
|
||||||
|
|
||||||
|
/// Query the reason.
|
||||||
|
const std::wstring& getReason() const
|
||||||
|
{ return reason; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/BadTime.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Base class of all time-related exceptions.
|
||||||
|
class BadTime : public Exception {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
BadTime(const std::wstring& errorDesc)
|
||||||
|
: Exception(errorDesc)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~BadTime() throw()
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Thrown when a time operation overflows.
|
||||||
|
|
||||||
|
This exception will be thrown when a subtraction operation on an elapsed
|
||||||
|
time period would have resulted in a negative time period. Since this
|
||||||
|
result does not make sense, the operation cannot proceed.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class BadTimeOverflow : public BadTime {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
BadTimeOverflow() : BadTime(L"Cannot represent negative time periods.")
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Thrown when a time value is bad.
|
||||||
|
|
||||||
|
This exception is thrown when a time value is bad (for instance, you are
|
||||||
|
trying to specify a negative period, or a minute of 61, etc.).
|
||||||
|
|
||||||
|
*/
|
||||||
|
class BadTimeValue : public BadTime {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
BadTimeValue() : BadTime(L"Bad time value.")
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Thrown when a bad date is specified.
|
||||||
|
class BadDate : public BadTime {
|
||||||
|
public:
|
||||||
|
BadDate() : BadTime(L"Bad date specified.")
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/DNS.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring DnsError::buildDesc(const std::wstring& reason,
|
||||||
|
const std::string& host)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
o << L"DNS operation failed.\n"
|
||||||
|
L" Description: " << reason << L"\n"
|
||||||
|
L" Host : " << utf8ToUcs4(host, true);
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DnsError::DnsError(const std::wstring& reason, const std::string& host)
|
||||||
|
: Exception(buildDesc(reason, host)),
|
||||||
|
reason(reason), host(host)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/DNS.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Thrown when a DNS error occurs.
|
||||||
|
|
||||||
|
This class is thrown when a DNS lookup fails. It contains the name
|
||||||
|
of the host being looked up, and a description of what failed.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class DnsError : public Exception {
|
||||||
|
private:
|
||||||
|
std::wstring reason;
|
||||||
|
std::string host;
|
||||||
|
|
||||||
|
static std::wstring buildDesc(const std::wstring& reason,
|
||||||
|
const std::string& host);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param reason A description of what failed.
|
||||||
|
\param host The host name being manipulated.
|
||||||
|
|
||||||
|
*/
|
||||||
|
DnsError(const std::wstring& reason,
|
||||||
|
const std::string& host);
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~DnsError() throw()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Get reason for failure.
|
||||||
|
const std::wstring& getReason() const
|
||||||
|
{ return reason; }
|
||||||
|
|
||||||
|
/// Get host name being looked up.
|
||||||
|
const std::string& getHost() const
|
||||||
|
{ return host; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/Exception.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Exception::Exception(const std::wstring& errorDesc)
|
||||||
|
: errorDesc(errorDesc), whatBuffer(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Exception::~Exception() throw()
|
||||||
|
{
|
||||||
|
free(whatBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Exception& Exception::chain(const std::wstring& chainDesc)
|
||||||
|
{
|
||||||
|
errorDesc.append(L"\n From: ");
|
||||||
|
errorDesc.append(chainDesc);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const char* Exception::what() const throw()
|
||||||
|
{
|
||||||
|
free(whatBuffer);
|
||||||
|
whatBuffer = strdup(toUtf8().c_str());
|
||||||
|
return whatBuffer ?: "Couldn't allocate memory for exception conversion.";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string Exception::toUtf8() const
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return ucs4ToUtf8(errorDesc);
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
return "Couldn't encode exception text into UTF-8.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wostream& operator<<(std::wostream& ostr, const Exception& e)
|
||||||
|
{
|
||||||
|
ostr << e.toString();
|
||||||
|
return ostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/Exception.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief LW Support exception base class.
|
||||||
|
|
||||||
|
This is the base class for all LW Support exceptions. It is derived
|
||||||
|
from std::exception, allowing you to treat it as a standard library
|
||||||
|
exception, if that is desired. It provides an implementation of the
|
||||||
|
what() function that returns a UTF-8 representation of the error; the
|
||||||
|
error can also be accessed through the toString() and toUtf8()
|
||||||
|
methods.
|
||||||
|
|
||||||
|
Exceptions chains may be built; these simply track the progress of the
|
||||||
|
exception through the library. An example of such a chain is:
|
||||||
|
|
||||||
|
<pre>void a()
|
||||||
|
{
|
||||||
|
throw Exception("errorMessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
void b()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
a();
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain("b()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
b();
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
cout << e.what() << endl;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
This would give the following output:
|
||||||
|
|
||||||
|
<pre>errorMessage
|
||||||
|
From: b()</pre>
|
||||||
|
|
||||||
|
It is guaranteed that no exceptions will be thrown from this class,
|
||||||
|
unless the system is totally out of memory and cannot allocate std::string
|
||||||
|
objects.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Exception : public std::exception {
|
||||||
|
private:
|
||||||
|
// description of the error itself
|
||||||
|
std::wstring errorDesc;
|
||||||
|
|
||||||
|
// buffer for what()
|
||||||
|
mutable char* whatBuffer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Construct from error message.
|
||||||
|
|
||||||
|
\param errorDesc A UCS-4 description of the error.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Exception(const std::wstring& errorDesc);
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~Exception() throw();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Add a link in the error chain.
|
||||||
|
|
||||||
|
\param chainDesc A UCS-4 description of the chain.
|
||||||
|
\returns a reference to the exception, so you can chain again.
|
||||||
|
|
||||||
|
This allows you to add another link into the exception chain, after
|
||||||
|
the current exception. This is intended to be used when you want to
|
||||||
|
propagate a lower-level error to a higher level, but want to mark
|
||||||
|
where the exception has been on its way through.
|
||||||
|
|
||||||
|
\note It is good practice to give a function name here.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Exception& chain(const std::wstring& chainDesc);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Return UTF-8 error report.
|
||||||
|
virtual const char* what() const throw();
|
||||||
|
|
||||||
|
/// Return UTF-8 error report.
|
||||||
|
std::string toUtf8() const;
|
||||||
|
|
||||||
|
/// Return UCS-4 error report.
|
||||||
|
const std::wstring& toString() const
|
||||||
|
{ return errorDesc; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief LW Support program exception base class.
|
||||||
|
|
||||||
|
Program exceptions are exceptions that are expected to occur, and do
|
||||||
|
not necessarily indicate system failure. They do signal that an
|
||||||
|
exceptional condition has arisen, but this is something that the program
|
||||||
|
should foresee and cope with. This sort of exception probably won't lead
|
||||||
|
directly to user intervention, so no description is required.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class ProgramException : public Exception {
|
||||||
|
public:
|
||||||
|
ProgramException()
|
||||||
|
: Exception(L"Program exception. Should not be seen by users!")
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Numeric overflow class.
|
||||||
|
class NumericOverflow : public Exception {
|
||||||
|
public:
|
||||||
|
NumericOverflow()
|
||||||
|
: Exception(L"Numeric overflow.")
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream insertion operator.
|
||||||
|
std::wostream& operator<<(std::wostream&, const lw::Exception&);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/FIFO.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "lw-support"
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring FIFOOutOfMemory::getDesc(size_t amt)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << L"Ran out of system memory allocating "
|
||||||
|
<< amt << L" bytes for FIFO.";
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/FIFO.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// FIFO exception base class.
|
||||||
|
class FIFOException : public Exception {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
FIFOException(const std::wstring& desc)
|
||||||
|
: Exception(desc)
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// FIFO memory limit exceeded.
|
||||||
|
class FIFOOverflow : public FIFOException {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
FIFOOverflow()
|
||||||
|
: FIFOException(L"FIFO buffer overflowed (too much data).")
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Ran out of system memory while enlarging FIFO.
|
||||||
|
class FIFOOutOfMemory : public FIFOException {
|
||||||
|
private:
|
||||||
|
static std::wstring getDesc(size_t amt);
|
||||||
|
size_t amt;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
FIFOOutOfMemory(size_t amt)
|
||||||
|
: FIFOException(getDesc(amt)), amt(amt)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Get amount allocated, in bytes.
|
||||||
|
size_t getAmount() const
|
||||||
|
{ return amt; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Not enough data in FIFO.
|
||||||
|
class FIFOUnderflow : public FIFOException {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
FIFOUnderflow()
|
||||||
|
: FIFOException(L"FIFO buffer underflowed (not enough data).")
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/HTTP.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring HTTPMalformed::format(const std::wstring& msg)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
o << L"Error parsing HTTP header fields: " << msg << L".";
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HTTPMalformed::HTTPMalformed(const std::wstring& msg)
|
||||||
|
: Exception(format(msg))
|
||||||
|
{
|
||||||
|
chain(L"while parsing HTTP header fields");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/HTTP.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Thrown when HTTP is malformed.
|
||||||
|
class HTTPMalformed : public Exception {
|
||||||
|
private:
|
||||||
|
static std::wstring format(const std::wstring& msg);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
HTTPMalformed(const std::wstring& msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/IO.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring IOModeError::getModeDesc(IOMode mode)
|
||||||
|
{
|
||||||
|
switch(mode) {
|
||||||
|
case IOClosed:
|
||||||
|
return L"closed";
|
||||||
|
case IONone:
|
||||||
|
return L"not opened for reading or writing";
|
||||||
|
case IORead:
|
||||||
|
return L"opened for reading";
|
||||||
|
case IOWrite:
|
||||||
|
return L"opened for writing";
|
||||||
|
case IOReadWrite:
|
||||||
|
return L"opened for reading and writing";
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldn't reach here
|
||||||
|
return L"Bug in IOModeError::getModeDesc()";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring IOModeError::getDesc(IOMode currentMode, IOMode requiredMode,
|
||||||
|
const std::wstring& op)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << L"Could not perform operation on device in its current mode.\n"
|
||||||
|
L" Current mode : " << getModeDesc(currentMode) << L"\n"
|
||||||
|
L" Required mode: " << getModeDesc(requiredMode) << L"\n"
|
||||||
|
L" Operation : " << op;
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IOModeError::IOModeError(IOInterface* ioInterface, const std::wstring& op,
|
||||||
|
IOMode requiredMode)
|
||||||
|
: IOException(getDesc(ioInterface->getIOMode(), requiredMode, op),
|
||||||
|
ioInterface),
|
||||||
|
op(op), currentMode(ioInterface->getIOMode()),
|
||||||
|
requiredMode(requiredMode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring IOTimeout::getDesc(const std::wstring& op, int timeout,
|
||||||
|
IOTimeoutMode timeoutMode, size_t amountTransferred)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << L"I/O operation timed out.\n"
|
||||||
|
L" Operation : " << op << L"\n"
|
||||||
|
L" Timeout : " << timeout;
|
||||||
|
if(timeout < 0) o << L" (never -- so why are we here?)";
|
||||||
|
else if(!timeout) o << L" (immediately)";
|
||||||
|
else o << "ms";
|
||||||
|
|
||||||
|
o << L"\n Timeout mode: ";
|
||||||
|
switch(timeoutMode) {
|
||||||
|
case IOTimeoutHardPartial:
|
||||||
|
o << L"hard timeout, partial transfer OK";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IOTimeoutHardFull:
|
||||||
|
o << L"hard timeout, full transfer only";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IOTimeoutSoftPartial:
|
||||||
|
o << L"soft timeout, partial transfer OK";
|
||||||
|
|
||||||
|
case IOTimeoutSoftFull:
|
||||||
|
o << L"soft timeout, full transfer only";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IOTimeoutOneShot:
|
||||||
|
o << L"one-shot";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
o << L"\n Bytes transferred before timeout: " << amountTransferred;
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IOTimeout::IOTimeout(IOInterface* ioInterface, const std::wstring& op,
|
||||||
|
int timeout, IOTimeoutMode timeoutMode, size_t amountTransferred)
|
||||||
|
: IOException(getDesc(op, timeout, timeoutMode, amountTransferred),
|
||||||
|
ioInterface),
|
||||||
|
op(op), timeout(timeout), timeoutMode(timeoutMode),
|
||||||
|
amountTransferred(amountTransferred)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring FileNotFound::getDesc(const char* fileName)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << L"File not found.\n"
|
||||||
|
L" Filename: ";
|
||||||
|
if(fileName) o << utf8ToUcs4(fileName, true);
|
||||||
|
else o << L"(null)";
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FileNotFound::FileNotFound(const char* fileName)
|
||||||
|
: Exception(getDesc(fileName)), fileName(fileName)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FileNotFound::~FileNotFound() throw()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/IO.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Base class for all I/O device exceptions.
|
||||||
|
class IOException : public Exception {
|
||||||
|
private:
|
||||||
|
IOInterface* ioInterface;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
IOException(const std::wstring& desc, IOInterface* ioInterface)
|
||||||
|
: Exception(desc),
|
||||||
|
ioInterface(ioInterface)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~IOException() throw()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Get the underlying device.
|
||||||
|
IOInterface* getIOInterface() const
|
||||||
|
{ return ioInterface; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Thrown when trying to read past end-of-file.
|
||||||
|
class EndOfFile : public IOException {
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
EndOfFile(IOInterface* ioInterface)
|
||||||
|
: IOException(L"End of file reached.", ioInterface)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~EndOfFile() throw()
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Operation not permitted on device in current mode.
|
||||||
|
|
||||||
|
This exception is thrown whenever you try to carry out an operation on a
|
||||||
|
device, but the device isn't opened in the correct mode (e.g. if you try
|
||||||
|
to read from a write-only device, or if you try to write to an unopened
|
||||||
|
device).
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IOModeError : public IOException {
|
||||||
|
private:
|
||||||
|
const std::wstring op;
|
||||||
|
const IOMode currentMode, requiredMode;
|
||||||
|
|
||||||
|
static std::wstring getModeDesc(IOMode mode);
|
||||||
|
static std::wstring getDesc(IOMode currentMode, IOMode requiredMode,
|
||||||
|
const std::wstring& op);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param ioInterface The interface which was the source of the error.
|
||||||
|
\param requiredMode What the device needed to be opened in for the
|
||||||
|
operation to be legal.
|
||||||
|
\param op A quick description of the operation (probably the
|
||||||
|
function name).
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOModeError(IOInterface* ioInterface, const std::wstring& op,
|
||||||
|
IOMode requiredMode);
|
||||||
|
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~IOModeError() throw()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Returns the name of the operation that was attempted.
|
||||||
|
const std::wstring& getOp() const
|
||||||
|
{ return op; }
|
||||||
|
|
||||||
|
/// Returns the device's current mode.
|
||||||
|
IOMode getCurrentMode() const
|
||||||
|
{ return currentMode; }
|
||||||
|
|
||||||
|
/// Returns the mode required for the operation.
|
||||||
|
IOMode getRequiredMode() const
|
||||||
|
{ return requiredMode; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief I/O timed out.
|
||||||
|
|
||||||
|
This exception is thrown whenever an I/O operation times out.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IOTimeout : public IOException {
|
||||||
|
private:
|
||||||
|
const std::wstring op;
|
||||||
|
const int timeout;
|
||||||
|
const IOTimeoutMode timeoutMode;
|
||||||
|
const size_t amountTransferred;
|
||||||
|
|
||||||
|
static std::wstring getDesc(const std::wstring& op, int timeout,
|
||||||
|
IOTimeoutMode timeoutMode, size_t amountTransferred);
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param ioInterface The device that timed out.
|
||||||
|
\param op Name of the operation that was being performed (probably
|
||||||
|
the function name).
|
||||||
|
\param timeout The timeout value, in milliseconds.
|
||||||
|
\param timeoutMode The timeout mode.
|
||||||
|
\param amountTransferred The amount actually transferred before
|
||||||
|
timing out.
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOTimeout(IOInterface* ioInterface, const std::wstring& op, int timeout,
|
||||||
|
IOTimeoutMode timeoutMode, size_t amountTransferred);
|
||||||
|
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~IOTimeout() throw()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Returns the name of the operation that was attempted.
|
||||||
|
const std::wstring& getOp() const
|
||||||
|
{ return op; }
|
||||||
|
|
||||||
|
/// Returns the timeout value.
|
||||||
|
int getTimeout() const
|
||||||
|
{ return timeout; }
|
||||||
|
|
||||||
|
/// Returns the timeout mode.
|
||||||
|
IOTimeoutMode getTimeoutMode() const
|
||||||
|
{ return timeoutMode; }
|
||||||
|
|
||||||
|
/// Returns the amount of data actually transferred before timeout.
|
||||||
|
size_t getAmountTransferred() const
|
||||||
|
{ return amountTransferred; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// File not found exception.
|
||||||
|
class FileNotFound : public Exception {
|
||||||
|
private:
|
||||||
|
static std::wstring getDesc(const char* fileName);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
FileNotFound(const char* fileName);
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
~FileNotFound() throw();
|
||||||
|
|
||||||
|
/// The filename.
|
||||||
|
const std::string fileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/NullPointer.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Null pointer exception.
|
||||||
|
|
||||||
|
This exception is used whenever a function that is expecting a valid pointer is passed a null
|
||||||
|
pointer.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NullPointer : public Exception {
|
||||||
|
public:
|
||||||
|
/// Construct from pointer name.
|
||||||
|
NullPointer(const std::wstring& pointerName)
|
||||||
|
: lw::Exception(L"Null pointer exception: " + pointerName), pointerName(pointerName)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor (does nothing).
|
||||||
|
virtual ~NullPointer() throw()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Name of the pointer causing the problem.
|
||||||
|
const std::wstring pointerName;
|
||||||
|
|
||||||
|
/*! \brief Check a pointer.
|
||||||
|
|
||||||
|
\param ptr The pointer to check.
|
||||||
|
\param name Descriptive name of the pointer.
|
||||||
|
\param func Name of the function, passed to chain(). May be omitted.
|
||||||
|
|
||||||
|
If \a ptr is a null (zero) pointer, this function will throw a NullPointer exception.
|
||||||
|
Otherwise, it does nothing.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline static void check(const void* ptr, const wchar_t* name, const wchar_t* func = 0)
|
||||||
|
{
|
||||||
|
if(!ptr) {
|
||||||
|
if(func) throw NullPointer(name).chain(func);
|
||||||
|
throw NullPointer(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/ParseError.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring ParseError::constructDesc(const std::wstring& reason,
|
||||||
|
const std::wstring& src, std::wstring::size_type errorPos)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
std::wstring::size_type len = src.length();
|
||||||
|
|
||||||
|
if(!len) {
|
||||||
|
o << "Error while parsing input.\n"
|
||||||
|
" Reason: " << reason << "\n"
|
||||||
|
" (std::string is empty)";
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring::size_type start = std::max(errorPos, std::wstring::size_type(32)) - 32,
|
||||||
|
end = std::min(errorPos + 32, len);
|
||||||
|
|
||||||
|
o << "Error while parsing input.\n"
|
||||||
|
" Reason : " << reason << "\n"
|
||||||
|
" Char offset : " << errorPos << "\n"
|
||||||
|
" Nearby : " << src.substr(start, end) << "\n"
|
||||||
|
" Error at : " << std::wstring(errorPos - start, L' ') << '^';
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ParseError::ParseError(const std::wstring& reason,
|
||||||
|
const std::wstring& src, std::wstring::size_type errorPos)
|
||||||
|
: Exception(constructDesc(reason, src, errorPos)),
|
||||||
|
reason(reason), src(src), errorPos(errorPos)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/ParseError.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Exception thrown when parsing a std::string fails.
|
||||||
|
|
||||||
|
This exception should be thrown when the parsing of a std::string fails. It
|
||||||
|
is intended for std::strings which are "displayable", since the exception
|
||||||
|
message will contain a copy of (part of) the std::string being parsed. The
|
||||||
|
exception message is intended for output onto a fixed-width font
|
||||||
|
display.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class ParseError : public Exception {
|
||||||
|
private:
|
||||||
|
std::wstring reason, src;
|
||||||
|
std::wstring::size_type errorPos;
|
||||||
|
|
||||||
|
static std::wstring constructDesc(const std::wstring& reason,
|
||||||
|
const std::wstring& src, std::wstring::size_type errorPos);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param reason A description of why the parsing error occured.
|
||||||
|
\param src The source std::string.
|
||||||
|
\param errorPos The character offset at which the error occured.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ParseError(const std::wstring& reason,
|
||||||
|
const std::wstring& src, std::wstring::size_type errorPos);
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~ParseError() throw()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Gets the reason for the parsing error.
|
||||||
|
const std::wstring& getReason() const
|
||||||
|
{ return reason; }
|
||||||
|
|
||||||
|
/// Gets the source std::string.
|
||||||
|
const std::wstring& getSource() const
|
||||||
|
{ return src; }
|
||||||
|
|
||||||
|
/// Gets the position (in characters) at which the error occured.
|
||||||
|
std::wstring::size_type getErrorPos() const
|
||||||
|
{ return errorPos; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/SystemError.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring SystemError::errnoToString(int errnum)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
o << L"System error.\n"
|
||||||
|
L" Error number: " << errnum << L"\n"
|
||||||
|
L" Description : ";
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// this is needed if we're using the non-GNU version of strerror_r()
|
||||||
|
// specified by POSIX, with signature:
|
||||||
|
// int strerror_r(int, char*, int)
|
||||||
|
//
|
||||||
|
// the GNU version may go away one day, according to the manpage.
|
||||||
|
int bufSize = 500;
|
||||||
|
char* buf = 0;
|
||||||
|
bool loop = true;
|
||||||
|
while(loop) {
|
||||||
|
delete [] buf;
|
||||||
|
bufSize <<= 1;
|
||||||
|
buf = new char[bufSize];
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
if(strerror_r(errnum, buf, bufSize)) {
|
||||||
|
switch(errno) {
|
||||||
|
case ERANGE:
|
||||||
|
break; // go through again
|
||||||
|
case EINVAL:
|
||||||
|
o << L"(unknown -- invalid error code)";
|
||||||
|
loop = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
o << L"(unknown -- error " << errno
|
||||||
|
<< L" during strerror_r conversion)";
|
||||||
|
loop = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// conversion was OK
|
||||||
|
try {
|
||||||
|
o << utf8ToUcs4(buf);
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
delete [] buf;
|
||||||
|
o << "(invalid UTF-8 std::string)";
|
||||||
|
}
|
||||||
|
loop = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete [] buf;
|
||||||
|
#else
|
||||||
|
// this is needed for the GNU version of strerror_r(), with
|
||||||
|
// signature:
|
||||||
|
// char* strerror_r(int, char*, int);
|
||||||
|
//
|
||||||
|
// see implementation at glibc/sysdeps/generic/_strerror.c
|
||||||
|
char buf[30];
|
||||||
|
o << utf8ToUcs4(strerror_r(errnum, buf, sizeof(buf)), true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SystemError::SystemError()
|
||||||
|
: Exception(errnoToString(errno))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SystemError::SystemError(int errnum)
|
||||||
|
: Exception(errnoToString(errnum))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* lw-support/src/lib/Exceptions/SystemError.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief System call error.
|
||||||
|
|
||||||
|
This exception is used whenever a system call results in an error that
|
||||||
|
cannot be handled by LW Support (for instance, if an I/O device fails to
|
||||||
|
open). Many of the important cases will be handled with their own,
|
||||||
|
specific exception classes; those that aren't will be handled with this
|
||||||
|
class.
|
||||||
|
|
||||||
|
This class is intended as a direct replacement for the global variable
|
||||||
|
\a errno.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class SystemError : public Exception {
|
||||||
|
private:
|
||||||
|
static std::wstring errnoToString(int errnum);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Construct from errno.
|
||||||
|
|
||||||
|
This constructor will examine the current value of \a errno and
|
||||||
|
build the exception from there. The exception is always built, even
|
||||||
|
if \a errno is 0.
|
||||||
|
|
||||||
|
*/
|
||||||
|
SystemError();
|
||||||
|
|
||||||
|
/*! \brief Construct from error number.
|
||||||
|
|
||||||
|
\param errnum The error number.
|
||||||
|
|
||||||
|
This constructor doesn't use the global \a errno variable, but
|
||||||
|
instead uses the user-supplied value. The exception is always built,
|
||||||
|
even if \a errnum is 0.
|
||||||
|
|
||||||
|
*/
|
||||||
|
SystemError(int errnum);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/* lw-support/src/lib/ForwardDeclare.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file simply contains forward declarations of all LW Support
|
||||||
|
// classes, to facilitate header ordering, etc.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// This namespace holds the LW Support core classes.
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BadDate;
|
||||||
|
class BadSourceChar;
|
||||||
|
class BadString;
|
||||||
|
class BadTime;
|
||||||
|
class BadTimeOverflow;
|
||||||
|
class BadTimeValue;
|
||||||
|
class CannotConvertChar;
|
||||||
|
class CompletionCallback;
|
||||||
|
class CompletionData;
|
||||||
|
class CompletionList;
|
||||||
|
class CompletionNotifier;
|
||||||
|
class CompletionTask;
|
||||||
|
class Date;
|
||||||
|
class DateTime;
|
||||||
|
class DayTime;
|
||||||
|
class DnsError;
|
||||||
|
class ElapsedTime;
|
||||||
|
class EndOfFile;
|
||||||
|
class EndPipe;
|
||||||
|
class EventCallbackIO;
|
||||||
|
class EventCallbackNetServer;
|
||||||
|
class EventCallbackTimer;
|
||||||
|
class EventManager;
|
||||||
|
class Exception;
|
||||||
|
class FIFO;
|
||||||
|
class FIFOException;
|
||||||
|
class FIFOOutOfMemory;
|
||||||
|
class FIFOOverflow;
|
||||||
|
class FIFOUnderflow;
|
||||||
|
class FileNotFound;
|
||||||
|
class HTTPHeaderParser;
|
||||||
|
class HTTPMalformed;
|
||||||
|
class HTTPRequestParser;
|
||||||
|
class HTTPResponseParser;
|
||||||
|
class IODevice;
|
||||||
|
class IOException;
|
||||||
|
class IOFilter;
|
||||||
|
class IOInterface;
|
||||||
|
class IOModeError;
|
||||||
|
class IOPosixDevice;
|
||||||
|
class IOSerialPort;
|
||||||
|
class IOTimeout;
|
||||||
|
class IOWriteTask;
|
||||||
|
class Key;
|
||||||
|
class Log;
|
||||||
|
class LoggingSMTPClient;
|
||||||
|
class Mutex;
|
||||||
|
class MutexHolder;
|
||||||
|
class MutexPadlock;
|
||||||
|
class MutexPadlockRecursive;
|
||||||
|
class NetAddress;
|
||||||
|
class NetAddressIPv4;
|
||||||
|
class NetAddressIPv6;
|
||||||
|
class NetClient;
|
||||||
|
class NetClientTCP;
|
||||||
|
class NetServer;
|
||||||
|
class NetServerCallback;
|
||||||
|
class NetServerTCP;
|
||||||
|
class NullPointer;
|
||||||
|
class NumericOverflow;
|
||||||
|
class ParseError;
|
||||||
|
class Pipe;
|
||||||
|
class ProgramException;
|
||||||
|
class SMTPClient;
|
||||||
|
class Semaphore;
|
||||||
|
class StopWatch;
|
||||||
|
class SystemError;
|
||||||
|
class TaskAlreadyCompleted;
|
||||||
|
class Thread;
|
||||||
|
class Uncopyable;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* lw-support/src/lib/IO/Device.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Base I/O device class.
|
||||||
|
|
||||||
|
This class is the base of the I/O device hierarchy. It specifies the
|
||||||
|
common functionality, such as closing, reading and writing. It provides
|
||||||
|
for both blocking and non-blocking I/O (which means that an 'open'
|
||||||
|
function would have to open its file descriptor in non-blocking mode).
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IODevice : public IOInterface {
|
||||||
|
protected:
|
||||||
|
friend class EventManager;
|
||||||
|
|
||||||
|
/// Returns a file descriptor suitable for EventManager.
|
||||||
|
virtual int getListenFd() const = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor. Does nothing.
|
||||||
|
IODevice()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor. Calls close().
|
||||||
|
virtual ~IODevice()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Closes the device.
|
||||||
|
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
This call will close a device.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
// implemented virtuals
|
||||||
|
virtual IOMode getIOMode() const
|
||||||
|
{ return ioMode; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* lw-support/src/lib/IO/Filter.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IOFilter::IOFilter(IOInterface* ioDev)
|
||||||
|
: ioDev(ioDev)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOFilter::sync()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
flush();
|
||||||
|
ioDev->sync();
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOFilter::sync()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* lw-support/src/lib/IO/Filter.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief I/O filter base class.
|
||||||
|
|
||||||
|
The I/O filter class sits between the program and an underlying device
|
||||||
|
(or another filter, since it is possible to create arbitrary chains of
|
||||||
|
such filters). It can perform various transformations on the data. One
|
||||||
|
example is a gzip filter, which compresses data before writing it and
|
||||||
|
decompresses it when reading.
|
||||||
|
|
||||||
|
\warning Closing and reopening the underlying device may leave data in
|
||||||
|
the filter's buffers. The solution is to call the reset()
|
||||||
|
method (or delete the filter and create a new one).
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IOFilter : public IOInterface {
|
||||||
|
protected:
|
||||||
|
/// The source device.
|
||||||
|
IOInterface* ioDev;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor. Takes pointer to underlying device.
|
||||||
|
IOFilter(IOInterface* ioDev);
|
||||||
|
|
||||||
|
/// Destructor. Flushes write cache.
|
||||||
|
virtual ~IOFilter()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Empty the filter's write buffers.
|
||||||
|
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOModeError if the file isn't open for writing.
|
||||||
|
|
||||||
|
This causes the filter to flush its write buffers to the underlying
|
||||||
|
device. If the underlying device has its own write buffer, this
|
||||||
|
function does not cause the underlying buffer to be flushed; that
|
||||||
|
has to be done explicitly.
|
||||||
|
|
||||||
|
\sa sync()
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void flush() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Reset the filter.
|
||||||
|
|
||||||
|
This should only be called if you want to clear all buffer data and
|
||||||
|
state information in the filter (e.g. if you have closed and
|
||||||
|
reopened the underlying device). It's important to note that calling
|
||||||
|
this will simply discard any data waiting to be read/written, so you
|
||||||
|
should call flush() first.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void reset() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// implemented virtuals
|
||||||
|
virtual IOMode getIOMode() const
|
||||||
|
{ return ioDev->getIOMode(); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Ensure data is written to disk.
|
||||||
|
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOModeError if the file isn't open for writing.
|
||||||
|
|
||||||
|
This function will flush any output buffers to the underlying
|
||||||
|
device, and will then propagate down the chain, until it reaches
|
||||||
|
the eventual sink device (e.g. file or network connection), at which
|
||||||
|
point it will call fdatasync() to ensure the data is physically
|
||||||
|
written to disk/network/whatever.
|
||||||
|
|
||||||
|
\sa flush()
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void sync();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* lw-support/src/lib/IO/Interface.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief I/O interface.
|
||||||
|
|
||||||
|
This class is the interface that any I/O device or filter will
|
||||||
|
implement. It has both blocking and non-blocking functions.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IOInterface {
|
||||||
|
protected:
|
||||||
|
/// The read/write mode of the device.
|
||||||
|
IOMode ioMode;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor. Initialises ioMode.
|
||||||
|
IOInterface()
|
||||||
|
: ioMode(IOClosed)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Destructor -- does nothing.
|
||||||
|
virtual ~IOInterface() { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Gets the mode the device was opened in.
|
||||||
|
virtual IOMode getIOMode() const = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Read some data (non-blocking).
|
||||||
|
|
||||||
|
\param buf Buffer to read data into.
|
||||||
|
\param amt Maximum amount of data to read (i.e. size of buffer).
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws EndOfFile if end-of-file has been reached.
|
||||||
|
\throws IOModeError if the file isn't open for reading.
|
||||||
|
\returns Number of bytes actually read (which will be zero if no
|
||||||
|
data is currently available).
|
||||||
|
|
||||||
|
Reads some data in a non-blocking manner. This call will return
|
||||||
|
immediately and report the number of bytes read (which may well be
|
||||||
|
less than the number of bytes requested). It will return zero if no
|
||||||
|
data is available.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual size_t read(char* buf, size_t amt) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Write some data (non-blocking).
|
||||||
|
|
||||||
|
\param buf Data to write.
|
||||||
|
\param amt Maximum amount of data to write.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOModeError if the file isn't open for writing.
|
||||||
|
\returns Number of bytes actually written (will be zero if the
|
||||||
|
device's output buffer is currently full).
|
||||||
|
|
||||||
|
Writes some data in a non-blocking manner. This call will return
|
||||||
|
immediately and report the number of bytes written (which may well
|
||||||
|
be less than the number of bytes requested). It will return zero if
|
||||||
|
the device wasn't ready for writing.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual size_t write(const char* buf, size_t amt) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Wait for I/O.
|
||||||
|
|
||||||
|
\param read If \a true, returns when the device has input available.
|
||||||
|
\param write If \a true, returns when you can output to the device.
|
||||||
|
\param timeout Timeout period, in milliseconds. 0 means immediate;
|
||||||
|
negative means forever.
|
||||||
|
\throws IOTimeout if a timeout occurs.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\retval true if an exceptional condition occurred.
|
||||||
|
\retval false if no exception condition occurred.
|
||||||
|
|
||||||
|
This function waits for the underlying device to become ready for
|
||||||
|
the specified I/O operations, and returns when it is. It will
|
||||||
|
return immediately if there is some exceptional condition, you can
|
||||||
|
check for this by looking at the return value, which will be
|
||||||
|
\a true if there is such a condition. Exceptional conditions
|
||||||
|
include things such as out-of-band data and asynchronous errors.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual bool wait(bool read, bool write, int timeout) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Read some data (blocking) (full buffer).
|
||||||
|
|
||||||
|
\param buf Buffer to read data into.
|
||||||
|
\param amt Maximum amount of data to read (i.e. size of buffer).
|
||||||
|
\param timeout Timeout period, in milliseconds. 0 means immediate;
|
||||||
|
negative means forever.
|
||||||
|
\param timeoutMode The timeout mode.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOModeError if the file isn't open for reading.
|
||||||
|
\throws EndOfFile if end-of-file has been reached.
|
||||||
|
\throws IOTimeout if a timeout occurs.
|
||||||
|
\returns Number of bytes actually read (always more than zero).
|
||||||
|
|
||||||
|
Reads some data, blocking if none is available. Various timeout
|
||||||
|
options are available. By default, the entire buffer must be filled
|
||||||
|
in the specified time period (but this is set to "forever" by
|
||||||
|
default). Should the end of file be reached before the buffer is
|
||||||
|
completely filled, an EndOfFile exception will be thrown; instead,
|
||||||
|
you could use wait() and read() individually to get around this.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual size_t readBlock(char* buf, size_t amt, int timeout = -1,
|
||||||
|
IOTimeoutMode timeoutMode = IOTimeoutHardFull) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Write some data (blocking).
|
||||||
|
|
||||||
|
\param buf Data to write.
|
||||||
|
\param amt Amount of data to write.
|
||||||
|
\param timeout Timeout period, in milliseconds. 0 means immediate;
|
||||||
|
negative means forever.
|
||||||
|
\param timeoutMode The timeout mode.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOModeError if the file isn't open for writing.
|
||||||
|
\throws IOTimeout if a timeout occurs.
|
||||||
|
\returns Number of bytes actually written (always more than zero).
|
||||||
|
|
||||||
|
Writes some data, blocking until it is sent. Various timeout
|
||||||
|
options are available. By default, the entire buffer must be sent
|
||||||
|
in the specified time period (but this is set to "forever" by
|
||||||
|
default).
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual size_t writeBlock(const char* buf, size_t amt, int timeout
|
||||||
|
= -1, IOTimeoutMode timeoutMode = IOTimeoutHardFull) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Ensure data is written to disk.
|
||||||
|
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOModeError if the file isn't open for writing.
|
||||||
|
|
||||||
|
This function will flush any output buffers, then call fdatasync()
|
||||||
|
on the underlying device, ensuring the data is written to disk (or
|
||||||
|
network, etc.).
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void sync() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Check for asynchronous errors.
|
||||||
|
|
||||||
|
\throws SystemError if there are any asynchronous errors.
|
||||||
|
|
||||||
|
This function should be called to check for asynchronous errors.
|
||||||
|
Examples of where you would use it are after a non-blocking
|
||||||
|
connection has completed (socket will be marked as ready for
|
||||||
|
reading/writing, so you need to check what's going on), and if
|
||||||
|
you receive an error signal while waiting for events on the
|
||||||
|
device, etc. Implementations of this function may do nothing if
|
||||||
|
asynchronous errors can't occur (e.g. on standard disk files).
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void checkErrors() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/* lw-support/src/lib/IO/Pipe.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Pipe::Pipe()
|
||||||
|
: read(0), write(0)
|
||||||
|
{
|
||||||
|
int fds[2];
|
||||||
|
if(TEMP_FAILURE_RETRY(::pipe(fds)))
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"Pipe::Pipe()").chain(L"pipe()");
|
||||||
|
try {
|
||||||
|
read = new EndPipe(this, fds[0], true);
|
||||||
|
read->setNonBlocking();
|
||||||
|
write = new EndPipe(this, fds[1], false);
|
||||||
|
write->setNonBlocking();
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
delete read;
|
||||||
|
delete write;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Pipe::~Pipe()
|
||||||
|
{
|
||||||
|
delete read;
|
||||||
|
delete write;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EndPipe::EndPipe(Pipe* myPipe, int fd, bool read)
|
||||||
|
: myPipe(myPipe)
|
||||||
|
{
|
||||||
|
this->fd = fd;
|
||||||
|
this->ioMode = read ? IORead : IOWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EndPipe::~EndPipe()
|
||||||
|
{
|
||||||
|
// unregister with the containing Pipe object
|
||||||
|
if(ioMode == IORead) myPipe->read = 0;
|
||||||
|
else myPipe->write = 0;
|
||||||
|
|
||||||
|
// closing happens automatically in IOPosixDevice's destructor.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* lw-support/src/lib/IO/Pipe.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Pipe class container.
|
||||||
|
|
||||||
|
This class, when instantiated, creates a POSIX pipe device. This device
|
||||||
|
has two ends (of type EndPipe); you can write to one of these ends, and
|
||||||
|
read from the other.
|
||||||
|
|
||||||
|
This class just acts as a container for the two ends of the pipe.
|
||||||
|
Deleting it will delete the two end pipes.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Pipe : public Uncopyable {
|
||||||
|
private:
|
||||||
|
friend class EndPipe;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\throws lw::SystemError if an error occurs while creating the pipes.
|
||||||
|
|
||||||
|
The constructor sets up both ends of the pipe.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Pipe();
|
||||||
|
|
||||||
|
/// Destructor. Closes both ends of the pipe.
|
||||||
|
~Pipe();
|
||||||
|
|
||||||
|
/// Reading end of the pipe.
|
||||||
|
EndPipe* read;
|
||||||
|
|
||||||
|
/// Writing end of the pipe.
|
||||||
|
EndPipe* write;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief One end of a pipe connection.
|
||||||
|
|
||||||
|
See the lw::Pipe class for a better description of POSIX pipes. This
|
||||||
|
object has a private constructor, since it can only be created as part
|
||||||
|
of an lw::Pipe container object.
|
||||||
|
|
||||||
|
The destructor will automatically unregister the object with its
|
||||||
|
containing lw::Pipe, in order to avoid double deletion when the Pipe
|
||||||
|
instance is deleted.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class EndPipe : public IOPosixDevice {
|
||||||
|
private:
|
||||||
|
friend class Pipe;
|
||||||
|
Pipe* myPipe;
|
||||||
|
EndPipe(Pipe* myPipe, int fd, bool read);
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~EndPipe();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,379 @@
|
||||||
|
/* lw-support/src/lib/IO/PosixDevice.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// This class is a helper class used to keep track of timeouts and
|
||||||
|
// implement timeout behaviour for an I/O device.
|
||||||
|
class IOCountdown {
|
||||||
|
private:
|
||||||
|
StopWatch started;
|
||||||
|
IOInterface* ioInterface;
|
||||||
|
std::wstring op;
|
||||||
|
int timeout;
|
||||||
|
IOTimeoutMode timeoutMode;
|
||||||
|
size_t totalTransferred;
|
||||||
|
|
||||||
|
public:
|
||||||
|
IOCountdown(IOInterface* ioInterface, const std::wstring& op, int timeout,
|
||||||
|
IOTimeoutMode timeoutMode)
|
||||||
|
: ioInterface(ioInterface), op(op), timeout(timeout),
|
||||||
|
timeoutMode(timeoutMode), totalTransferred(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
// Call this when some data is transferred.
|
||||||
|
void transferred(size_t amt)
|
||||||
|
{
|
||||||
|
// record amount transferred
|
||||||
|
totalTransferred += amt;
|
||||||
|
|
||||||
|
// reset countdown timer
|
||||||
|
if(amt && (timeoutMode == IOTimeoutSoftFull ||
|
||||||
|
timeoutMode == IOTimeoutSoftPartial))
|
||||||
|
{
|
||||||
|
started.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this when poll() returns a timeout. Returns no. of bytes
|
||||||
|
// transferred, but throws an exception if specified by behaviour.
|
||||||
|
size_t timedOut()
|
||||||
|
{
|
||||||
|
switch(timeoutMode) {
|
||||||
|
case IOTimeoutHardPartial:
|
||||||
|
case IOTimeoutSoftPartial:
|
||||||
|
case IOTimeoutOneShot:
|
||||||
|
if(totalTransferred) break;
|
||||||
|
// fall through
|
||||||
|
case IOTimeoutHardFull:
|
||||||
|
case IOTimeoutSoftFull:
|
||||||
|
throw IOTimeout(ioInterface, op, timeout,
|
||||||
|
timeoutMode, totalTransferred);
|
||||||
|
}
|
||||||
|
return totalTransferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this when you need to update the timeout variable. It will
|
||||||
|
// return false if the timeout has happened.
|
||||||
|
bool update(int& remaining)
|
||||||
|
{
|
||||||
|
/* Special case for one-shot mode: after initial transfer has
|
||||||
|
completed, we want to continue retrying until the I/O is
|
||||||
|
drained; therefore, we set the timeout to zero.
|
||||||
|
|
||||||
|
FIXME: a stray signal could cause poll() to fail with EINTR,
|
||||||
|
at which point update() will be called event though no I/O
|
||||||
|
has yet occurred, possibly leading to a premature timeout.
|
||||||
|
*/
|
||||||
|
if(timeoutMode == IOTimeoutOneShot) return 0;
|
||||||
|
|
||||||
|
if(timeout < 0) {
|
||||||
|
remaining = timeout;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining = timeout - started.elapsed().to_ms();
|
||||||
|
return (remaining >= 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IOPosixDevice::~IOPosixDevice()
|
||||||
|
{
|
||||||
|
if(fd >= 0) TEMP_FAILURE_RETRY(::close(fd));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::close()
|
||||||
|
{
|
||||||
|
if(fd != -1) {
|
||||||
|
int ret = TEMP_FAILURE_RETRY(::close(fd));
|
||||||
|
fd = -1;
|
||||||
|
ioMode = IOClosed;
|
||||||
|
|
||||||
|
if(ret) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"close()")
|
||||||
|
.chain(L"IOPosixDevice::close()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t IOPosixDevice::read(char* buf, size_t amt)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
checkRead();
|
||||||
|
|
||||||
|
ssize_t ret = TEMP_FAILURE_RETRY(::read(fd, buf, amt));
|
||||||
|
if(!ret) throw EndOfFile(this);
|
||||||
|
if(ret == -1) {
|
||||||
|
if(errno == EAGAIN) return 0;
|
||||||
|
throw SystemError().chain(L"read()");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOPosixDevice::read()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t IOPosixDevice::write(const char* buf, size_t amt)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
checkWrite();
|
||||||
|
|
||||||
|
ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, buf, amt));
|
||||||
|
if(ret == -1) {
|
||||||
|
if(errno == EAGAIN) return 0;
|
||||||
|
throw SystemError().chain(L"write()");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOPosixDevice::write()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool IOPosixDevice::wait(bool read, bool write, int timeout)
|
||||||
|
{
|
||||||
|
struct pollfd ufd = { fd, POLLPRI | (read ? POLLIN : 0) | (write ? POLLOUT : 0), 0 };
|
||||||
|
switch(poll(&ufd, 1, timeout)) {
|
||||||
|
case -1:
|
||||||
|
if(errno == EINTR) return false;
|
||||||
|
throw SystemError().chain(L"IOPosixDevice::wait()").chain(L"poll()");
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
// timeout
|
||||||
|
throw IOTimeout(this, L"wait()", timeout, IOTimeoutHardFull, 0);
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (ufd.revents & (POLLERR | POLLPRI | POLLHUP | POLLNVAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t IOPosixDevice::readBlock(char* buf, size_t amt, int timeout,
|
||||||
|
IOTimeoutMode timeoutMode)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
checkRead();
|
||||||
|
|
||||||
|
size_t remaining = amt;
|
||||||
|
int timeout_remaining = timeout;
|
||||||
|
struct pollfd ufd = { fd, POLLIN, 0 };
|
||||||
|
IOCountdown countdown(this, L"readBlock()", timeout, timeoutMode);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
switch(poll(&ufd, 1, timeout_remaining)) {
|
||||||
|
case -1:
|
||||||
|
if(errno == EINTR) break;
|
||||||
|
throw SystemError().chain(L"poll()");
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
// timeout
|
||||||
|
return countdown.timedOut();
|
||||||
|
|
||||||
|
default:
|
||||||
|
// perform read
|
||||||
|
size_t res = read(buf, remaining);
|
||||||
|
if( !(remaining -= res) ) return amt;
|
||||||
|
buf += res;
|
||||||
|
countdown.transferred(res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update timeout
|
||||||
|
if(!countdown.update(timeout_remaining))
|
||||||
|
return countdown.timedOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOPosixDevice::readBlock()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t IOPosixDevice::writeBlock(const char* buf, size_t amt, int timeout,
|
||||||
|
IOTimeoutMode timeoutMode)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
checkWrite();
|
||||||
|
|
||||||
|
size_t remaining = amt;
|
||||||
|
int timeout_remaining = timeout;
|
||||||
|
struct pollfd ufd = { fd, POLLOUT, 0 };
|
||||||
|
IOCountdown countdown(this, L"writeBlock()", timeout, timeoutMode);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
switch(poll(&ufd, 1, timeout_remaining)) {
|
||||||
|
case -1:
|
||||||
|
if(errno == EINTR) break;
|
||||||
|
throw SystemError().chain(L"poll()");
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
// timeout
|
||||||
|
return countdown.timedOut();
|
||||||
|
|
||||||
|
default:
|
||||||
|
// perform read
|
||||||
|
size_t res = write(buf, remaining);
|
||||||
|
if( !(remaining -= res) ) return amt;
|
||||||
|
buf += res;
|
||||||
|
countdown.transferred(res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update timeout
|
||||||
|
if(!countdown.update(timeout_remaining))
|
||||||
|
return countdown.timedOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOPosixDevice::writeBlock()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::sync()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
checkWrite();
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOPosixDevice::sync()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(TEMP_FAILURE_RETRY(::fdatasync(fd))) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"fdatasync()")
|
||||||
|
.chain(L"IOPosixDevice::sync()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::setNonBlocking()
|
||||||
|
{
|
||||||
|
int flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
if(flags == -1) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"fcntl(..., F_GETFL, ...)")
|
||||||
|
.chain(L"IOPosixDevice::setNonBlocking()");
|
||||||
|
}
|
||||||
|
if(flags & O_NONBLOCK) return;
|
||||||
|
flags |= O_NONBLOCK;
|
||||||
|
if(fcntl(fd, F_SETFL, flags)) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"fcntl(..., F_SETFL, ...)")
|
||||||
|
.chain(L"IOPosixDevice::setNonBlocking()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int IOPosixDevice::ioModeFlags(IOMode mode)
|
||||||
|
{
|
||||||
|
switch(mode) {
|
||||||
|
case IOClosed:
|
||||||
|
throw IOModeError(this, L"open", IONone);
|
||||||
|
|
||||||
|
case IONone:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case IORead:
|
||||||
|
return O_RDONLY;
|
||||||
|
|
||||||
|
case IOWrite:
|
||||||
|
return O_WRONLY;
|
||||||
|
|
||||||
|
case IOReadWrite:
|
||||||
|
return O_RDWR;
|
||||||
|
}
|
||||||
|
throw ProgramException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::checkRead() const
|
||||||
|
{
|
||||||
|
IOMode mode = getIOMode();
|
||||||
|
if(mode == IORead || mode == IOReadWrite) return;
|
||||||
|
throw IOModeError(const_cast<IOPosixDevice*>(this), L"read", IORead);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::checkWrite() const
|
||||||
|
{
|
||||||
|
IOMode mode = getIOMode();
|
||||||
|
if(mode == IOWrite || mode == IOReadWrite) return;
|
||||||
|
throw IOModeError(const_cast<IOPosixDevice*>(this), L"write", IOWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::checkOpen(const std::wstring& op) const
|
||||||
|
{
|
||||||
|
IOMode mode = getIOMode();
|
||||||
|
if(mode != IOClosed) return;
|
||||||
|
throw IOModeError(const_cast<IOPosixDevice*>(this), op, IOWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::posixOpen(const char* path, IOMode mode)
|
||||||
|
{
|
||||||
|
fd = TEMP_FAILURE_RETRY(::open(path, ioModeFlags(mode)));
|
||||||
|
if(fd == -1) {
|
||||||
|
if(errno == ENOENT) throw FileNotFound(path);
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"open()")
|
||||||
|
.chain(L"IOPosixDevice::posixOpen()");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setNonBlocking();
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
TEMP_FAILURE_RETRY(::close(fd));
|
||||||
|
fd = -1;
|
||||||
|
e.chain(L"IOPosixDevice::posixOpen()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
ioMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOPosixDevice::checkErrors()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/* lw-support/src/lib/IO/PosixDevice.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief POSIX I/O device class.
|
||||||
|
|
||||||
|
Many standard devices can be accessed through a POSIX file descriptor.
|
||||||
|
This is the base class for all such devices. It provides implementations
|
||||||
|
for reading, writing, etc.; but not for actually opening devices.
|
||||||
|
|
||||||
|
\todo Port readBlock() and writeBlock() over to use wait(), and deal
|
||||||
|
with any exceptional conditions somehow.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IOPosixDevice : public IODevice, public Uncopyable {
|
||||||
|
protected:
|
||||||
|
/// The POSIX file descriptor.
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
// implemented virtuals
|
||||||
|
virtual int getListenFd() const
|
||||||
|
{ return fd; }
|
||||||
|
|
||||||
|
/// Convenience function: sets the fd into non-blocking mode.
|
||||||
|
void setNonBlocking();
|
||||||
|
|
||||||
|
/// Convenience function: converts enumeration value into GNU
|
||||||
|
/// \c open() bitfield, throwing an exception if necessary.
|
||||||
|
int ioModeFlags(IOMode mode);
|
||||||
|
|
||||||
|
/// Convenience function: POSIX open() wrapper, throws exception.
|
||||||
|
void posixOpen(const char* path, IOMode mode);
|
||||||
|
|
||||||
|
/// Convenience function: check that we can read or throw exception.
|
||||||
|
void checkRead() const;
|
||||||
|
|
||||||
|
/// Convenience function: check that we can write or throw exception.
|
||||||
|
void checkWrite() const;
|
||||||
|
|
||||||
|
/// Convenience function: check device is open or throw exception.
|
||||||
|
void checkOpen(const std::wstring& op) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor. Does nothing.
|
||||||
|
IOPosixDevice()
|
||||||
|
: fd(-1)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor. Closes device.
|
||||||
|
virtual ~IOPosixDevice();
|
||||||
|
|
||||||
|
// implemented virtuals
|
||||||
|
virtual void close();
|
||||||
|
virtual size_t read(char* buf, size_t amt);
|
||||||
|
virtual size_t write(const char* buf, size_t amt);
|
||||||
|
virtual bool wait(bool read, bool write, int timeout);
|
||||||
|
virtual size_t readBlock(char* buf, size_t amt, int timeout = -1,
|
||||||
|
IOTimeoutMode timeoutMode = IOTimeoutHardFull);
|
||||||
|
virtual size_t writeBlock(const char* buf, size_t amt, int timeout
|
||||||
|
= -1, IOTimeoutMode timeoutMode = IOTimeoutHardFull);
|
||||||
|
virtual void sync();
|
||||||
|
virtual void checkErrors();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/* lw-support/src/lib/IO/SerialPort.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOSerialPort::open(IOMode mode, const char* path, int baud, bool twoStopBits)
|
||||||
|
{
|
||||||
|
if(fd != -1) close();
|
||||||
|
|
||||||
|
try {
|
||||||
|
posixOpen(path, mode);
|
||||||
|
setOptions(baud, twoStopBits);
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOSerialPort::open()");
|
||||||
|
close();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOSerialPort::setOptions(int baud, bool twoStopBits)
|
||||||
|
{
|
||||||
|
// read current characteristics
|
||||||
|
struct termios term;
|
||||||
|
if(tcgetattr(fd, &term) < 0) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"tcgetattr()")
|
||||||
|
.chain(L"IOSerialPort::setOptions()");
|
||||||
|
}
|
||||||
|
|
||||||
|
// set 8N1 mode
|
||||||
|
term.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR
|
||||||
|
| ICRNL | IXON | IXOFF);
|
||||||
|
term.c_oflag &= ~OPOST;
|
||||||
|
term.c_cflag &= ~(PARENB | CSIZE | CSTOPB);
|
||||||
|
term.c_cflag |= CS8;
|
||||||
|
term.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||||
|
|
||||||
|
// set baud rate and stop bits
|
||||||
|
if(twoStopBits) term.c_cflag |= CSTOPB;
|
||||||
|
if(cfsetspeed(&term, baud)) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"cfsetspeed()")
|
||||||
|
.chain(L"IOSerialPort::setOptions()");
|
||||||
|
}
|
||||||
|
|
||||||
|
// set attributes
|
||||||
|
if(tcsetattr(fd, TCSAFLUSH, &term)) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"tcsetattr()")
|
||||||
|
.chain(L"IOSerialPort::setOptions()");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOSerialPort::discardInput()
|
||||||
|
{
|
||||||
|
if(TEMP_FAILURE_RETRY(tcflush(fd, TCIFLUSH))) {
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"tcflush()")
|
||||||
|
.chain(L"IOSerialPort::discardInput()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* lw-support/src/lib/IO/SerialPort.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Serial port I/O device.
|
||||||
|
|
||||||
|
This class allows you to control a serial (RS232) port or equivalent
|
||||||
|
device. It provides control over the baud rate and whether you want to
|
||||||
|
use one stop bit (the standard) or two (useful for communicating with
|
||||||
|
embedded devices when the baud rate generators don't exactly match). It
|
||||||
|
does not provide support for parity, flow control or other than 8 data
|
||||||
|
bits.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IOSerialPort : public IOPosixDevice {
|
||||||
|
public:
|
||||||
|
/// Constructor (doesn't open any device).
|
||||||
|
IOSerialPort()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Open or reopen device.
|
||||||
|
|
||||||
|
\param mode The mode in which the device should be opened.
|
||||||
|
\param path Path to the device node to open.
|
||||||
|
\param baud The baud rate you want.
|
||||||
|
\param twoStopBits \a true if you want two stop bits; \a false if
|
||||||
|
you don't.
|
||||||
|
\throws IOModeError if you pass lw::IOClosed as the mode.
|
||||||
|
\throws SystemError if the device couldn't be opened or if the
|
||||||
|
options cannot be set.
|
||||||
|
|
||||||
|
Opens the device, setting the desired baud rate and number of stop
|
||||||
|
bits. If a device is already open, it is closed first.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void open(IOMode mode, const char* path, int baud,
|
||||||
|
bool twoStopBits = false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Change device options (baud rate etc.).
|
||||||
|
|
||||||
|
\param baud The new baud rate.
|
||||||
|
\param twoStopBits \a true if you want two stop bits; \a false if
|
||||||
|
you don't.
|
||||||
|
\throws IOModeError if the device is closed.
|
||||||
|
\throws SystemError if the options cannot be set.
|
||||||
|
|
||||||
|
Changes the baud rate and number of stop bits.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void setOptions(int baud, bool twoStopBits = false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Discard any input waiting to be read.
|
||||||
|
void discardInput();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
/* lw-support/src/lib/IO/Util/WriteTask.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IOWriteTask::IOWriteTask(lw::EventManager& eventManager, IODevice* ioDev, int timeoutInactive,
|
||||||
|
int timeoutOverall, CompletionCallback* completion,
|
||||||
|
const char* data, size_t amt, bool copy)
|
||||||
|
: CompletionTask(completion), eventManager(eventManager), ioDev(ioDev),
|
||||||
|
timeoutInactive(timeoutInactive), timeoutOverall(timeoutOverall), data(0), wr(0), amt(amt),
|
||||||
|
copy(copy)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
NullPointer::check(data, L"data");
|
||||||
|
NullPointer::check(ioDev, L"ioDev");
|
||||||
|
|
||||||
|
eventManager.registerDevice(ioDev, this, lw::IOEventHUP | lw::IOEventWrite
|
||||||
|
| lw::IOEventError);
|
||||||
|
#if 0
|
||||||
|
timerKey2 = eventManager.registerTimer(this, lw::ElapsedTime(0));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// copy data if necessary
|
||||||
|
if(copy) {
|
||||||
|
this->data = new char[amt];
|
||||||
|
memcpy(this->data, data, amt);
|
||||||
|
} else {
|
||||||
|
this->data = (char*)data;
|
||||||
|
}
|
||||||
|
wr = this->data;
|
||||||
|
|
||||||
|
// set timeouts if necessary
|
||||||
|
if(timeoutInactive >= 0)
|
||||||
|
timerKey0 = eventManager.registerTimer(this, lw::ElapsedTime::from_ms(timeoutInactive));
|
||||||
|
if(timeoutOverall >= 0)
|
||||||
|
timerKey1 = eventManager.registerTimer(this, lw::ElapsedTime::from_ms(timeoutOverall));
|
||||||
|
}
|
||||||
|
catch(lw::Exception& e) {
|
||||||
|
if(copy) delete [] this->data;
|
||||||
|
e.chain(L"IOWriteTask::IOWriteTask()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IOWriteTask::~IOWriteTask()
|
||||||
|
{
|
||||||
|
if(this->timeoutInactive >= 0) eventManager.ignoreTimer(timerKey0);
|
||||||
|
if(this->timeoutOverall >= 0) eventManager.ignoreTimer(timerKey1);
|
||||||
|
delete ioDev;
|
||||||
|
// if(copy) delete [] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOWriteTask::taskAbort(const lw::Exception& e)
|
||||||
|
{
|
||||||
|
taskAborted(e);
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool IOWriteTask::ioReady(uint32_t flags)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if(flags & lw::IOEventError) ioDev->checkErrors();
|
||||||
|
if(flags & lw::IOEventHUP) throw lw::Exception(L"Remote device closed connection.");
|
||||||
|
if(flags & lw::IOEventWrite) tryWrite();
|
||||||
|
}
|
||||||
|
catch(lw::Exception& e) {
|
||||||
|
taskAbort(e);
|
||||||
|
delete this;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void IOWriteTask::tryWrite()
|
||||||
|
{
|
||||||
|
while(amt) {
|
||||||
|
size_t w = ioDev->write(wr, amt);
|
||||||
|
if(!w) return;
|
||||||
|
|
||||||
|
wr += w;
|
||||||
|
amt -= w;
|
||||||
|
|
||||||
|
if(timeoutInactive >= 0) {
|
||||||
|
eventManager.ignoreTimer(timerKey0);
|
||||||
|
timerKey0 = eventManager.registerTimer(this, lw::ElapsedTime::from_ms(timeoutInactive), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// done!
|
||||||
|
taskCompleted();
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool IOWriteTask::timer(Key)
|
||||||
|
{
|
||||||
|
// must be a timeout
|
||||||
|
taskAbort(lw::Exception(L"WriteTask timeout."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/* lw-support/src/lib/IO/Util/WriteTask.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Class which writes some data to an I/O device and then closes it.
|
||||||
|
|
||||||
|
Instances of this class will write data to an I/O device, using non-blocking I/O, and (on
|
||||||
|
destruction) will close the device and delete it. It has options for timeouts and deals correctly
|
||||||
|
with errors. On task completion, the object will delete itself.
|
||||||
|
|
||||||
|
A typical usage example would be to accept a TCP connection, instantiate an IOWriteTask (with a
|
||||||
|
suitable message and timeout, pointed at the TCP connection, connected to a CompletionList object),
|
||||||
|
register the task with the CompletionList, and then simply process events as usual, forgetting about
|
||||||
|
the TCP connection and the IOWriteTask instance.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class IOWriteTask : public CompletionTask,
|
||||||
|
private EventCallbackIO,
|
||||||
|
private EventCallbackTimer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
lw::EventManager& eventManager;
|
||||||
|
IOInterface* ioDev;
|
||||||
|
int timeoutInactive, timeoutOverall;
|
||||||
|
char* data, * wr;
|
||||||
|
size_t amt;
|
||||||
|
bool copy;
|
||||||
|
Key timerKey0, timerKey1;
|
||||||
|
|
||||||
|
void tryWrite();
|
||||||
|
|
||||||
|
// from EventCallbackIO
|
||||||
|
virtual bool ioReady(uint32_t flags);
|
||||||
|
|
||||||
|
// from EventCallbackTimer
|
||||||
|
virtual bool timer(Key key);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor. Sets up the task.
|
||||||
|
|
||||||
|
\param eventManager The event manager object to use.
|
||||||
|
\param ioDev The I/O device in question.
|
||||||
|
\param timeoutInactive An inactivity timeout period, in milliseconds. -1 to disable.
|
||||||
|
\param timeoutOverall An overall timeout period, in milliseconds. -1 to disable.
|
||||||
|
\param completion The object which will be notified about completion. Can be 0.
|
||||||
|
\param data Pointer to the block of data to write.
|
||||||
|
\param amt Number of bytes of data to write.
|
||||||
|
\param copy Pass \c true if you want the data to be copied, or \c false if you guarantee its
|
||||||
|
persistence until the task is completed.
|
||||||
|
|
||||||
|
\throws NullPointer if \a ioDev or \a data is 0.
|
||||||
|
\throws SystemError if a system call fails.
|
||||||
|
|
||||||
|
Begins the process. Registers the device with the event manager, attempts to write some data
|
||||||
|
(these can result in system errors). On completion, the object will delete itself.
|
||||||
|
|
||||||
|
*/
|
||||||
|
IOWriteTask(lw::EventManager& eventManager, IODevice* ioDev, int timeoutInactive,
|
||||||
|
int timeoutOverall, CompletionCallback* completion,
|
||||||
|
const char* data, size_t amt, bool copy);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Destructor. Closes and deletes I/O device.
|
||||||
|
virtual ~IOWriteTask();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Aborts task, closing and deleting the I/O device and the instance.
|
||||||
|
virtual void taskAbort(const lw::Exception& e);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* lw-support/src/lib/Net/Address/Address.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wostream& operator<<(std::wostream& ostr, const lw::NetAddress& addr)
|
||||||
|
{
|
||||||
|
ostr << addr.toString();
|
||||||
|
return ostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* lw-support/src/lib/Net/Address/Address.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Base class for network addresses.
|
||||||
|
|
||||||
|
Since network addresses are used in very different ways, this class
|
||||||
|
doesn't provide much of an interface. However, it does allow for
|
||||||
|
dynamic casting, which is its main purpose.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NetAddress {
|
||||||
|
public:
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~NetAddress()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Convert to std::string representation
|
||||||
|
virtual std::wstring toString() const = 0;
|
||||||
|
|
||||||
|
/// Clone address.
|
||||||
|
virtual NetAddress* clone() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream insertion operator.
|
||||||
|
std::wostream& operator<<(std::wostream&, const lw::NetAddress&);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* lw-support/src/lib/Net/Address/IPv4.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const NetAddressIPv4 NetAddressIPv4::any(0, 0, 0, 0);
|
||||||
|
const NetAddressIPv4 NetAddressIPv4::loopback(127, 0, 0, 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring NetAddressIPv4::toString() const
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << (uint)(addr[0]) << L'.'
|
||||||
|
<< (uint)(addr[1]) << L'.'
|
||||||
|
<< (uint)(addr[2]) << L'.'
|
||||||
|
<< (uint)(addr[3]);
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetAddressIPv4 NetAddressIPv4::fromString(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
uint8_t addr[4];
|
||||||
|
int addrPos = 0;
|
||||||
|
uint p = 0;
|
||||||
|
bool seen = false;
|
||||||
|
wchar_t ch;
|
||||||
|
|
||||||
|
std::wstring::size_type pos = 0, len = str.length();
|
||||||
|
|
||||||
|
for(pos = 0; pos < len; ++pos) {
|
||||||
|
ch = str[pos];
|
||||||
|
switch(ch) {
|
||||||
|
case '0' ... '9':
|
||||||
|
seen = true;
|
||||||
|
p *= 10;
|
||||||
|
p += (ch - '0');
|
||||||
|
if(p > 255) {
|
||||||
|
throw ParseError(L"Octet cannot be > 255.", str, pos)
|
||||||
|
.chain(L"NetAddressIPv4::fromString()");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
if(!seen) {
|
||||||
|
throw ParseError(L"Empty octet.", str, pos)
|
||||||
|
.chain(L"NetAddressIPv4::fromString()");
|
||||||
|
}
|
||||||
|
if(addrPos == 3) {
|
||||||
|
throw ParseError(L"Too many octets.", str, pos)
|
||||||
|
.chain(L"NetAddressIPv4::fromString()");
|
||||||
|
}
|
||||||
|
|
||||||
|
addr[addrPos++] = p;
|
||||||
|
p = 0;
|
||||||
|
seen = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw ParseError(L"Unexpected symbol.", str, pos)
|
||||||
|
.chain(L"NetAddressIPv4::fromString()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(addrPos != 3 || !seen) {
|
||||||
|
throw ParseError(L"Not enough octets.", str, pos)
|
||||||
|
.chain(L"NetAddressIPv4::fromString()");
|
||||||
|
}
|
||||||
|
|
||||||
|
addr[3] = p;
|
||||||
|
return NetAddressIPv4(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetAddressIPv4 NetAddressIPv4::fromDNS(const std::string& host)
|
||||||
|
{
|
||||||
|
// attempt to convert from numeric form first
|
||||||
|
try {
|
||||||
|
return fromString(lw::asciiToUcs4(host));
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
|
||||||
|
// now perform blocking DNS lookup
|
||||||
|
struct hostent he, * hep = 0;
|
||||||
|
int err = 0, result = 0;
|
||||||
|
char buffer[4096];
|
||||||
|
|
||||||
|
result = gethostbyname2_r(host.c_str(), AF_INET,
|
||||||
|
&he, buffer, sizeof(buffer), &hep, &err);
|
||||||
|
switch(result) {
|
||||||
|
case 0:
|
||||||
|
// success!
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HOST_NOT_FOUND:
|
||||||
|
throw DnsError(L"Host not found.", host);
|
||||||
|
|
||||||
|
case TRY_AGAIN:
|
||||||
|
throw DnsError(L"Temporary failure in name resolution.", host);
|
||||||
|
|
||||||
|
case NO_RECOVERY:
|
||||||
|
throw DnsError(L"Unrecoverable failure in name resolution.", host);
|
||||||
|
|
||||||
|
case NO_ADDRESS:
|
||||||
|
throw DnsError(L"Host does not have an associated IPv4 address.", host);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw DnsError(L"Unknown DNS error.", host);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return NetAddressIPv4(he.h_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
/* lw-support/src/lib/Net/Address/IPv4.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief IPv4 network address.
|
||||||
|
|
||||||
|
This class is used to represent an IPv4 network address. It has a basic
|
||||||
|
blocking DNS lookup function.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NetAddressIPv4 : public NetAddress {
|
||||||
|
private:
|
||||||
|
uint8_t addr[4];
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Well-known addresses ////////////////////////////////////
|
||||||
|
|
||||||
|
/// 0.0.0.0
|
||||||
|
static const NetAddressIPv4 any;
|
||||||
|
|
||||||
|
/// 127.0.0.1
|
||||||
|
static const NetAddressIPv4 loopback;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Constructors ////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Default constructor: address 0.0.0.0.
|
||||||
|
NetAddressIPv4()
|
||||||
|
{
|
||||||
|
memset(addr, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy constructor.
|
||||||
|
NetAddressIPv4(const NetAddressIPv4& other)
|
||||||
|
: NetAddress()
|
||||||
|
{
|
||||||
|
memcpy(addr, other.addr, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor: specify numerically.
|
||||||
|
NetAddressIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
||||||
|
{
|
||||||
|
addr[0] = a;
|
||||||
|
addr[1] = b;
|
||||||
|
addr[2] = c;
|
||||||
|
addr[3] = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor: from array.
|
||||||
|
NetAddressIPv4(const uint8_t* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor: from memory.
|
||||||
|
NetAddressIPv4(const char* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// implemented virtual
|
||||||
|
virtual NetAddressIPv4* clone() const
|
||||||
|
{ return new NetAddressIPv4(*this); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Modifying functions /////////////////////////////////////
|
||||||
|
|
||||||
|
/// Assignment operator.
|
||||||
|
NetAddressIPv4& operator=(const NetAddressIPv4& other)
|
||||||
|
{
|
||||||
|
memcpy(addr, other.addr, 4);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set numerically.
|
||||||
|
NetAddressIPv4& set(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
||||||
|
{
|
||||||
|
addr[0] = a;
|
||||||
|
addr[1] = b;
|
||||||
|
addr[2] = c;
|
||||||
|
addr[3] = d;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set from array.
|
||||||
|
NetAddressIPv4& set(const uint8_t* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 4);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set from memory.
|
||||||
|
NetAddressIPv4& set(const char* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 4);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Query functions /////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Copy to array.
|
||||||
|
uint8_t* copy(uint8_t* dest) const
|
||||||
|
{
|
||||||
|
memcpy(dest, addr, 4);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy to memory.
|
||||||
|
char* copy(char* dest) const
|
||||||
|
{
|
||||||
|
memcpy(dest, addr, 4);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For interfacing with the C library.
|
||||||
|
struct sockaddr* copy(struct sockaddr_in* s, uint16_t port) const
|
||||||
|
{
|
||||||
|
s->sin_family = AF_INET;
|
||||||
|
s->sin_port = htons(port);
|
||||||
|
memcpy(&(s->sin_addr.s_addr), addr, 4);
|
||||||
|
return (struct sockaddr*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // String functions ////////////////////////////////////////
|
||||||
|
|
||||||
|
// implemented virtual
|
||||||
|
virtual std::wstring toString() const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Construct from string.
|
||||||
|
|
||||||
|
\param str The string containing an IPv4 address to parse.
|
||||||
|
\throws ParseError if a parse error occurs.
|
||||||
|
\returns A new address object.
|
||||||
|
|
||||||
|
This function parses an IPv4 address in dotted quad format, i.e.
|
||||||
|
<tt>a.b.c.d</tt> where a, b, c and d are all 8-bit unsigned values.
|
||||||
|
Any trailing or leading whitespace is ignored.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static NetAddressIPv4 fromString(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Look up via DNS (blocking).
|
||||||
|
|
||||||
|
\param host The hostname to look up.
|
||||||
|
\throws DnsError If an error occurs.
|
||||||
|
\returns A new address object.
|
||||||
|
|
||||||
|
This function looks up a specific host via DNS, returning its IPv4
|
||||||
|
address. It is a blocking function.
|
||||||
|
|
||||||
|
\todo std::wstring-capable version
|
||||||
|
|
||||||
|
*/
|
||||||
|
static NetAddressIPv4 fromDNS(const std::string& host);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/* lw-support/src/lib/Net/Address/IPv6.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
uint8_t ipv6_any_initialiser[16] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
};
|
||||||
|
uint8_t ipv6_loopback_initialiser[16] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const NetAddressIPv6 NetAddressIPv6::any(ipv6_any_initialiser);
|
||||||
|
const NetAddressIPv6 NetAddressIPv6::loopback(ipv6_loopback_initialiser);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring NetAddressIPv6::toString() const
|
||||||
|
{
|
||||||
|
char buffer[50]; // should be enough for a full IPv6 address
|
||||||
|
if(!inet_ntop(AF_INET6, addr, buffer, sizeof(buffer))) {
|
||||||
|
throw lw::ProgramException().chain(L"NetAddressIPv6::toString()");
|
||||||
|
}
|
||||||
|
|
||||||
|
return lw::strToUcs4(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetAddressIPv6 NetAddressIPv6::fromString(const std::wstring& str)
|
||||||
|
{
|
||||||
|
std::string ip;
|
||||||
|
try {
|
||||||
|
ip = ucs4ToAscii(stripWhitespace(str));
|
||||||
|
}
|
||||||
|
catch(lw::Exception& e) {
|
||||||
|
e.chain(L"NetAddressIPv6::fromString()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct in6_addr addr;
|
||||||
|
|
||||||
|
if(!inet_pton(AF_INET6, ip.c_str(), &addr))
|
||||||
|
throw ParseError(L"Not an IPv6 address.", str, 0);
|
||||||
|
|
||||||
|
return NetAddressIPv6(addr.s6_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetAddressIPv6 NetAddressIPv6::fromDNS(const std::string& host)
|
||||||
|
{
|
||||||
|
// attempt to convert from numeric form first
|
||||||
|
try {
|
||||||
|
return fromString(lw::asciiToUcs4(host));
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
|
||||||
|
// now perform blocking DNS lookup
|
||||||
|
struct hostent he, * hep = 0;
|
||||||
|
int err = 0, result = 0;
|
||||||
|
char buffer[4096];
|
||||||
|
|
||||||
|
result = gethostbyname2_r(host.c_str(), AF_INET6,
|
||||||
|
&he, buffer, sizeof(buffer), &hep, &err);
|
||||||
|
switch(result) {
|
||||||
|
case 0:
|
||||||
|
// success!
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HOST_NOT_FOUND:
|
||||||
|
throw DnsError(L"Host not found.", host);
|
||||||
|
|
||||||
|
case TRY_AGAIN:
|
||||||
|
throw DnsError(L"Temporary failure in name resolution.", host);
|
||||||
|
|
||||||
|
case NO_RECOVERY:
|
||||||
|
throw DnsError(L"Unrecoverable failure in name resolution.", host);
|
||||||
|
|
||||||
|
case NO_ADDRESS:
|
||||||
|
throw DnsError(L"Host does not have an associated IPv6 address.", host);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw DnsError(L"Unknown DNS error.", host);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return NetAddressIPv6(he.h_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
/* lw-support/src/lib/Net/Address/IPv6.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief IPv6 network address.
|
||||||
|
|
||||||
|
This class is used to represent an IPv6 network address. It does not
|
||||||
|
have functions for converting names to IP addresses (DNS); this must be
|
||||||
|
done with the ... class.
|
||||||
|
|
||||||
|
\todo mention DNS class
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NetAddressIPv6 : public NetAddress {
|
||||||
|
private:
|
||||||
|
uint8_t addr[16];
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Well-known addresses ////////////////////////////////////
|
||||||
|
|
||||||
|
/// The address \c ::
|
||||||
|
static const NetAddressIPv6 any;
|
||||||
|
|
||||||
|
/// The address \c ::1
|
||||||
|
static const NetAddressIPv6 loopback;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Constructors ////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Default constructor: address \c ::
|
||||||
|
NetAddressIPv6()
|
||||||
|
{
|
||||||
|
memset(addr, 0, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy constructor.
|
||||||
|
NetAddressIPv6(const NetAddressIPv6& other)
|
||||||
|
: NetAddress()
|
||||||
|
{
|
||||||
|
memcpy(addr, other.addr, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor: from array.
|
||||||
|
NetAddressIPv6(const uint8_t* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructor: from memory.
|
||||||
|
NetAddressIPv6(const char* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// implemented virtual
|
||||||
|
virtual NetAddressIPv6* clone() const
|
||||||
|
{ return new NetAddressIPv6(*this); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Modifying functions /////////////////////////////////////
|
||||||
|
|
||||||
|
/// Assignment operator.
|
||||||
|
NetAddressIPv6& operator=(const NetAddressIPv6& other)
|
||||||
|
{
|
||||||
|
memcpy(addr, other.addr, 16);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set from array.
|
||||||
|
NetAddressIPv6& set(const uint8_t* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 16);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set from memory.
|
||||||
|
NetAddressIPv6& set(const char* newAddr)
|
||||||
|
{
|
||||||
|
memcpy(addr, newAddr, 16);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Query functions /////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Copy to array.
|
||||||
|
uint8_t* copy(uint8_t* dest) const
|
||||||
|
{
|
||||||
|
memcpy(dest, addr, 16);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy to memory.
|
||||||
|
char* copy(char* dest) const
|
||||||
|
{
|
||||||
|
memcpy(dest, addr, 16);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For interfacing with the C library.
|
||||||
|
struct sockaddr* copy(struct sockaddr_in6* s, uint16_t port) const
|
||||||
|
{
|
||||||
|
s->sin6_family = AF_INET6;
|
||||||
|
s->sin6_port = htons(port);
|
||||||
|
memcpy(s->sin6_addr.s6_addr, addr, 16);
|
||||||
|
return (struct sockaddr*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // String functions ////////////////////////////////////////
|
||||||
|
|
||||||
|
// implemented virtual
|
||||||
|
virtual std::wstring toString() const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Construct from std::string.
|
||||||
|
|
||||||
|
\param str The std::string containing an IPv6 address to parse.
|
||||||
|
\throws ParseError if a parse error occurs.
|
||||||
|
\returns A new address object.
|
||||||
|
|
||||||
|
This function parses an IPv6 address in colon-delimited format,
|
||||||
|
e.g. \c "0123:4567:89ab:cdef:0123:4567:89ab:cdef". It understands
|
||||||
|
the compact form, e.g. \c "::1". Any traling or leading whitespace
|
||||||
|
is ignored.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static NetAddressIPv6 fromString(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Look up via DNS (blocking).
|
||||||
|
|
||||||
|
\param host The hostname to look up.
|
||||||
|
\throws DnsError If an error occurs.
|
||||||
|
\returns A new address object.
|
||||||
|
|
||||||
|
This function looks up a specific host via DNS, returning its IPv6
|
||||||
|
address. It is a blocking function.
|
||||||
|
|
||||||
|
\todo std::wstring-capable version
|
||||||
|
|
||||||
|
*/
|
||||||
|
static NetAddressIPv6 fromDNS(const std::string& host);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* lw-support/src/lib/Net/Client.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetClient::checkErrors()
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
socklen_t socklen = sizeof(error);
|
||||||
|
|
||||||
|
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &socklen) == -1)
|
||||||
|
throw SystemError().chain(L"NetClient::checkSocketErrors()")
|
||||||
|
.chain(L"getsockopt()");
|
||||||
|
|
||||||
|
if(error)
|
||||||
|
throw SystemError(error).chain(L"NetClient::checkSocketErrors()")
|
||||||
|
.chain(L"(pending error)");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetClient::shutdown(bool read, bool write)
|
||||||
|
{
|
||||||
|
if(ioMode == IOClosed)
|
||||||
|
throw IOModeError(this, L"shutdown", IONone);
|
||||||
|
|
||||||
|
IOMode newIoMode;
|
||||||
|
int shut;
|
||||||
|
|
||||||
|
if(read && write) {
|
||||||
|
newIoMode = IONone;
|
||||||
|
shut = SHUT_RDWR;
|
||||||
|
} else if (read) {
|
||||||
|
if(ioMode == IOWrite || ioMode == IOReadWrite) newIoMode = IOWrite;
|
||||||
|
else newIoMode = IONone;
|
||||||
|
shut = SHUT_RD;
|
||||||
|
} else if (write) {
|
||||||
|
if(ioMode == IORead || ioMode == IOReadWrite) newIoMode = IORead;
|
||||||
|
else newIoMode = IONone;
|
||||||
|
shut = SHUT_WR;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(TEMP_FAILURE_RETRY(::shutdown(fd, shut)))
|
||||||
|
throw SystemError().chain(L"NetClient::shutdown()");
|
||||||
|
ioMode = newIoMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/* lw-support/src/lib/Net/Client.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Base class for network clients.
|
||||||
|
|
||||||
|
This class contains some common functionality shared by all network
|
||||||
|
socket clients.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NetClient : public IOPosixDevice {
|
||||||
|
public:
|
||||||
|
// implemented virtuals
|
||||||
|
virtual void checkErrors();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Shut down part of a connection.
|
||||||
|
|
||||||
|
\param read Set to \a true if you want to shut down reading.
|
||||||
|
\param write Set to \a true if you want to shut down writing.
|
||||||
|
\throws IOModeError if the socket is not connected.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
This allows you to shut down part of a connection; either reading or
|
||||||
|
writing (or possibly both). Any data waiting to be received or
|
||||||
|
transmitted is discarded, and the read or write operation becomes
|
||||||
|
unavailable (the I/O mode is changed to reflect this).
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void shutdown(bool read, bool write);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,372 @@
|
||||||
|
/* lw-support/src/lib/Net/Protocols/HTTP.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // HTTPHeaderParser ////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HTTPHeaderParser::HTTPHeaderParser()
|
||||||
|
: state(StateBegin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t HTTPHeaderParser::parse(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
size_t parsed = 0;
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
begin:
|
||||||
|
if(parsed == amt || state == StateDone) return parsed;
|
||||||
|
if(state == StateError) throw HTTPMalformed(L"(previous error)");
|
||||||
|
|
||||||
|
#define ERROR(p) do { \
|
||||||
|
state = StateError; \
|
||||||
|
throw HTTPMalformed(p); \
|
||||||
|
}while(0)
|
||||||
|
|
||||||
|
// extract character (ASCII only)
|
||||||
|
ch = data[parsed++];
|
||||||
|
if(ch & ~0x7F) {
|
||||||
|
state = StateError;
|
||||||
|
throw HTTPMalformed(L"Non-ASCII data encountered in HTTP header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case StateBegin:
|
||||||
|
if(ch == '\r') {
|
||||||
|
state = StateNewline2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!isgraph(ch) || ch == ':') ERROR(L"invalid field name");
|
||||||
|
fieldName = tolower(ch);
|
||||||
|
state = StateFieldName;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateNewline:
|
||||||
|
if(ch != '\n') ERROR(L"expecting \\n after \\r");
|
||||||
|
state = StateLine;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateLine:
|
||||||
|
if(ch == '\r') {
|
||||||
|
state = StateNewline2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(isblank(ch)) {
|
||||||
|
state = StateFieldValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!isgraph(ch) || ch == ':') ERROR(L"invalid field name");
|
||||||
|
fieldName = tolower(ch);
|
||||||
|
state = StateFieldName;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateFieldName:
|
||||||
|
if(ch == ':') {
|
||||||
|
state = StateFieldValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!isgraph(ch)) ERROR(L"invalid field name");
|
||||||
|
fieldName += tolower(ch);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateFieldValue:
|
||||||
|
if(ch == '\r') {
|
||||||
|
state = StateNewline;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!isprint(ch) && ch != '\t') ERROR(L"invalid field value");
|
||||||
|
fields[fieldName] += ch;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateNewline2:
|
||||||
|
if(ch != '\n') ERROR(L"expecting \\n after \\r");
|
||||||
|
state = StateDone;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateDone:
|
||||||
|
case StateError:
|
||||||
|
// should never get here!
|
||||||
|
throw lw::ProgramException().chain(L"HTTPHeaderParser::parse() [1]");
|
||||||
|
}
|
||||||
|
|
||||||
|
goto begin;
|
||||||
|
#undef ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//END // HTTPHeaderParser //////////////////////////////////////////////
|
||||||
|
//BEGIN // HTTPResponseParser //////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const char* HTTPResponseParser::HTTPSeq = "HTTP/";
|
||||||
|
|
||||||
|
HTTPResponseParser::HTTPResponseParser()
|
||||||
|
: state(StateHTTPSeq), HTTPSeqPos(HTTPSeq),
|
||||||
|
versionMajor(0), versionMinor(0), responseCode(0), fields(hp.fields)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t HTTPResponseParser::parse(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
size_t parsed = 0;
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
begin:
|
||||||
|
if(parsed == amt || state == StateDone) return parsed;
|
||||||
|
if(state == StateError) throw HTTPMalformed(L"(previous error)");
|
||||||
|
if(state == StateHeader) {
|
||||||
|
try {
|
||||||
|
parsed += hp.parse(data + parsed, amt - parsed);
|
||||||
|
if(hp.completed()) state = StateDone;
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
state = StateError;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ERROR(p) do { \
|
||||||
|
state = StateError; \
|
||||||
|
throw HTTPMalformed(p); \
|
||||||
|
}while(0)
|
||||||
|
|
||||||
|
// extract character (ASCII only)
|
||||||
|
ch = data[parsed++];
|
||||||
|
if(ch & ~0x7F) {
|
||||||
|
state = StateError;
|
||||||
|
throw HTTPMalformed(L"Non-ASCII data encountered in HTTP response line.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case StateHTTPSeq:
|
||||||
|
if(ch != *HTTPSeqPos) ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
if(!*(++HTTPSeqPos)) state = StateHTTPMajor1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMajor1:
|
||||||
|
if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
versionMajor = ch - '0';
|
||||||
|
state = StateHTTPMajor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMajor:
|
||||||
|
if(isdigit(ch)) {
|
||||||
|
versionMajor *= 10;
|
||||||
|
versionMajor += ch - '0';
|
||||||
|
} else if(ch == '.') {
|
||||||
|
state = StateHTTPMinor1;
|
||||||
|
} else ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMinor1:
|
||||||
|
if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
versionMinor = ch - '0';
|
||||||
|
state = StateHTTPMinor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMinor:
|
||||||
|
if(isdigit(ch)) {
|
||||||
|
versionMinor *= 10;
|
||||||
|
versionMinor += ch - '0';
|
||||||
|
} else if(ch == ' ') {
|
||||||
|
state = StateHTTPResp1;
|
||||||
|
} else ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPResp1:
|
||||||
|
if(!isdigit(ch)) ERROR(L"invalid response code");
|
||||||
|
responseCode = 100 * (ch - '0');
|
||||||
|
state = StateHTTPResp2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPResp2:
|
||||||
|
if(!isdigit(ch)) ERROR(L"invalid response code");
|
||||||
|
responseCode += 10 * (ch - '0');
|
||||||
|
state = StateHTTPResp3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPResp3:
|
||||||
|
if(!isdigit(ch)) ERROR(L"invalid response code");
|
||||||
|
responseCode += (ch - '0');
|
||||||
|
state = StateHTTPResp4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPResp4:
|
||||||
|
if(ch != ' ') ERROR(L"invalid response code");
|
||||||
|
state = StateHTTPMsg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMsg:
|
||||||
|
if(ch == '\n') ERROR(L"\\n invalid on its own");
|
||||||
|
else if(ch == '\r') state = StateHTTPNewline;
|
||||||
|
else status += ch;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPNewline:
|
||||||
|
if(ch != '\n') ERROR(L"expecting \\r after \\n");
|
||||||
|
state = StateHeader;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHeader:
|
||||||
|
case StateDone:
|
||||||
|
case StateError:
|
||||||
|
// should never get here!
|
||||||
|
throw lw::ProgramException().chain(L"HTTPResponseParser::parse() [1]");
|
||||||
|
}
|
||||||
|
|
||||||
|
goto begin;
|
||||||
|
#undef ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//END // HTTPResponseParser ////////////////////////////////////////////
|
||||||
|
//BEGIN // HTTPRequestParser ///////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const char* HTTPRequestParser::HTTPSeq = "HTTP/";
|
||||||
|
|
||||||
|
HTTPRequestParser::HTTPRequestParser()
|
||||||
|
: state(StateHTTPMethod1), HTTPSeqPos(HTTPSeq), fields(hp.fields)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t HTTPRequestParser::parse(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
size_t parsed = 0;
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
begin:
|
||||||
|
if(parsed == amt || state == StateDone) return parsed;
|
||||||
|
if(state == StateError) throw HTTPMalformed(L"(previous error)");
|
||||||
|
if(state == StateHeader) {
|
||||||
|
try {
|
||||||
|
parsed += hp.parse(data + parsed, amt - parsed);
|
||||||
|
if(hp.completed()) state = StateDone;
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
state = StateError;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ERROR(p) do { \
|
||||||
|
state = StateError; \
|
||||||
|
throw HTTPMalformed(p); \
|
||||||
|
}while(0)
|
||||||
|
|
||||||
|
// extract character (ASCII only)
|
||||||
|
ch = data[parsed++];
|
||||||
|
if(ch & ~0x7F) {
|
||||||
|
state = StateError;
|
||||||
|
throw HTTPMalformed(L"Non-ASCII data encountered in HTTP request line.");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case StateHTTPMethod1:
|
||||||
|
if(!isgraph(ch)) ERROR(L"invalid HTTP method");
|
||||||
|
method = ch;
|
||||||
|
state = StateHTTPMethod;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMethod:
|
||||||
|
if(ch == ' ') {
|
||||||
|
state = StateHTTPUrl1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!isgraph(ch)) ERROR(L"invalid HTTP method");
|
||||||
|
method += ch;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPUrl1:
|
||||||
|
if(!isgraph(ch)) ERROR(L"invalid HTTP URL");
|
||||||
|
url = ch;
|
||||||
|
state = StateHTTPUrl;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPUrl:
|
||||||
|
if(ch == ' ') {
|
||||||
|
state = StateHTTPSeq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!isgraph(ch)) ERROR(L"invalid HTTP URL");
|
||||||
|
url += ch;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPSeq:
|
||||||
|
if(ch != *HTTPSeqPos) ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
if(!*(++HTTPSeqPos)) state = StateHTTPMajor1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMajor1:
|
||||||
|
if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
versionMajor = ch - '0';
|
||||||
|
state = StateHTTPMajor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMajor:
|
||||||
|
if(isdigit(ch)) {
|
||||||
|
versionMajor *= 10;
|
||||||
|
versionMajor += ch - '0';
|
||||||
|
} else if(ch == '.') {
|
||||||
|
state = StateHTTPMinor1;
|
||||||
|
} else ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMinor1:
|
||||||
|
if(!isdigit(ch)) ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
versionMinor = ch - '0';
|
||||||
|
state = StateHTTPMinor;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPMinor:
|
||||||
|
if(isdigit(ch)) {
|
||||||
|
versionMinor *= 10;
|
||||||
|
versionMinor += ch - '0';
|
||||||
|
} else if(ch == '\r') {
|
||||||
|
state = StateHTTPNewline;
|
||||||
|
} else ERROR(L"expecting 'HTTP/major.minor'");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHTTPNewline:
|
||||||
|
if(ch != '\n') ERROR(L"\\r must be followed by \\n");
|
||||||
|
state = StateHeader;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHeader:
|
||||||
|
case StateDone:
|
||||||
|
case StateError:
|
||||||
|
// should never get here!
|
||||||
|
throw lw::ProgramException().chain(L"HTTPRequestParser::parse() [1]");
|
||||||
|
}
|
||||||
|
|
||||||
|
goto begin;
|
||||||
|
#undef ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//END // HTTPRequestParser /////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
/* lw-support/src/lib/Net/Protocols/HTTP.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parser for HTTP header fields.
|
||||||
|
|
||||||
|
This class is used to parse the HTTP header fields that occur after the
|
||||||
|
HTTP request or response line. It is a finite state machine parser that
|
||||||
|
only consumes as many characters as it needs (i.e. it stops at the final
|
||||||
|
\c "\r\n\r\n" sequence demanded by the standard). It exposes these
|
||||||
|
header fields as part of a map.
|
||||||
|
|
||||||
|
\warning This isn't 100% RFC-compliant: it won't handle multiple
|
||||||
|
instances of the same field name in an RFC-compatible manner.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class HTTPHeaderParser {
|
||||||
|
private:
|
||||||
|
enum State {
|
||||||
|
StateBegin,
|
||||||
|
StateNewline,
|
||||||
|
StateLine,
|
||||||
|
StateFieldName,
|
||||||
|
StateFieldValue,
|
||||||
|
StateNewline2,
|
||||||
|
StateDone,
|
||||||
|
StateError
|
||||||
|
}state;
|
||||||
|
|
||||||
|
std::string fieldName;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
HTTPHeaderParser();
|
||||||
|
|
||||||
|
/// Map of field name (key) to value.
|
||||||
|
std::map<std::string, std::string> fields;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse incoming data.
|
||||||
|
|
||||||
|
\param data Pointer to character data.
|
||||||
|
\param amt Number of bytes to read.
|
||||||
|
\throws lw::HTTPMalformed if the HTTP is malformed in any way.
|
||||||
|
\returns Number of bytes parsed. If it returns less than the number
|
||||||
|
of bytes given, it has reached the end.
|
||||||
|
|
||||||
|
*/
|
||||||
|
size_t parse(const char* data, size_t amt);
|
||||||
|
|
||||||
|
/// Returns true if parsing is complete.
|
||||||
|
bool completed() const { return state == StateDone; }
|
||||||
|
|
||||||
|
/// Returns true if parsing failed.
|
||||||
|
bool failed() const { return state == StateError; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief HTTP response parser.
|
||||||
|
|
||||||
|
This class parses HTTP responses. It uses an HTTPHeaderParser internally
|
||||||
|
to deal with the header fields, and has code for dealing with the first
|
||||||
|
line of the HTTP response.
|
||||||
|
|
||||||
|
\warning ASCII only; see also HTTPHeaderParser.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class HTTPResponseParser {
|
||||||
|
private:
|
||||||
|
HTTPHeaderParser hp;
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
StateHTTPSeq,
|
||||||
|
StateHTTPMajor1,
|
||||||
|
StateHTTPMajor,
|
||||||
|
StateHTTPMinor1,
|
||||||
|
StateHTTPMinor,
|
||||||
|
StateHTTPResp1,
|
||||||
|
StateHTTPResp2,
|
||||||
|
StateHTTPResp3,
|
||||||
|
StateHTTPResp4,
|
||||||
|
StateHTTPMsg,
|
||||||
|
StateHTTPNewline,
|
||||||
|
StateHeader,
|
||||||
|
StateDone,
|
||||||
|
StateError
|
||||||
|
}state;
|
||||||
|
|
||||||
|
static const char* HTTPSeq;
|
||||||
|
const char* HTTPSeqPos;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
HTTPResponseParser();
|
||||||
|
|
||||||
|
/// HTTP major version number.
|
||||||
|
int versionMajor;
|
||||||
|
|
||||||
|
/// HTTP minor version number.
|
||||||
|
int versionMinor;
|
||||||
|
|
||||||
|
/// HTTP 3-digit response code.
|
||||||
|
int responseCode;
|
||||||
|
|
||||||
|
/// HTTP status message.
|
||||||
|
std::string status;
|
||||||
|
|
||||||
|
/// HTTP header fields.
|
||||||
|
std::map<std::string, std::string>& fields;
|
||||||
|
|
||||||
|
/*! \brief Parse incoming data.
|
||||||
|
|
||||||
|
\param data Pointer to character data.
|
||||||
|
\param amt Number of bytes to read.
|
||||||
|
\throws lw::HTTPMalformed if the HTTP is malformed in any way.
|
||||||
|
\returns Number of bytes parsed. If it returns less than the number
|
||||||
|
of bytes given, it has reached the end.
|
||||||
|
|
||||||
|
*/
|
||||||
|
size_t parse(const char* data, size_t amt);
|
||||||
|
|
||||||
|
/// Returns true if parsing is complete.
|
||||||
|
bool completed() const { return state == StateDone; }
|
||||||
|
|
||||||
|
/// Returns true if parsing failed.
|
||||||
|
bool failed() const { return state == StateError; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief HTTP request parser.
|
||||||
|
|
||||||
|
This class parses HTTP requests. It uses an HTTPHeaderParser internally
|
||||||
|
to deal with the header fields, and has code for dealing with the first
|
||||||
|
line of the HTTP request.
|
||||||
|
|
||||||
|
\warning ASCII only; see also HTTPHeaderParser.
|
||||||
|
\warning URIs are simply copied verbatim and are not validated in any
|
||||||
|
way whatsoever.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class HTTPRequestParser {
|
||||||
|
private:
|
||||||
|
HTTPHeaderParser hp;
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
StateHTTPMethod1,
|
||||||
|
StateHTTPMethod,
|
||||||
|
StateHTTPUrl1,
|
||||||
|
StateHTTPUrl,
|
||||||
|
StateHTTPSeq,
|
||||||
|
StateHTTPMajor1,
|
||||||
|
StateHTTPMajor,
|
||||||
|
StateHTTPMinor1,
|
||||||
|
StateHTTPMinor,
|
||||||
|
StateHTTPNewline,
|
||||||
|
StateHeader,
|
||||||
|
StateDone,
|
||||||
|
StateError
|
||||||
|
}state;
|
||||||
|
|
||||||
|
static const char* HTTPSeq;
|
||||||
|
const char* HTTPSeqPos;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
HTTPRequestParser();
|
||||||
|
|
||||||
|
/// HTTP method (POST, GET, etc.).
|
||||||
|
std::string method;
|
||||||
|
|
||||||
|
/// URL
|
||||||
|
std::string url;
|
||||||
|
|
||||||
|
/// HTTP major version number.
|
||||||
|
int versionMajor;
|
||||||
|
|
||||||
|
/// HTTP minor version number.
|
||||||
|
int versionMinor;
|
||||||
|
|
||||||
|
/// HTTP header fields.
|
||||||
|
std::map<std::string, std::string>& fields;
|
||||||
|
|
||||||
|
/*! \brief Parse incoming data.
|
||||||
|
|
||||||
|
\param data Pointer to character data.
|
||||||
|
\param amt Number of bytes to read.
|
||||||
|
\throws lw::HTTPMalformed if the HTTP is malformed in any way.
|
||||||
|
\returns Number of bytes parsed. If it returns less than the number
|
||||||
|
of bytes given, it has reached the end.
|
||||||
|
|
||||||
|
*/
|
||||||
|
size_t parse(const char* data, size_t amt);
|
||||||
|
|
||||||
|
/// Returns true if parsing is complete.
|
||||||
|
bool completed() const { return state == StateDone; }
|
||||||
|
|
||||||
|
/// Returns true if parsing failed.
|
||||||
|
bool failed() const { return state == StateError; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,629 @@
|
||||||
|
/* lw-support/src/lib/Net/Protocols/SMTP.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const char* const SMTPClient::dayName[8] = {
|
||||||
|
"(?) ", "Mon ", "Tue ", "Wed ", "Thu ", "Fri ", "Sat ", "Sun "
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* const SMTPClient::monthName[13] = {
|
||||||
|
" (?) ", " Jan ", " Feb ", " Mar ", " Apr ", " May ", " Jun ",
|
||||||
|
" Jul ", " Aug ", " Sep ", " Oct ", " Nov ", " Dec "
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
size_t SMTPClient::parserFeedData(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
for(size_t used = 0; used < amt; ++used) {
|
||||||
|
char ch = data[used];
|
||||||
|
|
||||||
|
switch(parserState) {
|
||||||
|
case ParserStateDone:
|
||||||
|
throw std::wstring(L"Program logic error [1]");
|
||||||
|
|
||||||
|
case ParserStateNone:
|
||||||
|
if(!isdigit(ch)) throw std::wstring(L"Expecting SMTP reply code.");
|
||||||
|
parserCode = 100 * (ch - '0');
|
||||||
|
parserState = ParserStateCode1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateCode1:
|
||||||
|
if(!isdigit(ch)) throw std::wstring(L"Expecting SMTP reply code.");
|
||||||
|
parserCode += 10 * (ch - '0');
|
||||||
|
parserState = ParserStateCode2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateCode2:
|
||||||
|
if(!isdigit(ch)) throw std::wstring(L"Expecting SMTP reply code.");
|
||||||
|
parserCode += (ch - '0');
|
||||||
|
parserState = ParserStateSP;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateSP:
|
||||||
|
if(isspace(ch)) parserState = ParserStateData;
|
||||||
|
else if(ch == '-') parserState = ParserStateContData;
|
||||||
|
else throw std::wstring(L"Expecting ' ' or '-' after SMTP reply code.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateData:
|
||||||
|
if(ch == '\n') { // bad server!
|
||||||
|
parserState = ParserStateDone;
|
||||||
|
return used + 1;
|
||||||
|
}
|
||||||
|
if(ch == '\r') {
|
||||||
|
parserState = ParserStateLF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
parserData.append(&ch, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateLF:
|
||||||
|
if(ch != '\n') throw std::wstring(L"Expecting \\r\\n sequence.");
|
||||||
|
parserState = ParserStateDone;
|
||||||
|
return used + 1;
|
||||||
|
|
||||||
|
case ParserStateContData:
|
||||||
|
if(ch == '\n') parserState = ParserStateContCode0;
|
||||||
|
else if(ch == '\r') parserState = ParserStateContLF;
|
||||||
|
else parserData.append(&ch, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateContLF:
|
||||||
|
if(ch != '\n') throw std::wstring(L"Expecting \\r\\n sequence.");
|
||||||
|
parserState = ParserStateContCode0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateContCode0:
|
||||||
|
if((parserCode / 100) != (ch - '0'))
|
||||||
|
throw std::wstring(L"Mismatched multi-line SMTP reply code.");
|
||||||
|
parserState = ParserStateContCode1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateContCode1:
|
||||||
|
if(((parserCode % 100) / 10) != (ch - '0'))
|
||||||
|
throw std::wstring(L"Mismatched multi-line SMTP reply code.");
|
||||||
|
parserState = ParserStateContCode2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ParserStateContCode2:
|
||||||
|
if((parserCode % 10) != (ch - '0'))
|
||||||
|
throw std::wstring(L"Mismatched multi-line SMTP reply code.");
|
||||||
|
parserState = ParserStateSP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::parserReset()
|
||||||
|
{
|
||||||
|
parserState = ParserStateNone;
|
||||||
|
parserCode = 0;
|
||||||
|
parserData.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool SMTPClient::ioReady(uint32_t flags)
|
||||||
|
{
|
||||||
|
if(flags & (IOEventUrgent | IOEventHUP)) {
|
||||||
|
state = StateDone;
|
||||||
|
logFail(L"Urgent data received / remote hangup");
|
||||||
|
return IOListenResultDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(flags & IOEventError) netClient.checkErrors();
|
||||||
|
|
||||||
|
while(tryRead() | tryWrite())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
logFail(e);
|
||||||
|
delete this;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch(std::wstring& e) {
|
||||||
|
logFail(e);
|
||||||
|
delete this;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(state == StateDone) {
|
||||||
|
delete this;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool SMTPClient::tryRead() // returns true if I/O happens
|
||||||
|
{
|
||||||
|
bool ioHappened = false;
|
||||||
|
while(true) {
|
||||||
|
size_t amt = inBuffer.getRemaining();
|
||||||
|
amt = netClient.read(inBuffer.getBuffer(amt), amt);
|
||||||
|
inBuffer.addedData(amt);
|
||||||
|
if(!amt) return ioHappened;
|
||||||
|
ioHappened = true;
|
||||||
|
logRead(inBuffer.getData(), inBuffer.getSize());
|
||||||
|
|
||||||
|
while(!inBuffer.isEmpty()) {
|
||||||
|
// parse data
|
||||||
|
inBuffer.getData(parserFeedData(inBuffer.getData(),
|
||||||
|
inBuffer.getSize()));
|
||||||
|
if(parserState != ParserStateDone) break;
|
||||||
|
|
||||||
|
#define FAIL() do { \
|
||||||
|
logFail(parserCode, parserData); \
|
||||||
|
state = StateDone; \
|
||||||
|
return true; \
|
||||||
|
}while(0)
|
||||||
|
|
||||||
|
#define WRITE(x) do { \
|
||||||
|
writePtr = x.c_str(); \
|
||||||
|
writeAmt = x.size(); \
|
||||||
|
}while(0)
|
||||||
|
|
||||||
|
switch(state) {
|
||||||
|
case StateDone:
|
||||||
|
throw std::wstring(L"Program logic error [2].");
|
||||||
|
|
||||||
|
case StateNone:
|
||||||
|
if((parserCode / 10) != 22) FAIL();
|
||||||
|
else {
|
||||||
|
state = StateHelo;
|
||||||
|
outBuffer = "helo ";
|
||||||
|
outBuffer.append(hostName);
|
||||||
|
outBuffer.append("\r\n");
|
||||||
|
WRITE(outBuffer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateHelo:
|
||||||
|
if((parserCode / 10) != 25) FAIL();
|
||||||
|
else {
|
||||||
|
state = StateMailFrom;
|
||||||
|
outBuffer = "mail from: ";
|
||||||
|
outBuffer.append(mailFrom);
|
||||||
|
outBuffer.append("\r\n");
|
||||||
|
WRITE(outBuffer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateMailFrom:
|
||||||
|
if((parserCode / 10) != 25) FAIL();
|
||||||
|
else {
|
||||||
|
state = StateRcptTo;
|
||||||
|
outBuffer = "rcpt to: ";
|
||||||
|
outBuffer.append(rcptTo);
|
||||||
|
outBuffer.append("\r\n");
|
||||||
|
WRITE(outBuffer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateRcptTo:
|
||||||
|
if((parserCode / 10) != 25) FAIL();
|
||||||
|
else {
|
||||||
|
state = StateData;
|
||||||
|
outBuffer = "data\r\n";
|
||||||
|
WRITE(outBuffer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateData:
|
||||||
|
if((parserCode / 10) != 35) FAIL();
|
||||||
|
else {
|
||||||
|
state = StateQuit;
|
||||||
|
WRITE(data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateQuit:
|
||||||
|
if((parserCode / 10) != 25) FAIL();
|
||||||
|
else {
|
||||||
|
state = StateFinal;
|
||||||
|
outBuffer = "quit\r\n";
|
||||||
|
WRITE(outBuffer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StateFinal:
|
||||||
|
if((parserCode / 10) != 22) FAIL();
|
||||||
|
else {
|
||||||
|
state = StateDone;
|
||||||
|
logDone();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef WRITE
|
||||||
|
#undef FAIL
|
||||||
|
|
||||||
|
// reset parser and go again
|
||||||
|
parserReset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool SMTPClient::tryWrite()
|
||||||
|
{
|
||||||
|
bool ioHappened = false;
|
||||||
|
if(!writePtr) return false;
|
||||||
|
|
||||||
|
while(writeAmt) {
|
||||||
|
size_t amt = netClient.write(writePtr, writeAmt);
|
||||||
|
if(!amt) return ioHappened;
|
||||||
|
ioHappened = true;
|
||||||
|
|
||||||
|
logWrite(writePtr, amt);
|
||||||
|
writeAmt -= amt;
|
||||||
|
writePtr += amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
writePtr = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool SMTPClient::isWhitespace(char ch)
|
||||||
|
{
|
||||||
|
return (ch >= 0x9 && ch <= 0xD) || ch == 0x20;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string SMTPClient::wsFieldName(const std::string& fieldName)
|
||||||
|
{
|
||||||
|
// strip whitespace from beginning and end
|
||||||
|
std::string::size_type pos, len = fieldName.length();
|
||||||
|
for(pos = 0; pos < len; ++pos) if(!isWhitespace(fieldName[pos])) break;
|
||||||
|
if(pos == len) return std::string();
|
||||||
|
|
||||||
|
std::wstring::size_type endPos = len - 1;
|
||||||
|
while(isWhitespace(fieldName[endPos])) --endPos;
|
||||||
|
|
||||||
|
std::string str = fieldName.substr(pos, endPos - pos + 1);
|
||||||
|
|
||||||
|
// return empty string if there are any illegal chars, and convert
|
||||||
|
// to lowercase
|
||||||
|
for(pos = 0, len = str.length(); pos < len; ++pos) {
|
||||||
|
if(str[pos] <= 0x20 || str[pos] & ~0x7F) return std::string();
|
||||||
|
str[pos] = tolower(str[pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string SMTPClient::wsFieldValue(const std::string& fieldValue)
|
||||||
|
{
|
||||||
|
// strip whitespace from beginning and end
|
||||||
|
std::string::size_type pos, len = fieldValue.length();
|
||||||
|
for(pos = 0; pos < len; ++pos) if(!isWhitespace(fieldValue[pos])) break;
|
||||||
|
if(pos == len) return std::string();
|
||||||
|
|
||||||
|
std::wstring::size_type endPos = len - 1;
|
||||||
|
while(isWhitespace(fieldValue[endPos])) --endPos;
|
||||||
|
|
||||||
|
std::string str = fieldValue.substr(pos, endPos - pos + 1);
|
||||||
|
|
||||||
|
// convert remaining whitespace
|
||||||
|
for(pos = 0, len = str.length(); pos < len; ++pos)
|
||||||
|
if(str[pos] == '\r' || str[pos] == '\n')
|
||||||
|
str[pos] = ' ';
|
||||||
|
|
||||||
|
// done
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string SMTPClient::buildRFC2822(const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message)
|
||||||
|
{
|
||||||
|
(void)hostName; // TODO: add message-id
|
||||||
|
std::ostringstream o;
|
||||||
|
|
||||||
|
// first, deal with headers (sanitise as we go)
|
||||||
|
bool seenDate = false, seenFrom = false, seenTo = false;
|
||||||
|
|
||||||
|
std::multimap<std::string, std::string>::const_iterator
|
||||||
|
iter = headerLines.begin(), end = headerLines.end();
|
||||||
|
for(; iter != end; ++iter) {
|
||||||
|
std::string fieldName = wsFieldName(iter->first);
|
||||||
|
std::string fieldValue = wsFieldValue(iter->second);
|
||||||
|
|
||||||
|
if(fieldName.empty()) continue;
|
||||||
|
if(fieldName == "date") seenDate = true;
|
||||||
|
if(fieldName == "from") seenFrom = true;
|
||||||
|
if(fieldName == "to") seenTo = true;
|
||||||
|
if(fieldValue.empty()) continue;
|
||||||
|
|
||||||
|
o << fieldName << ": " << fieldValue << "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!seenDate) {
|
||||||
|
lw::DateTime date = lw::DateTime::now();
|
||||||
|
o << std::setfill('0')
|
||||||
|
<< "Date: "
|
||||||
|
<< dayName[date.getDate().getWeekDay()]
|
||||||
|
<< date.getDate().getDay()
|
||||||
|
<< monthName[date.getDate().getMonth()]
|
||||||
|
<< date.getDate().getYear()
|
||||||
|
<< ' '
|
||||||
|
<< std::setw(2) << date.getTime().h() << ':'
|
||||||
|
<< std::setw(2) << date.getTime().m() << ':'
|
||||||
|
<< std::setw(2) << date.getTime().s()
|
||||||
|
<< " +0000" // FIXME
|
||||||
|
<< "\r\n"
|
||||||
|
<< std::setfill(' ');
|
||||||
|
}
|
||||||
|
if(!seenFrom) o << "From: " << wsFieldValue(mailFrom) << "\r\n";
|
||||||
|
if(!seenTo) o << "To: " << wsFieldValue(rcptTo) << "\r\n";
|
||||||
|
|
||||||
|
o << "\r\n";
|
||||||
|
|
||||||
|
// now output the escaped message
|
||||||
|
bool wasNewline = true;
|
||||||
|
for(std::string::size_type pos = 0, len = message.size(); pos < len; ++pos) {
|
||||||
|
char ch = message[pos];
|
||||||
|
switch(ch) {
|
||||||
|
case '\r':
|
||||||
|
ch = ' ';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
o << "\r\n";
|
||||||
|
wasNewline = true;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
if(wasNewline) o.put('.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
o.put(ch);
|
||||||
|
wasNewline = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
o << "\r\n.\r\n";
|
||||||
|
|
||||||
|
// done
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SMTPClient::SMTPClient(const NetAddress& netAddress, uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, CompletionNotifier* completionNotifier)
|
||||||
|
: completionNotifier(completionNotifier),
|
||||||
|
hostName(wsFieldValue(hostName)),
|
||||||
|
mailFrom(wsFieldValue(mailFrom)),
|
||||||
|
rcptTo(wsFieldValue(rcptTo)),
|
||||||
|
data(buildRFC2822(hostName, mailFrom, rcptTo, headerLines, message)),
|
||||||
|
state(StateNone), inBuffer(1024, 1024), writePtr(0), writeAmt(0),
|
||||||
|
parserState(ParserStateNone), parserCode(0),
|
||||||
|
port(port), netAddress(netAddress.clone())
|
||||||
|
{
|
||||||
|
// NOTE: we can't use any of the log* functions in the constructor,
|
||||||
|
// because the virtual functions won't be pointing to the derived
|
||||||
|
// class yet. We'll have to wait until we're in the I/O callback to
|
||||||
|
// log anything.
|
||||||
|
|
||||||
|
// set up client
|
||||||
|
netClient.connect(netAddress, port);
|
||||||
|
eventManager.registerDevice(&netClient, this, ~0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SMTPClient::~SMTPClient()
|
||||||
|
{
|
||||||
|
if(completionNotifier) completionNotifier->signal();
|
||||||
|
delete netAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::sendMail(const NetAddress& netAddress, uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, CompletionNotifier* completionNotifier)
|
||||||
|
{
|
||||||
|
new SMTPClient(netAddress, port, eventManager, hostName,
|
||||||
|
mailFrom, rcptTo, headerLines, message, completionNotifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::logStart(const std::string&,
|
||||||
|
const std::string&, const std::string&,
|
||||||
|
const std::string&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::logDone()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::logRead(const char*, size_t)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::logWrite(const char*, size_t)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::logFail(int, const std::string&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::logFail(Exception&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void SMTPClient::logFail(const std::wstring&)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int LoggingSMTPClient::instanceCount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LoggingSMTPClient::LoggingSMTPClient(const NetAddress& netAddress,
|
||||||
|
uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, Log& logger, Log& logError,
|
||||||
|
CompletionNotifier* completionNotifier)
|
||||||
|
: SMTPClient(netAddress, port, eventManager, hostName, mailFrom, rcptTo,
|
||||||
|
headerLines, message, completionNotifier),
|
||||||
|
logger(logger), logError(logError)
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
o << L"SMTP <"
|
||||||
|
<< ++instanceCount
|
||||||
|
<< L">, "
|
||||||
|
<< netAddress.toString()
|
||||||
|
<< L" port "
|
||||||
|
<< port;
|
||||||
|
logID = o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LoggingSMTPClient::~LoggingSMTPClient()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::sendMail(const NetAddress& netAddress,
|
||||||
|
uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, Log& logger, Log& logError,
|
||||||
|
CompletionNotifier* completionNotifier)
|
||||||
|
{
|
||||||
|
new LoggingSMTPClient(netAddress, port, eventManager, hostName,
|
||||||
|
mailFrom, rcptTo, headerLines, message,
|
||||||
|
logger, logError, completionNotifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::logStart(const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::string& data)
|
||||||
|
{
|
||||||
|
logger << Log::start(logID)
|
||||||
|
<< L"Sending a new email:"
|
||||||
|
<< L"\n Host: " << hostName
|
||||||
|
<< L"\n From: " << mailFrom
|
||||||
|
<< L"\n To : " << rcptTo
|
||||||
|
<< L"\n\n"
|
||||||
|
<< Log::asciiDump(data.c_str(), data.size())
|
||||||
|
<< Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::logDone()
|
||||||
|
{
|
||||||
|
logger << Log::start(logID)
|
||||||
|
<< L"Email sent successfully."
|
||||||
|
<< Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::logRead(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
logger << Log::start(logID)
|
||||||
|
<< L"Incoming data.\n\n"
|
||||||
|
<< Log::asciiDump(data, amt)
|
||||||
|
<< Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::logWrite(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
logger << Log::start(logID)
|
||||||
|
<< L"Wrote data.\n\n"
|
||||||
|
<< Log::asciiDump(data, amt)
|
||||||
|
<< Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::logFail(int smtpCode, const std::string& errorString)
|
||||||
|
{
|
||||||
|
logError << Log::start(logID)
|
||||||
|
<< L"SMTP error."
|
||||||
|
L"\n Code : " << smtpCode
|
||||||
|
<< L"\n Reason: " << errorString
|
||||||
|
<< Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::logFail(Exception& e)
|
||||||
|
{
|
||||||
|
logError << Log::start(logID)
|
||||||
|
<< L"System error.\n"
|
||||||
|
<< e
|
||||||
|
<< Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LoggingSMTPClient::logFail(const std::wstring& s)
|
||||||
|
{
|
||||||
|
logError << Log::start(logID)
|
||||||
|
<< L"Communication failed: "
|
||||||
|
<< s
|
||||||
|
<< Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,287 @@
|
||||||
|
/* lw-support/src/lib/Net/Protocols/SMTP.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief An SMTP (email) client class.
|
||||||
|
|
||||||
|
This class can be used to perform an SMTP transaction. It will send one
|
||||||
|
email. It is used in an asynchronous manner, and can (optionally) signal
|
||||||
|
completion through a CompletionNotifier object.
|
||||||
|
|
||||||
|
\warning You should only send ASCII characters through this client, but
|
||||||
|
it does not perform any checking to enforce this.
|
||||||
|
|
||||||
|
\todo Timeout options.
|
||||||
|
\todo Split SMTP response parser into its own class.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class SMTPClient : private EventCallbackIO {
|
||||||
|
private:
|
||||||
|
// for performing I/O
|
||||||
|
CompletionNotifier* completionNotifier;
|
||||||
|
NetClientTCP netClient;
|
||||||
|
virtual bool ioReady(uint32_t flags);
|
||||||
|
bool tryRead();
|
||||||
|
bool tryWrite();
|
||||||
|
|
||||||
|
// data
|
||||||
|
const std::string hostName, mailFrom, rcptTo, data;
|
||||||
|
|
||||||
|
// builds an RFC2822-compliant message
|
||||||
|
static const char* const dayName[8];
|
||||||
|
static const char* const monthName[13];
|
||||||
|
static std::string buildRFC2822(const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message);
|
||||||
|
|
||||||
|
static bool isWhitespace(char ch);
|
||||||
|
static std::string wsFieldName(const std::string& fieldName);
|
||||||
|
static std::string wsFieldValue(const std::string& fieldValue);
|
||||||
|
|
||||||
|
// transaction state
|
||||||
|
enum State {
|
||||||
|
StateNone,
|
||||||
|
StateHelo,
|
||||||
|
StateMailFrom,
|
||||||
|
StateRcptTo,
|
||||||
|
StateData,
|
||||||
|
StateQuit,
|
||||||
|
StateFinal,
|
||||||
|
StateDone
|
||||||
|
}state;
|
||||||
|
FIFO inBuffer;
|
||||||
|
std::string outBuffer;
|
||||||
|
const char* writePtr;
|
||||||
|
size_t writeAmt;
|
||||||
|
|
||||||
|
// RFC 2821 parser
|
||||||
|
size_t parserFeedData(const char* data, size_t amt);
|
||||||
|
void parserReset();
|
||||||
|
enum ParserState {
|
||||||
|
ParserStateNone,
|
||||||
|
ParserStateCode1,
|
||||||
|
ParserStateCode2,
|
||||||
|
ParserStateSP,
|
||||||
|
ParserStateData,
|
||||||
|
ParserStateLF,
|
||||||
|
ParserStateContData,
|
||||||
|
ParserStateContLF,
|
||||||
|
ParserStateContCode0,
|
||||||
|
ParserStateContCode1,
|
||||||
|
ParserStateContCode2,
|
||||||
|
ParserStateDone
|
||||||
|
}parserState;
|
||||||
|
int parserCode;
|
||||||
|
std::string parserData;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// TCP port to connect to.
|
||||||
|
uint16_t port;
|
||||||
|
|
||||||
|
/// Host to connect to.
|
||||||
|
NetAddress* netAddress;
|
||||||
|
|
||||||
|
/// Protected constructor for "fire-and-forget".
|
||||||
|
SMTPClient(const NetAddress& netAddress, uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, CompletionNotifier* completionNotifier);
|
||||||
|
|
||||||
|
/// Log the start of a new message.
|
||||||
|
virtual void logStart(const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::string& data);
|
||||||
|
|
||||||
|
/// Log message complete.
|
||||||
|
virtual void logDone();
|
||||||
|
|
||||||
|
/// Log a data transfer (input).
|
||||||
|
virtual void logRead(const char* data, size_t amt);
|
||||||
|
|
||||||
|
/// Log a data transfer (output).
|
||||||
|
virtual void logWrite(const char* data, size_t amt);
|
||||||
|
|
||||||
|
/// Log an SMTP error.
|
||||||
|
virtual void logFail(int smtpCode, const std::string& errorString);
|
||||||
|
|
||||||
|
/// Log a connection error.
|
||||||
|
virtual void logFail(Exception& e);
|
||||||
|
|
||||||
|
/// Log a communications error.
|
||||||
|
virtual void logFail(const std::wstring& s);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~SMTPClient();
|
||||||
|
|
||||||
|
/*! \brief Begins sending email.
|
||||||
|
|
||||||
|
\param netAddress The address of the host to connect to.
|
||||||
|
\param port The TCP port number of the SMTP service.
|
||||||
|
\param eventManager The event manager to use.
|
||||||
|
\param hostName The name of the host to present to the SMTP server.
|
||||||
|
\param mailFrom Email address of the (SMTP envelope) sender.
|
||||||
|
\param rcptTo Email address of the (SMTP envelope) receiver.
|
||||||
|
\param headerLines The header lines to use in the email. These will
|
||||||
|
be properly escaped, whitespace stripped, etc.
|
||||||
|
\param message The actual message to send. It will be properly
|
||||||
|
escaped.
|
||||||
|
\param completionNotifier The CompletionNotifier object to use to
|
||||||
|
signal when the process is complete. May be left as 0 in which
|
||||||
|
case no signal will be issued.
|
||||||
|
\throws Exception if a system error occurs.
|
||||||
|
|
||||||
|
The constructor will create a properly-formatted email, and begin
|
||||||
|
connecting to the SMTP server.
|
||||||
|
|
||||||
|
Several standard header lines will be automatically filled out for
|
||||||
|
you. You may override this by providing values for them in the
|
||||||
|
\a headerLines parameter. These headers are:
|
||||||
|
- \a orig-date (set to current date).
|
||||||
|
- \a from (set to the SMTP envelope \a mailFrom).
|
||||||
|
- \a to (set to the SMTP envelope \a rcptTo).
|
||||||
|
|
||||||
|
Blank value strings in the \a headerLines array will cause the
|
||||||
|
corresponding header line \e not to be generated. Blank key strings
|
||||||
|
will be ignored. Whitespace will be stripped from all key strings.
|
||||||
|
Key strings are case insensitive.
|
||||||
|
|
||||||
|
Rules for whitespace handling are:
|
||||||
|
- all ASCII CR characters are replaced with an ASCII space;
|
||||||
|
- all ASCII LF characters in header fields are replaced with an
|
||||||
|
ASCII space;
|
||||||
|
- any remaining whitespace in field names causes the field to be
|
||||||
|
skipped;
|
||||||
|
- otherwise, whitespace is left verbatim.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static void sendMail(const NetAddress& netAddress, uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, CompletionNotifier* completionNotifier);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief A logging SMTP (email) client class.
|
||||||
|
|
||||||
|
This class can be used to perform an SMTP transaction. It will send one
|
||||||
|
email. It is used in a "fire and forget" manner, and there is no way to
|
||||||
|
determine the outcome of sending an email (except through the logging
|
||||||
|
functions).
|
||||||
|
|
||||||
|
This class will log its actions to the specified log objects.
|
||||||
|
|
||||||
|
\warning You should only send ASCII characters through this client, but
|
||||||
|
it does not perform any checking to enforce this.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class LoggingSMTPClient : public SMTPClient {
|
||||||
|
private:
|
||||||
|
static int instanceCount;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// This email's unique ID, for logging.
|
||||||
|
std::wstring logID;
|
||||||
|
|
||||||
|
/// Log object.
|
||||||
|
Log& logger, & logError;
|
||||||
|
|
||||||
|
/// Protected constructor for "fire-and-forget".
|
||||||
|
LoggingSMTPClient(const NetAddress& netAddress, uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, Log& logger, Log& logError,
|
||||||
|
CompletionNotifier* completionNotifier);
|
||||||
|
|
||||||
|
/// Log the start of a new message.
|
||||||
|
virtual void logStart(const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::string& data);
|
||||||
|
|
||||||
|
/// Log message complete.
|
||||||
|
virtual void logDone();
|
||||||
|
|
||||||
|
/// Log a data transfer (input).
|
||||||
|
virtual void logRead(const char* data, size_t amt);
|
||||||
|
|
||||||
|
/// Log a data transfer (output).
|
||||||
|
virtual void logWrite(const char* data, size_t amt);
|
||||||
|
|
||||||
|
/// Log an SMTP error.
|
||||||
|
virtual void logFail(int smtpCode, const std::string& errorString);
|
||||||
|
|
||||||
|
/// Log a connection error.
|
||||||
|
virtual void logFail(Exception& e);
|
||||||
|
|
||||||
|
/// Log a communications error.
|
||||||
|
virtual void logFail(const std::wstring& s);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~LoggingSMTPClient();
|
||||||
|
|
||||||
|
/*! \brief Begins sending email, with logging.
|
||||||
|
|
||||||
|
\param netAddress The address of the host to connect to.
|
||||||
|
\param port The TCP port number of the SMTP service.
|
||||||
|
\param eventManager The event manager to use.
|
||||||
|
\param hostName The name of the host to present to the SMTP server.
|
||||||
|
\param mailFrom Email address of the (SMTP envelope) sender.
|
||||||
|
\param rcptTo Email address of the (SMTP envelope) receiver.
|
||||||
|
\param headerLines The header lines to use in the email. These will
|
||||||
|
be properly escaped, whitespace stripped, etc.
|
||||||
|
\param message The actual message to send. It will be properly
|
||||||
|
escaped.
|
||||||
|
\param logger The lw::Log object to use.
|
||||||
|
\param logError The lw::Log object to use to log errors.
|
||||||
|
\param completionNotifier The CompletionNotifier object to use to
|
||||||
|
signal when the process is complete. May be left as 0 in which
|
||||||
|
case no signal will be issued.
|
||||||
|
\throws Exception if a system error occurs.
|
||||||
|
|
||||||
|
The constructor will create a properly-formatted email, and begin
|
||||||
|
connecting to the SMTP server.
|
||||||
|
|
||||||
|
Several standard header lines will be automatically filled out for
|
||||||
|
you. You may override this by providing values for them in the
|
||||||
|
\a headerLines parameter. These headers are:
|
||||||
|
- \a orig-date (set to current date).
|
||||||
|
- \a from (set to the SMTP envelope \a mailFrom).
|
||||||
|
- \a to (set to the SMTP envelope \a rcptTo).
|
||||||
|
|
||||||
|
Blank value strings in the \a headerLines array will cause the
|
||||||
|
corresponding header line \e not to be generated. Blank key strings
|
||||||
|
will be ignored. Whitespace will be stripped from all key strings.
|
||||||
|
Key strings are case insensitive.
|
||||||
|
|
||||||
|
Rules for whitespace handling are:
|
||||||
|
- all ASCII CR characters are replaced with an ASCII space;
|
||||||
|
- all ASCII LF characters in header fields are replaced with an
|
||||||
|
ASCII space;
|
||||||
|
- any remaining whitespace in field names causes the field to be
|
||||||
|
skipped;
|
||||||
|
- otherwise, whitespace is left verbatim.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static void sendMail(const NetAddress& netAddress, uint16_t port,
|
||||||
|
EventManager& eventManager, const std::string& hostName,
|
||||||
|
const std::string& mailFrom, const std::string& rcptTo,
|
||||||
|
const std::multimap<std::string, std::string>& headerLines,
|
||||||
|
const std::string& message, Log& logger, Log& logError,
|
||||||
|
CompletionNotifier* completionNotifier);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
/* lw-support/src/lib/Net/Server.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetServer::NetServer()
|
||||||
|
: nsCallback(0), eventManager(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetServer::~NetServer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetServer::Protocol NetServer::getProtocol(const NetAddress& addr)
|
||||||
|
{
|
||||||
|
const NetAddress* a = &addr;
|
||||||
|
if(dynamic_cast<const NetAddressIPv4*>(a)) return IPv4;
|
||||||
|
if(dynamic_cast<const NetAddressIPv6*>(a)) return IPv6;
|
||||||
|
throw UnknownProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetServer::registerCallback(EventManager& eventManager,
|
||||||
|
EventCallbackNetServer& callback)
|
||||||
|
{
|
||||||
|
clearCallback();
|
||||||
|
nsCallback = &callback;
|
||||||
|
this->eventManager = &eventManager;
|
||||||
|
eventManager.registerDevice(this, this, IOEventRead | IOEventError);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetServer::clearCallback()
|
||||||
|
{
|
||||||
|
if(eventManager) eventManager->ignoreDevice(this);
|
||||||
|
eventManager = 0;
|
||||||
|
nsCallback = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetServer::ioReady(uint32_t flags) throw()
|
||||||
|
{
|
||||||
|
// TODO -- this really needs sorting somehow, since we no longer support
|
||||||
|
// error handling in the event loop
|
||||||
|
#if 0
|
||||||
|
// deal with errors
|
||||||
|
try {
|
||||||
|
if(flags & IOEventError) if(!nsCallback->error()) return false;
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
|
||||||
|
// deal with waiting connections
|
||||||
|
if(flags & IOEventRead) {
|
||||||
|
IODevice* netClient = 0;
|
||||||
|
while(true) {
|
||||||
|
if(!(netClient = accept())) break;
|
||||||
|
try {
|
||||||
|
if(!nsCallback->accept(netClient)) return false;
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetServer::readBlock(int timeout, IOTimeoutMode timeoutMode)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
int timeout_remaining = timeout;
|
||||||
|
struct pollfd ufd = { fd, POLLIN, 0 };
|
||||||
|
IOCountdown countdown(this, L"readBlock()", timeout, timeoutMode);
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
switch(poll(&ufd, 1, timeout_remaining)) {
|
||||||
|
case -1:
|
||||||
|
if(errno == EINTR) break;
|
||||||
|
throw SystemError().chain(L"poll()");
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
throw IOTimeout(this, L"acceptBlocking", timeout, timeoutMode, 0);
|
||||||
|
|
||||||
|
default:
|
||||||
|
// perform read
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update timeout
|
||||||
|
if(!countdown.update(timeout_remaining))
|
||||||
|
throw IOTimeout(this, L"acceptBlocking", timeout, timeoutMode, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"IOPosixDevice::readBlock()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,155 @@
|
||||||
|
/* lw-support/src/lib/Net/Server.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Base class for all network servers.
|
||||||
|
|
||||||
|
This is the base class for any network server. It provides two main
|
||||||
|
functions: you can call accept() (non-blocking) to accept any waiting
|
||||||
|
connection; or you can register a callback with an EventManager. Your
|
||||||
|
callback will get error and new connection events.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NetServer
|
||||||
|
: protected IOPosixDevice,
|
||||||
|
private EventCallbackIO
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// callback stuff
|
||||||
|
EventCallbackNetServer* nsCallback;
|
||||||
|
EventManager* eventManager;
|
||||||
|
virtual void ioReady(uint32_t flags) throw(); // from EventCallbackIO()
|
||||||
|
|
||||||
|
//BEGIN // Protocol-specific implementation ////////////////////////
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Exception used for an unknown protocol.
|
||||||
|
class UnknownProtocol : public ProgramException { };
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Enumeration representing understood protocols.
|
||||||
|
enum Protocol {
|
||||||
|
IPv4,
|
||||||
|
IPv6
|
||||||
|
}protocol;
|
||||||
|
|
||||||
|
/*! \brief Get an address's protocol type.
|
||||||
|
|
||||||
|
\param addr The address to inquire about.
|
||||||
|
\throws UnknownProtocol if the protocol is entirely unknown.
|
||||||
|
\returns The type of the address.
|
||||||
|
|
||||||
|
This function can be used to ask what type of protocol an address
|
||||||
|
refers to. This isn't a virtual function in NetAddress because all
|
||||||
|
the behaviour changing functionality is (for the time being at
|
||||||
|
least) going to be in this class.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static Protocol getProtocol(const NetAddress& addr);
|
||||||
|
|
||||||
|
/*! \brief Block until there's a connection available.
|
||||||
|
|
||||||
|
\param timeout Timeout period, in milliseconds. 0 means immediate;
|
||||||
|
negative means forever.
|
||||||
|
\param timeoutMode The timeout mode.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOTimeout if a timeout occurs.
|
||||||
|
\returns A newly-allocated I/O device object.
|
||||||
|
|
||||||
|
This function can be called from within acceptBlocking(). Its purpose is to aid the
|
||||||
|
implementation of that function. It will wait until there is data available to read on the
|
||||||
|
associated fd.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void readBlock(int timeout = -1, IOTimeoutMode timeoutMode = IOTimeoutHardFull);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Constructors etc. ///////////////////////////////////////
|
||||||
|
|
||||||
|
/// Constructor. Does nothing.
|
||||||
|
NetServer();
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~NetServer();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Callback system /////////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Called to retrieve a connection (non-blocking).
|
||||||
|
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\returns A newly-allocated I/O device object.
|
||||||
|
\retval 0 if there is no connection waiting.
|
||||||
|
|
||||||
|
This function is called whenever you want to accept a new
|
||||||
|
connection. It either creates a new I/O device object or returns 0
|
||||||
|
if there are no connections waiting.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual lw::NetClient* accept() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Called to retrieve a connection (blocking).
|
||||||
|
|
||||||
|
\param timeout Timeout period, in milliseconds. 0 means immediate;
|
||||||
|
negative means forever.
|
||||||
|
\param timeoutMode The timeout mode.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws IOTimeout if a timeout occurs.
|
||||||
|
\returns A newly-allocated I/O device object.
|
||||||
|
|
||||||
|
This function is called whenever you want to accept a new connection. It creates a new I/O
|
||||||
|
device object.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual lw::NetClient* acceptBlocking(int timeout = -1,
|
||||||
|
IOTimeoutMode timeoutMode = IOTimeoutHardFull) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Registers object with an event manager.
|
||||||
|
|
||||||
|
\param eventManager The event manager object.
|
||||||
|
\param callback The NetServerCallback object that will receive
|
||||||
|
events for this server.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
This function will register your callback object with an event
|
||||||
|
manager. Only one callback can be active at once; calling this
|
||||||
|
function with a callback already set will simply clear the old
|
||||||
|
callback.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void registerCallback(EventManager& eventManager,
|
||||||
|
EventCallbackNetServer& callback);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Stops callbacks.
|
||||||
|
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
If you want to stop generating callback events, call this function.
|
||||||
|
Alternatively, you can return \a false in the callback
|
||||||
|
|
||||||
|
*/
|
||||||
|
void clearCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,18 @@
|
||||||
|
/* lw-support/src/lib/Net/ServerCallback.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool EventCallbackNetServer::error()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* lw-support/src/lib/Net/ServerCallback.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Base class for network server callback objects.
|
||||||
|
|
||||||
|
This is the callback objected to be used with IOListen when you want to
|
||||||
|
listen for incoming connections on a new server object. You simply need
|
||||||
|
to override the accept() function (and possibly the error() function);
|
||||||
|
everything else is ready to work with any object derived from the
|
||||||
|
NetServer class.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class EventCallbackNetServer {
|
||||||
|
public:
|
||||||
|
/// Destructor. Does nothing.
|
||||||
|
virtual ~EventCallbackNetServer()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Called when a new connection is accepted.
|
||||||
|
|
||||||
|
\param client The new connection object.
|
||||||
|
\retval true if you want to continue listening.
|
||||||
|
\retval false if you want to stop listening.
|
||||||
|
|
||||||
|
This function is called whenever the acceptance process for a new
|
||||||
|
socket is complete. This needs to be overridden in derived classes
|
||||||
|
to do something with \a client (such as add it to a global ready
|
||||||
|
list).
|
||||||
|
|
||||||
|
\note Any exceptions you throw from here will be ignored, and the
|
||||||
|
callback will still be called in future for new connections.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual bool accept(IODevice* client) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Called when an error is detected.
|
||||||
|
|
||||||
|
\retval true if you want to continue listening.
|
||||||
|
\retval false if you want to stop listening.
|
||||||
|
|
||||||
|
This function is called whenever \c epoll detects an error with the
|
||||||
|
server socket. The default behaviour is to ignore the error, but you
|
||||||
|
might have some more complicated error processing to do if you so
|
||||||
|
wish.
|
||||||
|
|
||||||
|
\note Any exceptions you throw from here will be ignored, and the
|
||||||
|
callback will still be called in future for further errors.
|
||||||
|
|
||||||
|
\todo Presumably this gets called whenever epoll returns the
|
||||||
|
EPOLLERR event; we should therefore find a way to retrieve the
|
||||||
|
error from the socket's fd.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual bool error();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
/* lw-support/src/lib/Net/TCP/Client.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetClientTCP::NetClientTCP()
|
||||||
|
: sockAddr(0), peerAddr(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetClientTCP::clearAddr()
|
||||||
|
{
|
||||||
|
delete sockAddr;
|
||||||
|
sockAddr = 0;
|
||||||
|
delete peerAddr;
|
||||||
|
peerAddr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetClientTCP::~NetClientTCP()
|
||||||
|
{
|
||||||
|
clearAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetClientTCP::NetClientTCP(int fd, NetAddress* sockAddr, uint16_t sockPort,
|
||||||
|
NetAddress* peerAddr, uint16_t peerPort)
|
||||||
|
: sockAddr(sockAddr), peerAddr(peerAddr),
|
||||||
|
sockPort(sockPort), peerPort(peerPort)
|
||||||
|
{
|
||||||
|
this->fd = fd;
|
||||||
|
ioMode = IOReadWrite;
|
||||||
|
setNonBlocking();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetClientTCP::connect(const NetAddress& addr, uint16_t port, bool block)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// clear old connection
|
||||||
|
clearAddr();
|
||||||
|
close();
|
||||||
|
|
||||||
|
// create a socket
|
||||||
|
fd = TEMP_FAILURE_RETRY(socket(PF_INET, SOCK_STREAM, IPPROTO_TCP));
|
||||||
|
if(fd == -1)
|
||||||
|
throw SystemError().chain(L"socket()");
|
||||||
|
ioMode = IOReadWrite;
|
||||||
|
|
||||||
|
// set non-blocking mode
|
||||||
|
if(!block) setNonBlocking();
|
||||||
|
|
||||||
|
// fill out addresses and connect
|
||||||
|
peerAddr = addr.clone();
|
||||||
|
peerPort = port;
|
||||||
|
|
||||||
|
// IPv4
|
||||||
|
if(!sockAddr) {
|
||||||
|
const NetAddressIPv4* a = dynamic_cast<const NetAddressIPv4*>(&addr);
|
||||||
|
if(a) {
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
socklen_t sa_len = sizeof(sa);
|
||||||
|
|
||||||
|
// get address
|
||||||
|
if(TEMP_FAILURE_RETRY(getsockname(
|
||||||
|
fd, (struct sockaddr*)&sa, &sa_len)))
|
||||||
|
throw SystemError().chain(L"getsockname()");
|
||||||
|
sockAddr = new NetAddressIPv4((char*)&(sa.sin_addr.s_addr));
|
||||||
|
sockPort = sa.sin_port;
|
||||||
|
|
||||||
|
// connect
|
||||||
|
int ret = TEMP_FAILURE_RETRY(::connect(
|
||||||
|
fd, a->copy(&sa, port), sa_len));
|
||||||
|
|
||||||
|
if(ret == -1 && (block || errno != EINPROGRESS))
|
||||||
|
throw SystemError().chain(L"connect()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv6
|
||||||
|
if(!sockAddr) {
|
||||||
|
const NetAddressIPv6* a = dynamic_cast<const NetAddressIPv6*>(&addr);
|
||||||
|
if(a) {
|
||||||
|
struct sockaddr_in6 sa;
|
||||||
|
socklen_t sa_len = sizeof(sa);
|
||||||
|
|
||||||
|
// get address
|
||||||
|
if(TEMP_FAILURE_RETRY(getsockname(
|
||||||
|
fd, (struct sockaddr*)&sa, &sa_len)))
|
||||||
|
throw SystemError().chain(L"getsockname()");
|
||||||
|
sockAddr = new NetAddressIPv6(sa.sin6_addr.s6_addr);
|
||||||
|
sockPort = sa.sin6_port;
|
||||||
|
|
||||||
|
// connect
|
||||||
|
int ret = TEMP_FAILURE_RETRY(::connect(
|
||||||
|
fd, a->copy(&sa, port), sa_len));
|
||||||
|
|
||||||
|
if(ret == -1 && (block || errno != EINPROGRESS))
|
||||||
|
throw SystemError().chain(L"connect()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknown
|
||||||
|
if(!sockAddr) {
|
||||||
|
throw NetServer::UnknownProtocol();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we were doing blocking I/O, set non-blocking mode now
|
||||||
|
setNonBlocking();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// deal with errors gracefully
|
||||||
|
catch(Exception& e) {
|
||||||
|
try {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
e.chain(L"NetClientTCP::connect()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetClientTCP::connect(const NetAddress& addr, uint16_t port)
|
||||||
|
{
|
||||||
|
connect(addr, port, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetClientTCP::connectBlocking(const NetAddress& addr, uint16_t port)
|
||||||
|
{
|
||||||
|
connect(addr, port, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetClientTCP::NetClientTCP(const NetAddress& addr, uint16_t port)
|
||||||
|
: sockAddr(0), peerAddr(0)
|
||||||
|
{
|
||||||
|
connect(addr, port, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t NetClientTCP::localPort() const
|
||||||
|
{
|
||||||
|
checkOpen(L"NetClientTCP::localPort()");
|
||||||
|
return sockPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t NetClientTCP::remotePort() const
|
||||||
|
{
|
||||||
|
checkOpen(L"NetClientTCP::remotePort()");
|
||||||
|
return peerPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const NetAddress& NetClientTCP::localAddr() const
|
||||||
|
{
|
||||||
|
checkOpen(L"NetClientTCP::localPort()");
|
||||||
|
return *sockAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const NetAddress& NetClientTCP::remoteAddr() const
|
||||||
|
{
|
||||||
|
checkOpen(L"NetClientTCP::remotePort()");
|
||||||
|
return *peerAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetClientTCP::setNoDelay(bool noDelay)
|
||||||
|
{
|
||||||
|
int n = noDelay;
|
||||||
|
if(setsockopt(fd, SOL_TCP, TCP_NODELAY, &n, sizeof(n)))
|
||||||
|
throw SystemError()
|
||||||
|
.chain(L"setsockopt(..., SOL_TCP, TCP_NODELAY, ...)")
|
||||||
|
.chain(L"NetClientTCP::setNoDelay()");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/* lw-support/src/lib/Net/TCP/Client.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief TCP client socket class.
|
||||||
|
|
||||||
|
\todo docs
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NetClientTCP : public NetClient {
|
||||||
|
private:
|
||||||
|
void clearAddr();
|
||||||
|
void connect(const NetAddress& addr, uint16_t port, bool block);
|
||||||
|
NetAddress* sockAddr, * peerAddr;
|
||||||
|
uint16_t sockPort, peerPort;
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Constructors etc. ///////////////////////////////////////
|
||||||
|
|
||||||
|
/// Constructor. Does not connect client.
|
||||||
|
NetClientTCP();
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
virtual ~NetClientTCP();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Construct from an already-connected socket.
|
||||||
|
|
||||||
|
\param fd The file descriptor of the socket.
|
||||||
|
\param sockAddr Local address of the socket.
|
||||||
|
\param sockPort Local port of the socket.
|
||||||
|
\param peerAddr Address of the remote socket.
|
||||||
|
\param peerPort Remote socket port number.
|
||||||
|
|
||||||
|
This constructor is intended for use in e.g. NetServerTCP(), and is
|
||||||
|
for situations where you have an already-connected socket and want
|
||||||
|
to build a wrapper around it. It takes ownership of the \a sockAddr
|
||||||
|
and \a peerAddr objects.
|
||||||
|
|
||||||
|
*/
|
||||||
|
NetClientTCP(int fd, NetAddress* sockAddr, uint16_t sockPort,
|
||||||
|
NetAddress* peerAddr, uint16_t peerPort);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Construct and connect (blocking).
|
||||||
|
|
||||||
|
\param addr The address of the machine to connect to.
|
||||||
|
\param port The TCP port to connect to.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws NetServer::UnknownProtocol if the protocol of \a addr is
|
||||||
|
not known.
|
||||||
|
|
||||||
|
Similar to \a connect(), this function will establish a connection
|
||||||
|
to a remote host. However, this is a blocking function: it will
|
||||||
|
only return once the connection has been established.
|
||||||
|
|
||||||
|
*/
|
||||||
|
NetClientTCP(const NetAddress& addr, uint16_t port);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Connection functions ////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Connect to a remote host (non-blocking).
|
||||||
|
|
||||||
|
\param addr The address of the machine to connect to.
|
||||||
|
\param port The TCP port to connect to.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws NetServer::UnknownProtocol if the protocol of \a addr is
|
||||||
|
not known.
|
||||||
|
|
||||||
|
This function begins connecting to a remote host. Note that it
|
||||||
|
doesn't actually guarantee the connection will be established,
|
||||||
|
since it is a non-blocking function. You will know the connection
|
||||||
|
is ready when you receive an IOListen event.
|
||||||
|
|
||||||
|
If the client is already connected, it will be disconnected first.
|
||||||
|
|
||||||
|
\todo figure out what happens in the case of a connection error.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void connect(const NetAddress& addr, uint16_t port);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Connect to a remote host (blocking).
|
||||||
|
|
||||||
|
\param addr The address of the machine to connect to.
|
||||||
|
\param port The TCP port to connect to.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
\throws NetServer::UnknownProtocol if the protocol of \a addr is
|
||||||
|
not known.
|
||||||
|
|
||||||
|
Similar to \a connect(), this function will establish a connection
|
||||||
|
to a remote host. However, this is a blocking function: it will
|
||||||
|
only return once the connection has been established.
|
||||||
|
|
||||||
|
\todo add some nicer exception classes for networking.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void connectBlocking(const NetAddress& addr, uint16_t port);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Query functions /////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Query local port number; throws IOModeError if not connected.
|
||||||
|
uint16_t localPort() const;
|
||||||
|
|
||||||
|
/// Query remote port number; throws IOModeError if not connected.
|
||||||
|
uint16_t remotePort() const;
|
||||||
|
|
||||||
|
/// Query local host address; throws IOModeError if not connected.
|
||||||
|
const NetAddress& localAddr() const;
|
||||||
|
|
||||||
|
/// Query remote host address; throws IOModeError if not connected.
|
||||||
|
const NetAddress& remoteAddr() const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Misc. operations ////////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Set no delay option.
|
||||||
|
|
||||||
|
\param noDelay Set to \a true if you want to turn on the no delay
|
||||||
|
option; set to \a false if you want to use Nagle's algorithm.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
This function allows you to turn on/off Nagle's algorithm, whereby
|
||||||
|
outgoing packets are stored until the remote system returns an ACK.
|
||||||
|
This generally results in better usage of the network but does
|
||||||
|
increase latency.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void setNoDelay(bool noDelay);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
/* lw-support/src/lib/Net/TCP/Server.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetServerTCP::listen(const NetAddress& addr,
|
||||||
|
uint16_t port, int queueLen)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// shut down any previous server
|
||||||
|
stop();
|
||||||
|
|
||||||
|
// create a socket
|
||||||
|
fd = TEMP_FAILURE_RETRY(socket(PF_INET, SOCK_STREAM, IPPROTO_TCP));
|
||||||
|
if(fd == -1)
|
||||||
|
throw SystemError().chain(L"socket()");
|
||||||
|
|
||||||
|
// set SO_REUSEADDR, so that we can bind to a socket even if
|
||||||
|
// it's in the TCP TIME_WAIT state.
|
||||||
|
int reuse = 1;
|
||||||
|
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)))
|
||||||
|
throw SystemError().chain(L"setsockopt(..., SOL_SOCKET, SO_REUSEADDR, ...)");
|
||||||
|
|
||||||
|
// bind that socket to the requested address
|
||||||
|
switch(protocol = getProtocol(addr)) {
|
||||||
|
case IPv4: {
|
||||||
|
const NetAddressIPv4& a = dynamic_cast<const NetAddressIPv4&>(addr);
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
if(TEMP_FAILURE_RETRY(bind(fd, a.copy(&sa, port), sizeof(sa))))
|
||||||
|
throw SystemError().chain(L"bind()");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case IPv6: {
|
||||||
|
const NetAddressIPv6& a = dynamic_cast<const NetAddressIPv6&>(addr);
|
||||||
|
struct sockaddr_in6 sa;
|
||||||
|
if(TEMP_FAILURE_RETRY(bind(fd, a.copy(&sa, port), sizeof(sa))))
|
||||||
|
throw SystemError().chain(L"bind()");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start listening
|
||||||
|
if(TEMP_FAILURE_RETRY(::listen(fd, queueLen)))
|
||||||
|
throw SystemError().chain(L"listen()");
|
||||||
|
|
||||||
|
// set non-blocking I/O mode
|
||||||
|
setNonBlocking();
|
||||||
|
ioMode = IONone;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gracefully deal with any errors
|
||||||
|
catch(Exception& e) {
|
||||||
|
try {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
|
||||||
|
e.chain(L"NetServerTCP::listen()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void NetServerTCP::stop()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"NetServerTCP::stop()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetClientTCP* NetServerTCP::accept()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
int sockfd = -1;
|
||||||
|
|
||||||
|
switch(protocol) {
|
||||||
|
case IPv4: {
|
||||||
|
while(true) {
|
||||||
|
// get connection and address
|
||||||
|
struct sockaddr_in sa, sa2;
|
||||||
|
socklen_t sa_len = sizeof(sa);
|
||||||
|
sockfd = TEMP_FAILURE_RETRY(::accept(
|
||||||
|
fd, (struct sockaddr*)(&sa), &sa_len));
|
||||||
|
|
||||||
|
// return 0 if there was nothing waiting
|
||||||
|
if(sockfd == -1) {
|
||||||
|
// see accept(2) man page: some errors are propagated
|
||||||
|
// from elsewhere in the TCP stack and we should
|
||||||
|
// handle them by retrying accept().
|
||||||
|
switch(errno) {
|
||||||
|
case EAGAIN:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case ENETDOWN:
|
||||||
|
case EPROTO:
|
||||||
|
case ENOPROTOOPT:
|
||||||
|
case EHOSTDOWN:
|
||||||
|
case ENONET:
|
||||||
|
case EHOSTUNREACH:
|
||||||
|
case EOPNOTSUPP:
|
||||||
|
case ENETUNREACH:
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw SystemError().chain(L"accept()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get local address
|
||||||
|
sa_len = sizeof(sa2);
|
||||||
|
if(TEMP_FAILURE_RETRY(getsockname(
|
||||||
|
sockfd, (struct sockaddr*)(&sa2), &sa_len)))
|
||||||
|
throw SystemError().chain(L"getsockname()");
|
||||||
|
|
||||||
|
// build client
|
||||||
|
return new NetClientTCP(sockfd,
|
||||||
|
new NetAddressIPv4((char*)&(sa2.sin_addr.s_addr)), ntohs(sa2.sin_port),
|
||||||
|
new NetAddressIPv4((char*)&(sa.sin_addr.s_addr)), ntohs(sa.sin_port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case IPv6: {
|
||||||
|
while(true) {
|
||||||
|
// get connection and address
|
||||||
|
struct sockaddr_in6 sa, sa2;
|
||||||
|
socklen_t sa_len = sizeof(sa);
|
||||||
|
sockfd = TEMP_FAILURE_RETRY(::accept(
|
||||||
|
fd, (struct sockaddr*)(&sa), &sa_len));
|
||||||
|
|
||||||
|
// return 0 if there was nothing waiting
|
||||||
|
if(sockfd == -1) {
|
||||||
|
// see accept(2) man page: some errors are propagated
|
||||||
|
// from elsewhere in the TCP stack and we should
|
||||||
|
// handle them by retrying accept().
|
||||||
|
//
|
||||||
|
// TODO: is this right for IPv6 as well?
|
||||||
|
switch(errno) {
|
||||||
|
case EAGAIN:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case ENETDOWN:
|
||||||
|
case EPROTO:
|
||||||
|
case ENOPROTOOPT:
|
||||||
|
case EHOSTDOWN:
|
||||||
|
case ENONET:
|
||||||
|
case EHOSTUNREACH:
|
||||||
|
case EOPNOTSUPP:
|
||||||
|
case ENETUNREACH:
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw SystemError().chain(L"accept()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get local address
|
||||||
|
sa_len = sizeof(sa2);
|
||||||
|
if(TEMP_FAILURE_RETRY(getsockname(
|
||||||
|
sockfd, (struct sockaddr*)(&sa2), &sa_len)))
|
||||||
|
throw SystemError().chain(L"getsockname()");
|
||||||
|
|
||||||
|
// build client
|
||||||
|
return new NetClientTCP(sockfd,
|
||||||
|
new NetAddressIPv6(sa2.sin6_addr.s6_addr), ntohs(sa2.sin6_port),
|
||||||
|
new NetAddressIPv6(sa.sin6_addr.s6_addr), ntohs(sa.sin6_port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ProgramException().chain(L"NetServerTCP::connect()");
|
||||||
|
}
|
||||||
|
catch(Exception& e) {
|
||||||
|
e.chain(L"NetServerTCP::accept()");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NetClientTCP* NetServerTCP::acceptBlocking(int timeout, IOTimeoutMode timeoutMode)
|
||||||
|
{
|
||||||
|
readBlock(timeout, timeoutMode);
|
||||||
|
NetClientTCP* client = accept();
|
||||||
|
|
||||||
|
// TODO: better error handling here would be appropriate... restart timeout somehow
|
||||||
|
if(!client) throw ProgramException().chain(L"NetServerTCP::acceptBlocking()");
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
/* lw-support/src/lib/Net/TCP/Server.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief TCP server class.
|
||||||
|
|
||||||
|
\todo docs
|
||||||
|
|
||||||
|
*/
|
||||||
|
class NetServerTCP : public NetServer {
|
||||||
|
public:
|
||||||
|
/// Constructor. Does not open connection.
|
||||||
|
NetServerTCP()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Begin listening on a specific address.
|
||||||
|
|
||||||
|
\param address The address to listen on.
|
||||||
|
\param port The port number to listen on.
|
||||||
|
\param queueLen The length of the unaccepted connection queue.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
This function starts the server listening for new connections. The
|
||||||
|
server will listen on any address.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void listen(const NetAddress& address, uint16_t port, int queueLen);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Stop listening to incoming connections.
|
||||||
|
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
This function will stop listening for incoming connections, and will
|
||||||
|
reject any connection requests that have not yet been accepted.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Implemented virtuals ////////////////////////////////////
|
||||||
|
|
||||||
|
virtual lw::NetClientTCP* accept();
|
||||||
|
virtual lw::NetClientTCP* acceptBlocking(int timeout, IOTimeoutMode timeoutMode);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,401 @@
|
||||||
|
/* lw-support/src/lib/String/basics.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring stripWhitespace(const std::wstring& str)
|
||||||
|
{
|
||||||
|
std::wstring::size_type pos, len = str.length();
|
||||||
|
for(pos = 0; pos < len; ++pos) if(!isWhitespace(str[pos])) break;
|
||||||
|
if(pos == len) return std::wstring();
|
||||||
|
|
||||||
|
std::wstring::size_type endPos = len - 1;
|
||||||
|
while(isWhitespace(str[endPos])) --endPos;
|
||||||
|
|
||||||
|
return str.substr(pos, endPos - pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string ucs4ToUtf8(const std::wstring& ucs4, bool force)
|
||||||
|
{
|
||||||
|
// compute size of string, skip empty strings
|
||||||
|
std::wstring::size_type len = ucs4.length(), rd_pos, start;
|
||||||
|
if(!len) return std::string();
|
||||||
|
|
||||||
|
// skip BOM
|
||||||
|
start = (ucs4[0] == 0xFEFF) ? 1 : 0;
|
||||||
|
|
||||||
|
// compute required size and check characters
|
||||||
|
int dlen = 0;
|
||||||
|
for(rd_pos = start; rd_pos < len; ++rd_pos) {
|
||||||
|
wchar_t ch = ucs4[rd_pos];
|
||||||
|
if(!force && (ch & ~0x7FFFFFFF)) {
|
||||||
|
throw BadSourceChar((const char*)&ch, (const char*)(&ch + 4),
|
||||||
|
rd_pos * 4, L"UCS-4", L"Character out of range.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ch < 0x80) ++dlen;
|
||||||
|
else if(ch < 0x800) dlen += 2;
|
||||||
|
else if(ch < 0x10000) dlen += 3;
|
||||||
|
else if(ch < 0x200000) dlen += 4;
|
||||||
|
else if(ch < 0x4000000) dlen += 5;
|
||||||
|
else dlen += 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate memory
|
||||||
|
std::string dest(dlen, 0);
|
||||||
|
|
||||||
|
// convert string
|
||||||
|
std::string::size_type wr_pos = 0;
|
||||||
|
for(rd_pos = start; rd_pos < len; ++rd_pos) {
|
||||||
|
uint32_t ch = ucs4[rd_pos];
|
||||||
|
|
||||||
|
if(ch & ~0x7FFFFFFF) continue;
|
||||||
|
|
||||||
|
if(ch < 0x80) {
|
||||||
|
dest[wr_pos++] = ch;
|
||||||
|
|
||||||
|
} else if(ch < 0x800) {
|
||||||
|
dest[wr_pos++] = 0xC0 | ((ch >> 6) & 0x1F);
|
||||||
|
dest[wr_pos++] = 0x80 | (ch & 0x3F);
|
||||||
|
|
||||||
|
} else if(ch < 0x10000) {
|
||||||
|
dest[wr_pos++] = 0xE0 | ((ch >> 12) & 0xF);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | (ch & 0x3F);
|
||||||
|
|
||||||
|
} else if(ch < 0x200000) {
|
||||||
|
dest[wr_pos++] = 0xF0 | ((ch >> 18) & 0x7);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 12) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | (ch & 0x3F);
|
||||||
|
|
||||||
|
} else if(ch < 0x4000000) {
|
||||||
|
dest[wr_pos++] = 0xF8 | ((ch >> 24) & 0x3);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 18) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 12) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | (ch & 0x3F);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
dest[wr_pos++] = 0xFC | ((ch >> 30) & 0x1);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 24) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 18) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 12) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | ((ch >> 6) & 0x3F);
|
||||||
|
dest[wr_pos++] = 0x80 | (ch & 0x3F);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::string ucs4ToAscii(const std::wstring& ucs4, bool force)
|
||||||
|
{
|
||||||
|
// compute size of string, skip empty strings
|
||||||
|
std::wstring::size_type len = ucs4.length(), rd_pos, start;
|
||||||
|
if(!len) return std::string();
|
||||||
|
|
||||||
|
// skip BOM
|
||||||
|
start = (ucs4[0] == 0xFEFF) ? 1 : 0;
|
||||||
|
|
||||||
|
// allocate destination string
|
||||||
|
int skipped = 0;
|
||||||
|
std::string ascii(len - start, 0);
|
||||||
|
|
||||||
|
// convert each char, checking values as we go
|
||||||
|
for(rd_pos = start; rd_pos < len; ++rd_pos) {
|
||||||
|
if(ucs4[rd_pos] & ~0x7F) {
|
||||||
|
++skipped;
|
||||||
|
if(force) continue;
|
||||||
|
throw CannotConvertChar(ucs4[rd_pos], rd_pos, L"UCS-4", L"ASCII");
|
||||||
|
}
|
||||||
|
|
||||||
|
ascii[rd_pos] = ucs4[rd_pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip off any skipped characters
|
||||||
|
if(skipped) ascii.resize(len - skipped);
|
||||||
|
|
||||||
|
// done
|
||||||
|
return ascii;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void utf8AuxConsume(wchar_t& ch, const std::string& utf8,
|
||||||
|
std::string::size_type& rd_pos, std::string::size_type ch_start)
|
||||||
|
{
|
||||||
|
uint8_t p = utf8[rd_pos++];
|
||||||
|
if((p & 0xC0) != 0x80) {
|
||||||
|
throw BadSourceChar(utf8.c_str() + ch_start,
|
||||||
|
utf8.c_str() + rd_pos, ch_start, L"UTF-8",
|
||||||
|
L"Invalid character sequence (expecting byte 10xxxxxx).");
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <<= 6;
|
||||||
|
ch |= p & 0x3F;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring utf8ToUcs4Forced(const std::string& str)
|
||||||
|
{
|
||||||
|
// compute size of std::string, skip empty std::strings
|
||||||
|
std::string::size_type len = str.length(), rd_pos = 0;
|
||||||
|
if(!len) return std::wstring();
|
||||||
|
|
||||||
|
// skip possible BOM
|
||||||
|
if(len >= 3 && str.substr(0, 3) == "\xEF\xBB\xBF") rd_pos = 3;
|
||||||
|
|
||||||
|
// for FSM
|
||||||
|
enum CharType {
|
||||||
|
CTypeAscii,
|
||||||
|
CTypeContinue,
|
||||||
|
CType2,
|
||||||
|
CType3,
|
||||||
|
CType4,
|
||||||
|
CType5,
|
||||||
|
CType6
|
||||||
|
}ctype;
|
||||||
|
int cremain = 0;
|
||||||
|
|
||||||
|
// destination buffer
|
||||||
|
std::wstring d;
|
||||||
|
|
||||||
|
// begin conversion using FSM
|
||||||
|
uint8_t ch;
|
||||||
|
wchar_t dch;
|
||||||
|
|
||||||
|
while(rd_pos < len) {
|
||||||
|
ch = str[rd_pos++];
|
||||||
|
|
||||||
|
// classify
|
||||||
|
if(ch & 0x80) {
|
||||||
|
if((ch & 0xC0) == 0x80) ctype = CTypeContinue;
|
||||||
|
else if((ch & 0xE0) == 0xC0) ctype = CType2;
|
||||||
|
else if((ch & 0xF0) == 0xE0) ctype = CType3;
|
||||||
|
else if((ch & 0xF8) == 0xF0) ctype = CType4;
|
||||||
|
else if((ch & 0xFC) == 0xF8) ctype = CType5;
|
||||||
|
else if((ch & 0xFE) == 0xFC) ctype = CType6;
|
||||||
|
else {
|
||||||
|
continue; // must be 0xFE or 0xFF, both invalid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctype = CTypeAscii;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode multi-byte
|
||||||
|
if(cremain) {
|
||||||
|
if(ctype != CTypeContinue) {
|
||||||
|
--rd_pos;
|
||||||
|
cremain = 0; // was expecting multi-byte; abort conversion
|
||||||
|
continue; // of this char and restart
|
||||||
|
}
|
||||||
|
dch <<= 6;
|
||||||
|
dch |= ch & 0x3F;
|
||||||
|
if(!--cremain) d.append(&dch, 1);
|
||||||
|
|
||||||
|
// decode start char
|
||||||
|
} else {
|
||||||
|
switch(ctype) {
|
||||||
|
case CTypeContinue:
|
||||||
|
// not valid here
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case CTypeAscii:
|
||||||
|
dch = ch;
|
||||||
|
d.append(&dch, 1);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case CType2:
|
||||||
|
cremain = 1;
|
||||||
|
dch = ch & 0x1F;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case CType3:
|
||||||
|
cremain = 2;
|
||||||
|
dch = ch & 0x0F;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case CType4:
|
||||||
|
cremain = 3;
|
||||||
|
dch = ch & 0x07;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case CType5:
|
||||||
|
cremain = 4;
|
||||||
|
dch = ch & 0x03;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case CType6:
|
||||||
|
cremain = 5;
|
||||||
|
dch = ch & 0x01;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring utf8ToUcs4(const std::string& utf8, bool force)
|
||||||
|
{
|
||||||
|
// when conversion is forced, we need to use a more hardy algorithm
|
||||||
|
if(force) return utf8ToUcs4Forced(utf8);
|
||||||
|
|
||||||
|
// compute size of std::string, skip empty std::strings
|
||||||
|
std::string::size_type len = utf8.length(), rd_pos, ch_start = 0;
|
||||||
|
if(!len) return std::wstring();
|
||||||
|
|
||||||
|
// skip BOM
|
||||||
|
std::string::size_type start = 0;
|
||||||
|
if(len >= 3 && utf8.substr(0, 3) == "\xEF\xBB\xBF") start = 3;
|
||||||
|
|
||||||
|
// compute required size
|
||||||
|
std::wstring::size_type dlen = 0;
|
||||||
|
rd_pos = start;
|
||||||
|
uint8_t ch;
|
||||||
|
while(rd_pos < len) {
|
||||||
|
ch_start = rd_pos;
|
||||||
|
ch = utf8[rd_pos];
|
||||||
|
if((ch & 0x80) == 0) ++rd_pos;
|
||||||
|
else if((ch & 0xE0) == 0xC0) rd_pos += 2;
|
||||||
|
else if((ch & 0xF0) == 0xE0) rd_pos += 3;
|
||||||
|
else if((ch & 0xF8) == 0xF0) rd_pos += 4;
|
||||||
|
else if((ch & 0xFC) == 0xF8) rd_pos += 5;
|
||||||
|
else if((ch & 0xFE) == 0xFC) rd_pos += 6;
|
||||||
|
else {
|
||||||
|
throw BadSourceChar(utf8.c_str() + ch_start,
|
||||||
|
utf8.c_str() + ch_start + 1, ch_start, L"UTF-8",
|
||||||
|
L"Invalid character in std::string.");
|
||||||
|
}
|
||||||
|
++dlen;
|
||||||
|
}
|
||||||
|
if(rd_pos != len) {
|
||||||
|
throw BadSourceChar(utf8.c_str() + ch_start,
|
||||||
|
utf8.c_str() + rd_pos, ch_start, L"UTF-8",
|
||||||
|
L"Last character appears to be truncated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate memory
|
||||||
|
std::wstring dest(dlen, 0);
|
||||||
|
|
||||||
|
// helper macros
|
||||||
|
#define CONSUME() utf8AuxConsume(ch, utf8, rd_pos, ch_start)
|
||||||
|
#define CONSUME_FIRST(mask) ch = (p & mask)
|
||||||
|
#define CHECK(min) do \
|
||||||
|
if(ch < min) { \
|
||||||
|
throw BadSourceChar(utf8.c_str() + ch_start, \
|
||||||
|
utf8.c_str() + rd_pos, ch_start, L"UTF-8", \
|
||||||
|
L"Character encoded in an overlong sequence."); \
|
||||||
|
} \
|
||||||
|
while(0)
|
||||||
|
|
||||||
|
// loop through each character in turn
|
||||||
|
rd_pos = start;
|
||||||
|
for(std::wstring::size_type wr_pos = 0; wr_pos < dlen; ++wr_pos) {
|
||||||
|
wchar_t ch = 0;
|
||||||
|
ch_start = rd_pos;
|
||||||
|
uint8_t p = utf8[rd_pos++];
|
||||||
|
|
||||||
|
if(!(p & 0x80)) {
|
||||||
|
ch = p;
|
||||||
|
|
||||||
|
} else if((p & 0xFE) == 0xFC) {
|
||||||
|
CONSUME_FIRST(0x01);
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CHECK(0x4000000);
|
||||||
|
|
||||||
|
} else if((p & 0xFC) == 0xF8) {
|
||||||
|
CONSUME_FIRST(0x03);
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CHECK(0x200000);
|
||||||
|
|
||||||
|
} else if((p & 0xF8) == 0xF0) {
|
||||||
|
CONSUME_FIRST(0x07);
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CHECK(0x10000);
|
||||||
|
|
||||||
|
} else if((p & 0xF0) == 0xE0) {
|
||||||
|
CONSUME_FIRST(0x0F);
|
||||||
|
CONSUME();
|
||||||
|
CONSUME();
|
||||||
|
CHECK(0x800);
|
||||||
|
|
||||||
|
} else if((p & 0xE0) == 0xC0) {
|
||||||
|
CONSUME_FIRST(0x1F);
|
||||||
|
CONSUME();
|
||||||
|
CHECK(0x80);
|
||||||
|
|
||||||
|
}
|
||||||
|
dest[wr_pos] = ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CONSUME
|
||||||
|
#undef CONSUME_FIRST
|
||||||
|
#undef CHECK
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring asciiToUcs4(const std::string& ascii, bool force)
|
||||||
|
{
|
||||||
|
int skipped = 0;
|
||||||
|
std::string::size_type size = ascii.size(), pos;
|
||||||
|
std::wstring d(size, 0);
|
||||||
|
|
||||||
|
for(pos = 0; pos < size; ++pos) {
|
||||||
|
if(d[pos] & ~0x7F) {
|
||||||
|
++skipped;
|
||||||
|
if(!force) throw BadSourceChar(ascii.c_str() + pos,
|
||||||
|
ascii.c_str() + pos + 1, pos, L"ASCII",
|
||||||
|
L"Invalid character (out of range).");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
d[pos] = ascii[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
d.resize(size - skipped);
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring strToUcs4(const std::string& str)
|
||||||
|
{
|
||||||
|
std::string::size_type size = str.size(), pos;
|
||||||
|
std::wstring d(size, 0);
|
||||||
|
for(pos = 0; pos < size; ++pos) d[pos] = str[pos];
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
/* lw-support/src/lib/String/basics.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Return true if character is whitespace.
|
||||||
|
|
||||||
|
\param ch The character to classify.
|
||||||
|
\retval true if character represents whitespace.
|
||||||
|
\retval false if character does not represent whitespace.
|
||||||
|
|
||||||
|
This function is identical to the C library's isspace() function, except
|
||||||
|
that it handles Unicode code points.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline bool isWhitespace(wchar_t ch) __attribute__((const));
|
||||||
|
bool isWhitespace(wchar_t ch)
|
||||||
|
{
|
||||||
|
return ((ch >= 0x9 && ch <= 0xD) ||
|
||||||
|
ch == 0x20 ||
|
||||||
|
ch == 0x85 ||
|
||||||
|
ch == 0xA0 ||
|
||||||
|
ch == 0x1680 ||
|
||||||
|
ch == 0x180E ||
|
||||||
|
(ch >= 0x2000 && ch <= 0x200A) ||
|
||||||
|
ch == 0x2028 ||
|
||||||
|
ch == 0x2029 ||
|
||||||
|
ch == 0x202F ||
|
||||||
|
ch == 0x205F ||
|
||||||
|
ch == 0x3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Strip leading and trailing whitespace.
|
||||||
|
|
||||||
|
\param str The UCS-4 encoded string to strip whitespace from.
|
||||||
|
\returns A copy of the string without trailing/leading whitespace.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring stripWhitespace(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Convert a UCS-4 string to UTF-8.
|
||||||
|
|
||||||
|
\param ucs4 The (UCS-4 encoded) string to convert.
|
||||||
|
\param force Set this to \a true if you want to force the conversion to
|
||||||
|
happen even in the face of invalid characters.
|
||||||
|
\returns A UTF-8 representation of \a ucs4.
|
||||||
|
\throws BadSourceChar if a character in the string is outside the
|
||||||
|
Unicode range (i.e. 0-0x7FFFFFFF).
|
||||||
|
|
||||||
|
This function can be used to convert a UCS-4 string into a UTF-8 string.
|
||||||
|
It will throw an exception if any error is encountered, unless \a force
|
||||||
|
is \a true, in which case invalid characters will be skipped. It will
|
||||||
|
strip a BOM (byte order mark) from the beginning of a string.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::string ucs4ToUtf8(const std::wstring& ucs4, bool force = false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Convert a UCS-4 string to ASCII.
|
||||||
|
|
||||||
|
\param ucs4 The (UCS-4 encoded) string to convert.
|
||||||
|
\param force Set this to \a true if you want to force the conversion to
|
||||||
|
happen even in the face of invalid characters.
|
||||||
|
\returns A UTF-8 representation of \a ucs4.
|
||||||
|
\throws BadSourceChar if a character in the string is outside the
|
||||||
|
ASCII range (i.e. 0-0x7F).
|
||||||
|
|
||||||
|
This function can be used to convert a UCS-4 string into an ASCII
|
||||||
|
string. It will throw an exception if any error is encountered, unless \a force
|
||||||
|
is \a true, in which case invalid characters will be skipped. It will
|
||||||
|
strip a BOM (byte order mark) from the beginning of a string.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::string ucs4ToAscii(const std::wstring& ucs4, bool force = false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Convert a UTF-8 string to UCS-4.
|
||||||
|
|
||||||
|
\param utf8 The (UTF-8 encoded) string to convert.
|
||||||
|
\param force Set this to \a true if you want to force the conversion to
|
||||||
|
happen even in the face of invalid characters.
|
||||||
|
\returns A UCS-4 representation of \a utf8.
|
||||||
|
\throws BadSourceChar if an invalid byte sequence is encountered in the
|
||||||
|
UTF-8 string.
|
||||||
|
|
||||||
|
This function can be used to convert a UTF-8 string into a UCS-4 string.
|
||||||
|
It will throw an exception if any error is encountered, unless \a force
|
||||||
|
is \a true, in which case invalid characters will be skipped. It will
|
||||||
|
strip a BOM (byte order mark) from the beginning of a string.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring utf8ToUcs4(const std::string& utf8, bool force = false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Convert an ASCII string to UCS-4.
|
||||||
|
|
||||||
|
\param ascii The ASCII string to convert.
|
||||||
|
\param force Set this to \a true if you want to force the conversion to
|
||||||
|
happen even in the face of invalid characters.
|
||||||
|
\returns A UCS-4 representation of \a ascii.
|
||||||
|
\throws BadSourceChar if an invalid byte (outside the range 0-x7F) is
|
||||||
|
encountered in the ASCII string.
|
||||||
|
|
||||||
|
This function can be used to convert an ASCII string into a UCS-4
|
||||||
|
string. It will throw an exception if any error is encountered, unless
|
||||||
|
\a force is \a true, in which case invalid characters will be skipped.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring asciiToUcs4(const std::string& ascii, bool force = false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Convert an 8-bit string to UCS-4.
|
||||||
|
|
||||||
|
\param str The 8-bit string to convert.
|
||||||
|
\returns A UCS-4 representation of \a str.
|
||||||
|
|
||||||
|
This function is used to perform a direct conversion of an 8-bit string
|
||||||
|
into UCS-4. It doesn't take any Unicode issues into account. It always
|
||||||
|
succeeds. This function can be used if the resultant string will be
|
||||||
|
checked for validity elsewhere anyway.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring strToUcs4(const std::string& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,530 @@
|
||||||
|
/* lw-support/src/lib/String/strToInt.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Auxiliary conversion functions //////////////////////////////
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
uint64_t strToIntDec(const std::wstring& str,
|
||||||
|
std::wstring::size_type pos, std::wstring::size_type strLen)
|
||||||
|
{
|
||||||
|
uint64_t value = 0;
|
||||||
|
for(; pos < strLen; ++pos) {
|
||||||
|
int ch = str[pos];
|
||||||
|
if(ch < '0' || ch > '9')
|
||||||
|
throw ParseError(L"Not a decimal number.", str, pos);
|
||||||
|
if(value > 1844674407370955161LL) throw NumericOverflow();
|
||||||
|
value *= 10;
|
||||||
|
if(~value < (uint64_t)(ch - '0')) throw NumericOverflow();
|
||||||
|
value += ch - '0';
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint64_t strToIntHex(const std::wstring& str,
|
||||||
|
std::wstring::size_type pos, std::wstring::size_type strLen)
|
||||||
|
{
|
||||||
|
uint64_t value = 0;
|
||||||
|
int maxDigRemaining = 16;
|
||||||
|
for(; pos < strLen; ++pos) {
|
||||||
|
if(!maxDigRemaining--) throw NumericOverflow();
|
||||||
|
value <<= 4;
|
||||||
|
|
||||||
|
int ch = str[pos];
|
||||||
|
switch(ch) {
|
||||||
|
case '0' ... '9':
|
||||||
|
value |= ch - '0';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a' ... 'f':
|
||||||
|
value |= ch - 'a' + 10;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'A' ... 'F':
|
||||||
|
value |= ch - 'A' + 10;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw ParseError(L"Not a decimal number.", str, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//END // Auxiliary conversion functions ////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int64_t strToInt64(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
bool negative = false;
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
negative = true;
|
||||||
|
++pos;
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
result = strToIntHex(str, pos, strLen);
|
||||||
|
} else {
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
result = strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(negative && result == 0x8000000000000000LL) return -0x8000000000000000LL;
|
||||||
|
if(result & ~0x7FFFFFFFFFFFFFFFLL) throw NumericOverflow();
|
||||||
|
|
||||||
|
int64_t p = (int64_t)result;
|
||||||
|
if(negative) p *= -1;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int32_t strToInt32(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
bool negative = false;
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
negative = true;
|
||||||
|
++pos;
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
result = strToIntHex(str, pos, strLen);
|
||||||
|
} else {
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
result = strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(negative && result == 0x80000000) return -0x80000000;
|
||||||
|
if(result & ~0x7FFFFFFF) throw NumericOverflow();
|
||||||
|
|
||||||
|
int32_t p = (int32_t)result;
|
||||||
|
if(negative) p *= -1;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int16_t strToInt16(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
bool negative = false;
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
negative = true;
|
||||||
|
++pos;
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
result = strToIntHex(str, pos, strLen);
|
||||||
|
} else {
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
result = strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(negative && result == 0x8000) return -0x8000;
|
||||||
|
if(result & ~0x7FFF) throw NumericOverflow();
|
||||||
|
|
||||||
|
int16_t p = (int16_t)result;
|
||||||
|
if(negative) p *= -1;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int8_t strToInt8(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
bool negative = false;
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
negative = true;
|
||||||
|
++pos;
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
result = strToIntHex(str, pos, strLen);
|
||||||
|
} else {
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
result = strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(negative && result == 0x80) return -0x80;
|
||||||
|
if(result & ~0x7F) throw NumericOverflow();
|
||||||
|
|
||||||
|
int8_t p = (int8_t)result;
|
||||||
|
if(negative) p *= -1;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint64_t strToUInt64(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
throw ParseError(L"Negative number found.", str, 0);
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
return strToIntHex(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
return strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint32_t strToUInt32(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
throw ParseError(L"Negative number found.", str, 0);
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
result = strToIntHex(str, pos, strLen);
|
||||||
|
} else {
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
result = strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(result & ~0xFFFFFFFF) throw NumericOverflow();
|
||||||
|
|
||||||
|
return uint32_t(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t strToUInt16(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
throw ParseError(L"Negative number found.", str, 0);
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
result = strToIntHex(str, pos, strLen);
|
||||||
|
} else {
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
result = strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(result & ~0xFFFF) throw NumericOverflow();
|
||||||
|
|
||||||
|
return uint16_t(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t strToUInt8(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
if(!strLen) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
std::wstring::size_type pos = 0;
|
||||||
|
uint64_t result;
|
||||||
|
|
||||||
|
// check for sign
|
||||||
|
if(str[0] == '-') {
|
||||||
|
throw ParseError(L"Negative number found.", str, 0);
|
||||||
|
} else if(str[0] == '+') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pos == strLen) {
|
||||||
|
throw ParseError(L"String only contained sign character.",
|
||||||
|
str, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for hexadecimal
|
||||||
|
if(str[pos] == '0' && ++pos != strLen &&
|
||||||
|
(str[pos] == 'x' || str[pos] == 'X'))
|
||||||
|
{
|
||||||
|
if(++pos == strLen)
|
||||||
|
throw ParseError(L"String only contained hex sequence.", str, pos);
|
||||||
|
result = strToIntHex(str, pos, strLen);
|
||||||
|
} else {
|
||||||
|
if(pos == strLen) return 0;
|
||||||
|
result = strToIntDec(str, pos, strLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for overflow
|
||||||
|
if(result & ~0xFF) throw NumericOverflow();
|
||||||
|
|
||||||
|
return uint8_t(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool strToBool(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
|
||||||
|
// get string length
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
switch(strLen) {
|
||||||
|
case 0:
|
||||||
|
throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
case 1: // "0" or "1"
|
||||||
|
// check for numeric values
|
||||||
|
if(str[0] == '0') return false;
|
||||||
|
if(str[0] == '1') return true;
|
||||||
|
throw ParseError(L"Unrecognised boolean value.", str, 0);
|
||||||
|
|
||||||
|
case 4: // "true"
|
||||||
|
if(tolower(str[0]) != 't' ||
|
||||||
|
tolower(str[1]) != 'r' ||
|
||||||
|
tolower(str[2]) != 'u' ||
|
||||||
|
tolower(str[3]) != 'e')
|
||||||
|
throw ParseError(L"Unrecognised boolean value.", str, 0);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 5: // "false"
|
||||||
|
if(tolower(str[0]) != 'f' ||
|
||||||
|
tolower(str[1]) != 'a' ||
|
||||||
|
tolower(str[2]) != 'l' ||
|
||||||
|
tolower(str[3]) != 's' ||
|
||||||
|
tolower(str[4]) != 'e')
|
||||||
|
throw ParseError(L"Unrecognised boolean value.", str, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ParseError(L"Unrecognised boolean value.", str, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
long double strToLongDouble(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
if(str.empty()) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
// perform conversion
|
||||||
|
errno = 0;
|
||||||
|
wchar_t* tailPtr = 0;
|
||||||
|
long double d = wcstold(str.c_str(), &tailPtr);
|
||||||
|
|
||||||
|
// check for errors
|
||||||
|
if(*tailPtr) {
|
||||||
|
throw ParseError(L"Not a valid floating point number.", str,
|
||||||
|
tailPtr - str.c_str());
|
||||||
|
}
|
||||||
|
if(errno) throw NumericOverflow();
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
double strToDouble(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
if(str.empty()) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
// perform conversion
|
||||||
|
errno = 0;
|
||||||
|
wchar_t* tailPtr = 0;
|
||||||
|
double d = wcstod(str.c_str(), &tailPtr);
|
||||||
|
|
||||||
|
// check for errors
|
||||||
|
if(*tailPtr) {
|
||||||
|
throw ParseError(L"Not a valid floating point number.", str,
|
||||||
|
tailPtr - str.c_str());
|
||||||
|
}
|
||||||
|
if(errno) throw NumericOverflow();
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float strToFloat(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
if(str.empty()) throw ParseError(L"Empty string.", str, 0);
|
||||||
|
|
||||||
|
// perform conversion
|
||||||
|
errno = 0;
|
||||||
|
wchar_t* tailPtr = 0;
|
||||||
|
float d = wcstof(str.c_str(), &tailPtr);
|
||||||
|
|
||||||
|
// check for errors
|
||||||
|
if(*tailPtr) {
|
||||||
|
throw ParseError(L"Not a valid floating point number.", str,
|
||||||
|
tailPtr - str.c_str());
|
||||||
|
}
|
||||||
|
if(errno) throw NumericOverflow();
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
/* lw-support/src/lib/String/strToInt.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
-0xhhh, 0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
int64_t strToInt64(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
-0xhhh, 0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
int32_t strToInt32(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
-0xhhh, 0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
int16_t strToInt16(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. -ddd, ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
-0xhhh, 0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
int8_t strToInt8(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
uint64_t strToUInt64(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
uint32_t strToUInt32(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer, or
|
||||||
|
if the integer is negative.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
uint16_t strToUInt16(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to an integer.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid integer, or
|
||||||
|
if the integer is negative.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into an integer.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as an integer. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two types of number:
|
||||||
|
- a decimal, with optional leading sign (i.e. ddd or +ddd).
|
||||||
|
- a hexadecimal, with optional leading sign, in C notation (i.e.
|
||||||
|
0xhhh or +0xhhh), case insensitive.
|
||||||
|
|
||||||
|
*/
|
||||||
|
uint8_t strToUInt8(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to a boolean.
|
||||||
|
|
||||||
|
\param str The string to parse.
|
||||||
|
\throws ParseError if the string does not represent a valid boolean.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as a boolean. Any trailing
|
||||||
|
or leading whitespace is ignored. It understands two notations:
|
||||||
|
- the strings "0" or "false" (case insensitive), for a false value.
|
||||||
|
- the strings "1" or "true" (case insensitive), for a true value.
|
||||||
|
|
||||||
|
*/
|
||||||
|
bool strToBool(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to a number.
|
||||||
|
|
||||||
|
\param str The string to parse
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into a double-precision floating point number.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as a quad-precision
|
||||||
|
floating point number. Any trailing or leading whitespace is ignored.
|
||||||
|
|
||||||
|
*/
|
||||||
|
long double strToLongDouble(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to a number.
|
||||||
|
|
||||||
|
\param str The string to parse
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into a double-precision floating point number.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as a double-precision
|
||||||
|
floating point number. Any trailing or leading whitespace is ignored.
|
||||||
|
|
||||||
|
*/
|
||||||
|
double strToDouble(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a string, converting it to a number.
|
||||||
|
|
||||||
|
\param str The string to parse
|
||||||
|
\throws ParseError if the string does not represent a valid integer.
|
||||||
|
\throws NumericOverflow if the value represented in the string does not
|
||||||
|
fit into a double-precision floating point number.
|
||||||
|
\returns The value represented in the string.
|
||||||
|
|
||||||
|
This function parses a string and returns it as a single-precision
|
||||||
|
floating point number. Any trailing or leading whitespace is ignored.
|
||||||
|
|
||||||
|
*/
|
||||||
|
float strToFloat(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* lw-support/src/lib/Thread/CompletionNotifier.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CompletionNotifier::~CompletionNotifier()
|
||||||
|
{
|
||||||
|
pthread_cond_broadcast(&cond);
|
||||||
|
while(true) {
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
if(!pthread_cond_destroy(&cond)) break;
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
pthread_mutex_destroy(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionNotifier::wait(int maxTime)
|
||||||
|
{
|
||||||
|
if(maxTime < 0) {
|
||||||
|
wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timespec ts;
|
||||||
|
ts.tv_sec = maxTime / 1000;
|
||||||
|
ts.tv_nsec = (maxTime % 1000) * 1000000;
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
|
||||||
|
while(!done) {
|
||||||
|
switch(pthread_cond_timedwait(&cond, &mut, &ts)) {
|
||||||
|
case ETIMEDOUT: {
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
throw Timeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/* lw-support/src/lib/Thread/CompletionNotifier.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Completion object.
|
||||||
|
|
||||||
|
A completion object is a synchronisation device used in the following
|
||||||
|
manner:
|
||||||
|
- interested parties register for a notification,
|
||||||
|
- the task is begun,
|
||||||
|
- on completion, all registered parties are notified.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class CompletionNotifier {
|
||||||
|
private:
|
||||||
|
pthread_mutex_t mut;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
bool done;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
CompletionNotifier()
|
||||||
|
: done(false)
|
||||||
|
{
|
||||||
|
pthread_mutex_init(&mut, 0);
|
||||||
|
pthread_cond_init(&cond, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
~CompletionNotifier();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Signal completion.
|
||||||
|
|
||||||
|
This will wake all threads currently waiting on the completion
|
||||||
|
condition, and will record the object as completed. Any future calls
|
||||||
|
to the wait() function will automatically succeed.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline void signal()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
done = true;
|
||||||
|
pthread_cond_broadcast(&cond);
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Wait for completion.
|
||||||
|
|
||||||
|
Threads calling this function will block until the object is marked
|
||||||
|
as complete.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline void wait()
|
||||||
|
{
|
||||||
|
if(done) return; // avoid lock contention if possible
|
||||||
|
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
if(done) { // in case variable was changed before lock acquired
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_cond_wait(&cond, &mut);
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Thrown if a timeout occurs.
|
||||||
|
class Timeout : public ProgramException { };
|
||||||
|
|
||||||
|
/*! \brief Wait for completion, with a timeout.
|
||||||
|
|
||||||
|
\param maxTime The maximum amount of time to wait, in milliseconds.
|
||||||
|
\throws CompletionNotifier::Timeout if a timeout occurs.
|
||||||
|
|
||||||
|
Threads calling this function will block until the object is marked
|
||||||
|
as complete. If you pass an negative value for \a maxTime, then it
|
||||||
|
will wait indefinitely. A zero value will cause it to return
|
||||||
|
immediately.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void wait(int maxTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* lw-support/src/lib/Thread/Mutex.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mutex::Mutex(bool recursive)
|
||||||
|
{
|
||||||
|
// note the function calls here always succeed
|
||||||
|
// tell pthreads we want a "normal" (simple) mutex
|
||||||
|
pthread_mutexattr_t attr;
|
||||||
|
pthread_mutexattr_init(&attr);
|
||||||
|
if(recursive) pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||||
|
else pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||||
|
|
||||||
|
// initialise the mutex
|
||||||
|
pthread_mutex_init(&mut, &attr);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
pthread_mutexattr_destroy(&attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Mutex::~Mutex()
|
||||||
|
{
|
||||||
|
// destroy the mutex
|
||||||
|
pthread_mutex_destroy(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* lw-support/src/lib/Thread/Mutex.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Mutex.
|
||||||
|
|
||||||
|
This object may be one of two types: fast or recursive. A fast mutex
|
||||||
|
cannot be locked twice in the same thread. Attempting to do so will
|
||||||
|
result in undefined behaviour (most likely deadlock).
|
||||||
|
|
||||||
|
However, a recursive mutex can be locked twice in the same thread. It
|
||||||
|
must be unlocked an equal number of times. It will only become available
|
||||||
|
to other threads when each lock() call has been matched by an unlock()
|
||||||
|
call.
|
||||||
|
|
||||||
|
\warning Behaviour is undefined if a locked mutex is destroyed.
|
||||||
|
Behaviour is undefined if a thread which does not own the lock
|
||||||
|
tries to unlock the mutex.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Mutex {
|
||||||
|
private:
|
||||||
|
pthread_mutex_t mut;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param recursive Indicates whether the mutex should be fast or
|
||||||
|
recursive.
|
||||||
|
|
||||||
|
Creates the mutex object. Note that, once constructed, the type of
|
||||||
|
a mutex cannot be changed. Mutexes are always constructed unlocked.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Mutex(bool recursive = false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Destructor.
|
||||||
|
~Mutex();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Lock the mutex.
|
||||||
|
inline void lock()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Unlock the mutex.
|
||||||
|
inline void unlock()
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
/* lw-support/src/lib/Thread/MutexPadlock.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Mutex utility class for safe locking/unlocking.
|
||||||
|
|
||||||
|
A Mutex \e padlock will unlock its associated Mutex when it is destroyed.
|
||||||
|
This means that you can use it to lock a Mutex and then safely unlock it
|
||||||
|
regardless of how the function exit occurs. A padlock is constructed
|
||||||
|
unlocked, and you may call the lock and unlock functions at will.
|
||||||
|
|
||||||
|
\warning Behaviour is undefined if you attempt to unlock an
|
||||||
|
already-unlocked Mutex. This type of padlock is not recursive;
|
||||||
|
calling lock twice in succession will probably result in
|
||||||
|
deadlock. But see MutexPadlockRecursive.
|
||||||
|
|
||||||
|
\sa MutexHolder
|
||||||
|
|
||||||
|
*/
|
||||||
|
class MutexPadlock {
|
||||||
|
private:
|
||||||
|
Mutex& mut;
|
||||||
|
bool locked;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param m The Mutex to operate on.
|
||||||
|
|
||||||
|
Constructs the padlock object, but does not lock the Mutex.
|
||||||
|
|
||||||
|
*/
|
||||||
|
MutexPadlock(Mutex& m)
|
||||||
|
: mut(m), locked(false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Destructor (unlocks padlock, if locked).
|
||||||
|
|
||||||
|
If the padlock is locked, the destructor will unlock it. However, if
|
||||||
|
it is not locked, then nothing will happen.
|
||||||
|
|
||||||
|
*/
|
||||||
|
~MutexPadlock()
|
||||||
|
{
|
||||||
|
if(locked) mut.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Lock the Mutex.
|
||||||
|
inline void lock()
|
||||||
|
{
|
||||||
|
mut.lock();
|
||||||
|
locked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Unlock the Mutex.
|
||||||
|
inline void unlock()
|
||||||
|
{
|
||||||
|
mut.unlock();
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Mutex utility class for safe, recursive locking/unlocking.
|
||||||
|
|
||||||
|
A Mutex \e padlock will unlock its associated Mutex when it is destroyed.
|
||||||
|
This means that you can use it to lock a Mutex and then safely unlock it
|
||||||
|
regardless of how the function exit occurs. A padlock is constructed
|
||||||
|
unlocked, and you may call the lock and unlock functions at will.
|
||||||
|
|
||||||
|
Note that this padlock allows recursive calls. It even works with fast
|
||||||
|
Mutexes, because it keeps count internally itself.
|
||||||
|
|
||||||
|
\warning Behaviour is undefined if you attempt to unlock an
|
||||||
|
already-unlocked Mutex.
|
||||||
|
|
||||||
|
\sa MutexHolder
|
||||||
|
|
||||||
|
*/
|
||||||
|
class MutexPadlockRecursive {
|
||||||
|
private:
|
||||||
|
Mutex& mut;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param m The Mutex to operate on.
|
||||||
|
|
||||||
|
Constructs the padlock object, but does not lock the Mutex.
|
||||||
|
|
||||||
|
*/
|
||||||
|
MutexPadlockRecursive(Mutex& m)
|
||||||
|
: mut(m), count(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Destructor (unlocks padlock, if locked).
|
||||||
|
|
||||||
|
If the padlock is locked, the destructor will unlock it. However, if
|
||||||
|
it is not locked, then nothing will happen.
|
||||||
|
|
||||||
|
*/
|
||||||
|
~MutexPadlockRecursive()
|
||||||
|
{
|
||||||
|
if(count) mut.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Lock the Mutex.
|
||||||
|
inline void lock()
|
||||||
|
{
|
||||||
|
if(!count++) mut.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Unlock the Mutex.
|
||||||
|
inline void unlock()
|
||||||
|
{
|
||||||
|
if(!--count) mut.unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Mutex utility class for safe locking/unlocking.
|
||||||
|
|
||||||
|
On construction, this object locks its associated Mutex. On destruction,
|
||||||
|
it releases the lock. In essence, it is a less flexible (but less error
|
||||||
|
prone) Mutex padlock.
|
||||||
|
|
||||||
|
\sa MutexPadlock
|
||||||
|
|
||||||
|
*/
|
||||||
|
class MutexHolder {
|
||||||
|
private:
|
||||||
|
Mutex& mut;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param m The Mutex to lock.
|
||||||
|
|
||||||
|
Locks the Mutex.
|
||||||
|
|
||||||
|
*/
|
||||||
|
MutexHolder(Mutex& m)
|
||||||
|
: mut(m)
|
||||||
|
{
|
||||||
|
mut.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Destructor.
|
||||||
|
|
||||||
|
Unlocks the Mutex.
|
||||||
|
|
||||||
|
*/
|
||||||
|
~MutexHolder()
|
||||||
|
{
|
||||||
|
mut.unlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* lw-support/src/lib/Thread/Semaphore.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Semaphore::Semaphore(unsigned int init)
|
||||||
|
: val(init), waiting(0), err(false)
|
||||||
|
{
|
||||||
|
pthread_mutex_init(&mut, 0);
|
||||||
|
pthread_cond_init(&cond, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Semaphore::~Semaphore()
|
||||||
|
{
|
||||||
|
signalError();
|
||||||
|
while(true) {
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
if(!waiting) break;
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
pthread_yield();
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Semaphore::wait(int maxTime)
|
||||||
|
{
|
||||||
|
if(maxTime < 0) {
|
||||||
|
wait();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get current time
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
|
||||||
|
// compute absolute time
|
||||||
|
struct timespec ts;
|
||||||
|
ts.tv_sec = tv.tv_sec + (maxTime / 1000);
|
||||||
|
ts.tv_nsec = (tv.tv_usec * 1000) + (maxTime % 1000) * tenExp6;
|
||||||
|
if(ts.tv_nsec > tenExp9) {
|
||||||
|
++ts.tv_sec;
|
||||||
|
ts.tv_nsec -= tenExp9;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
++waiting;
|
||||||
|
|
||||||
|
while(!val && !err) {
|
||||||
|
switch(pthread_cond_timedwait(&cond, &mut, &ts)) {
|
||||||
|
case ETIMEDOUT: {
|
||||||
|
--waiting;
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
throw Timeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(err) {
|
||||||
|
--waiting;
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
--val;
|
||||||
|
--waiting;
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
/* lw-support/src/lib/Thread/Semaphore.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Semaphore object.
|
||||||
|
|
||||||
|
Supports the usual signal and wait operations, as well as manipulation
|
||||||
|
of the value. In addition, there is an error handling mechanism which
|
||||||
|
can be used to signal every thread waiting on the semaphore that it has
|
||||||
|
become invalid.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Semaphore {
|
||||||
|
private:
|
||||||
|
pthread_mutex_t mut;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
int val, waiting;
|
||||||
|
bool err;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param init The initial value of the semaphore.
|
||||||
|
|
||||||
|
Creates the semaphore object, and sets its initial value to
|
||||||
|
\a init.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Semaphore(unsigned int init = 0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Destructor.
|
||||||
|
|
||||||
|
Destroys the semaphore. Any threads currently waiting on the
|
||||||
|
semaphore receive an error signal, so it is safe to delete a
|
||||||
|
semaphore object while there are threads waiting on it.
|
||||||
|
|
||||||
|
\warning If a semaphore is deleted in the time between a thread
|
||||||
|
deciding to call the wait() function and the time the
|
||||||
|
actual system call is executed, behaviour is undefined.
|
||||||
|
|
||||||
|
*/
|
||||||
|
~Semaphore();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Semaphore error signal.
|
||||||
|
|
||||||
|
Thrown if a thread is waiting on the semaphore when an error
|
||||||
|
condition is signalled. This also occurs when the semaphore is
|
||||||
|
destroyed but some threads are waiting on it.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Error : public ProgramException { };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Thrown if a semaphore wait operation times out.
|
||||||
|
class Timeout : public ProgramException { };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Increment / post / signal the semaphore.
|
||||||
|
inline void signal()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
++val;
|
||||||
|
pthread_cond_signal(&cond);
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Decrement / wait on the semaphore.
|
||||||
|
|
||||||
|
\throws Error if an error is signalled or the semaphore destroyed.
|
||||||
|
|
||||||
|
Decrements the value of the semaphore. If the value is zero when
|
||||||
|
this function is called, the calling thread is instead put to sleep,
|
||||||
|
to be awoken when an error is signalled or the semaphore is posted.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline void wait()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
++waiting;
|
||||||
|
while(!val && !err) pthread_cond_wait(&cond, &mut);
|
||||||
|
if(err) {
|
||||||
|
--waiting;
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
throw Error();
|
||||||
|
}
|
||||||
|
--val;
|
||||||
|
--waiting;
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Decrement / wait on the semaphore, with a timeout.
|
||||||
|
|
||||||
|
\param maxTime The maximum length of time to wait before timing
|
||||||
|
out, in milliseconds.
|
||||||
|
\throws Error if an error is signalled or the semaphore destroyed.
|
||||||
|
\throws Timeout if the timeout is reached before the thread can
|
||||||
|
decrement the semaphore.
|
||||||
|
|
||||||
|
Decrements the value of the semaphore. If the value is zero when
|
||||||
|
this function is called, the calling thread is instead put to sleep,
|
||||||
|
to be awoken when an error is signalled or the semaphore is posted.
|
||||||
|
This version includes a timeout. If you pass a negative \a maxTime,
|
||||||
|
it will wait indefinitely; a zero value will cause the semaphore to
|
||||||
|
be decremented immediately or an error will be thrown.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void wait(int maxTime);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Signal error.
|
||||||
|
inline void signalError()
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mut);
|
||||||
|
err = true;
|
||||||
|
pthread_cond_broadcast(&cond);
|
||||||
|
pthread_mutex_unlock(&mut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Retrieve the semaphore's current value.
|
||||||
|
inline int value()
|
||||||
|
{
|
||||||
|
if(err) throw Error();
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
/* lw-support/src/lib/Thread/Thread.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const size_t Thread::minStackSize = PTHREAD_STACK_MIN;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void* Thread::taskWrapper(void* v)
|
||||||
|
{
|
||||||
|
Thread* self = (Thread*)v;
|
||||||
|
|
||||||
|
try {
|
||||||
|
self->semStart.wait();
|
||||||
|
self->run();
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
self->exception = true;
|
||||||
|
}
|
||||||
|
self->done = true;
|
||||||
|
self->sem.signalError();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Thread::Thread(size_t stackSize)
|
||||||
|
: done(false), exception(false)
|
||||||
|
{
|
||||||
|
pthread_attr_t attr;
|
||||||
|
pthread_attr_init(&attr);
|
||||||
|
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||||
|
if(stackSize) pthread_attr_setstacksize(&attr, std::min(stackSize, minStackSize));
|
||||||
|
|
||||||
|
int err = pthread_create(&thr, &attr, taskWrapper, this);
|
||||||
|
if(err) throw SystemError(err);
|
||||||
|
|
||||||
|
pthread_attr_destroy(&attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Thread::~Thread()
|
||||||
|
{
|
||||||
|
wait(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Thread::start()
|
||||||
|
{
|
||||||
|
semStart.signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Thread::signalQuit()
|
||||||
|
{
|
||||||
|
semStart.signalError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Thread::wait(int timeout) const
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
sem.wait(timeout);
|
||||||
|
}
|
||||||
|
catch(Semaphore::Error&) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Thread::sleep(const ElapsedTime& time)
|
||||||
|
{
|
||||||
|
struct timespec go, remain;
|
||||||
|
|
||||||
|
lldiv_t div = lldiv(time.to_ns(), tenExp9);
|
||||||
|
go.tv_sec = div.quot;
|
||||||
|
go.tv_nsec = div.rem;
|
||||||
|
while(go.tv_sec || go.tv_nsec) {
|
||||||
|
if(!nanosleep(&go, &remain)) break;
|
||||||
|
if(errno == EINTR) go = remain;
|
||||||
|
else throw lw::SystemError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Thread::dbg_backtrace()
|
||||||
|
{
|
||||||
|
void* array[200];
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
size = ::backtrace(array, sizeof(array) / sizeof(void*));
|
||||||
|
::backtrace_symbols_fd(array, size, 2); /* 2 = stderr */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
/* lw-support/src/lib/Thread/Thread.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Thread object.
|
||||||
|
|
||||||
|
This class provides multithreading facilities. You must override its
|
||||||
|
run() function. The thread is begun after the first call to the start()
|
||||||
|
function. The thread's execution finishes once run() returns.
|
||||||
|
|
||||||
|
The Thread object also contains some static members, yield() and
|
||||||
|
sleep(), that affect the \em calling thread.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Thread : public Uncopyable {
|
||||||
|
private:
|
||||||
|
pthread_t thr;
|
||||||
|
mutable Semaphore sem;
|
||||||
|
bool done, exception; // to store the system state
|
||||||
|
|
||||||
|
Semaphore semStart;
|
||||||
|
static void* taskWrapper(void*);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*! \brief Thread function (override this).
|
||||||
|
|
||||||
|
This method must be overridden to provide the thread's behaviour. It
|
||||||
|
may either exit normally or by throwing an exception; this can be
|
||||||
|
determined through the isDone() and isException() functions.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void run() = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param stackSize The size of the stack, in bytes. Must be at least
|
||||||
|
lw::Thread::minStackSize, or 0 for the system default.
|
||||||
|
\throws SystemError if a system error occurs.
|
||||||
|
|
||||||
|
Constructing a new thread object starts up a new thread. The thread
|
||||||
|
blocks on a semaphore until you call start() for the first time.
|
||||||
|
This is to avoid the situation where the thread calls a method or
|
||||||
|
refers to a variable in the derived class which may not have been
|
||||||
|
fully instantiated yet.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Thread(size_t stackSize = 0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Destructor.
|
||||||
|
|
||||||
|
When deleted, the thread object's destructor will free up any thread
|
||||||
|
resources. It will wait for the thread to quit first.
|
||||||
|
|
||||||
|
\warning The derived class's destructor will be called first, even
|
||||||
|
while the thread is still running. You should probably call
|
||||||
|
wait() in your derived class's destructor.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual ~Thread();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Start thread running.
|
||||||
|
void start();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Minimum stack size, in bytes.
|
||||||
|
static const size_t minStackSize;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Reports whether the thread has finished executing.
|
||||||
|
bool isDone() const
|
||||||
|
{
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reports whether the thread finished because of an exception.
|
||||||
|
bool isException() const
|
||||||
|
{
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Wait for the thread to finish, with a timeout.
|
||||||
|
|
||||||
|
\param timeout The maximum length of time to wait before timing
|
||||||
|
out, in milliseconds (negative means forever).
|
||||||
|
\throws Semaphore::Timeout if the timeout is reached before the
|
||||||
|
thread has finished.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void wait(int timeout) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Signal that the thread needs to quit.
|
||||||
|
|
||||||
|
This function, by default, does nothing. However, if you have some
|
||||||
|
specific way of signalling to your thread function that it should
|
||||||
|
quit, then you can use that method here.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void signalQuit();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Yields CPU to another thread.
|
||||||
|
|
||||||
|
Briefly yields the CPU to another thread, but does not put the
|
||||||
|
process to sleep.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static inline void yield()
|
||||||
|
{
|
||||||
|
pthread_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Causes the current thread to sleep for a length of time.
|
||||||
|
static void sleep(const ElapsedTime& time);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Prints a stack trace.
|
||||||
|
|
||||||
|
This is a debugging function that prints a stack trace onto stderr. It assumes that fd 2 is
|
||||||
|
stderr. It will also be confused by compiler optimisations. See the `backtrace()' libc function
|
||||||
|
for details.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static void dbg_backtrace();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,817 @@
|
||||||
|
/* lw-support/src/lib/Time/Date.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Date::fixupDate()
|
||||||
|
{
|
||||||
|
while(ord < 1) {
|
||||||
|
--year;
|
||||||
|
ord += isLeapYear(year) ? 366 : 365;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(ord > (isLeapYear(year) ? 366 : 365)) {
|
||||||
|
ord -= isLeapYear(year) ? 366 : 365;
|
||||||
|
++year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* This comes from the algorithm given on Wikipedia at
|
||||||
|
http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week */
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
inline int weekDayJan0(int year)
|
||||||
|
{
|
||||||
|
div_t cent = div(year, 100);
|
||||||
|
div_t leap = div(cent.quot, 4);
|
||||||
|
|
||||||
|
int centOffset = (3 - leap.rem) * 2;
|
||||||
|
int yrOffset = cent.rem + ((cent.rem + 3) >> 2) - 1;
|
||||||
|
|
||||||
|
return (centOffset + yrOffset) % 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date::Date(int year, int month, int day)
|
||||||
|
: year(year)
|
||||||
|
{
|
||||||
|
if(month < 1 || month > 12 || day < 1 || day > 31 ||
|
||||||
|
!(ord = (isLeapYear(year) ? leapMDtoOrd : normalMDtoOrd)[month - 1][day - 1]) )
|
||||||
|
{
|
||||||
|
throw BadDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date::Date(int year, int ordinalDay)
|
||||||
|
: year(year), ord(ordinalDay)
|
||||||
|
{
|
||||||
|
if(ord < 1 || ord > 366 || (ord == 366 && !isLeapYear(year))) {
|
||||||
|
throw BadDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date Date::now()
|
||||||
|
{
|
||||||
|
time_t t = time(0);
|
||||||
|
struct tm tt;
|
||||||
|
localtime_r(&t, &tt);
|
||||||
|
return Date(tt.tm_year + 1900, tt.tm_mon + 1, tt.tm_mday);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date Date::fromWeek(int year, int week, int weekDay)
|
||||||
|
{
|
||||||
|
// sanity checks
|
||||||
|
if(weekDay < 1 || weekDay > 7 || week < 1 || week > 53)
|
||||||
|
throw BadDate();
|
||||||
|
if(week == 53 && (weekDayJan0(year) != 3 &&
|
||||||
|
(!isLeapYear(year) || weekDayJan0(year) != 2)))
|
||||||
|
throw BadDate();
|
||||||
|
|
||||||
|
// find date of Monday, week 1
|
||||||
|
Date d(year, 1);
|
||||||
|
switch(weekDayJan0(year)) {
|
||||||
|
case 0: // Sunday
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // Monday
|
||||||
|
d -= 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
d -= 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
d -= 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
d += 3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
d += 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
d += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now move to correct week and day
|
||||||
|
d += (week - 1) * 7 + (weekDay - 1);
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int Date::getMonth() const
|
||||||
|
{
|
||||||
|
return (isLeapYear(year) ? leapOrdtoMD : normalOrdtoMD)[ord][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int Date::getDay() const
|
||||||
|
{
|
||||||
|
return (isLeapYear(year) ? leapOrdtoMD : normalOrdtoMD)[ord][1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int Date::getWeekDay() const
|
||||||
|
{
|
||||||
|
int wd = (weekDayJan0(year) + ord) % 7;
|
||||||
|
return wd ?: 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int Date::getWeek() const
|
||||||
|
{
|
||||||
|
if(isLeapYear(year)) return leapOrdtoWeek[weekDayJan0(year)][ord];
|
||||||
|
else return normalOrdtoWeek[weekDayJan0(year)][ord];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date& Date::operator+=(int numDays)
|
||||||
|
{
|
||||||
|
ord += numDays;
|
||||||
|
fixupDate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date& Date::operator-=(int numDays)
|
||||||
|
{
|
||||||
|
ord -= numDays;
|
||||||
|
fixupDate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date& Date::operator++()
|
||||||
|
{
|
||||||
|
++ord;
|
||||||
|
fixupDate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date Date::operator++(int)
|
||||||
|
{
|
||||||
|
Date copy(*this);
|
||||||
|
++ord;
|
||||||
|
fixupDate();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date& Date::operator--()
|
||||||
|
{
|
||||||
|
--ord;
|
||||||
|
fixupDate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Date Date::operator--(int)
|
||||||
|
{
|
||||||
|
Date copy(*this);
|
||||||
|
--ord;
|
||||||
|
fixupDate();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int Date::operator-(const Date& sub) const
|
||||||
|
{
|
||||||
|
int diff = ord - sub.ord;
|
||||||
|
|
||||||
|
for(int xYear = year; xYear < sub.year; ++xYear) {
|
||||||
|
diff -= isLeapYear(xYear) ? 366 : 365;
|
||||||
|
}
|
||||||
|
for(int yYear = year; yYear > sub.year; --yYear) {
|
||||||
|
diff += isLeapYear(yYear - 1) ? 366 : 365;
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* A good ISO8601 description is at Wikipedia:
|
||||||
|
http://en.wikipedia.org/wiki/ISO_8601 */
|
||||||
|
|
||||||
|
std::wstring Date::toString(bool extended, ISO8601Format format,
|
||||||
|
ISO8601Precision precision) const
|
||||||
|
{
|
||||||
|
// sanity check arguments
|
||||||
|
if(format == ISO8601FormatDetect) throw BadFormat();
|
||||||
|
switch(precision) {
|
||||||
|
case ISO8601PrecisionFull:
|
||||||
|
case ISO8601PrecisionDay:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601PrecisionMonth:
|
||||||
|
case ISO8601PrecisionYear:
|
||||||
|
if(format != ISO8601FormatCalendar) throw BadFormat();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601PrecisionWeek:
|
||||||
|
if(format != ISO8601FormatWeekDate) throw BadFormat();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw BadFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up output stream
|
||||||
|
std::wostringstream o;
|
||||||
|
o << std::setfill(L'0');
|
||||||
|
|
||||||
|
// deal with the weekdate format
|
||||||
|
if(format == ISO8601FormatWeekDate) {
|
||||||
|
int wk = getWeek();
|
||||||
|
if(!wk) {
|
||||||
|
o << std::setw(4) << (year + 1);
|
||||||
|
if(extended) o << '-';
|
||||||
|
o << "W01";
|
||||||
|
|
||||||
|
} else if(wk < 0) {
|
||||||
|
o << std::setw(4) << (year - 1);
|
||||||
|
if(extended) o << '-';
|
||||||
|
o << 'W' << std::setw(2) << (-wk);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
o << std::setw(4) << year;
|
||||||
|
if(extended) o << '-';
|
||||||
|
o << 'W' << std::setw(2) << wk;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if(precision == ISO8601PrecisionWeek) return o.str();
|
||||||
|
if(extended) o << '-';
|
||||||
|
o << getWeekDay();
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// output the year
|
||||||
|
o << std::setw(4) << year;
|
||||||
|
|
||||||
|
// deal with the ordinal format
|
||||||
|
if(format == ISO8601FormatOrdinal) {
|
||||||
|
if(extended) o << L'-';
|
||||||
|
o << std::setw(3) << getOrdinal();
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// output the month and (possibly) day
|
||||||
|
switch(precision) {
|
||||||
|
case ISO8601PrecisionFull:
|
||||||
|
case ISO8601PrecisionDay:
|
||||||
|
if(extended) o << L'-';
|
||||||
|
o << std::setw(2) << getMonth();
|
||||||
|
if(extended) o << L'-';
|
||||||
|
o << std::setw(2) << getDay();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601PrecisionMonth:
|
||||||
|
if(extended) o << L'-';
|
||||||
|
o << std::setw(2) << getMonth();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/* The ISO8601 date parsing subsystem uses a bunch of parallel state
|
||||||
|
machines. Each active state machine is fed a character; if it can't cope
|
||||||
|
with that character, the machine becomes inactive. On completion, each
|
||||||
|
machine is asked for a probability. The machine with highest probability
|
||||||
|
wins. */
|
||||||
|
class ISO8601DateParser {
|
||||||
|
public:
|
||||||
|
// Processes a char, returning false if the char doesn't fit the
|
||||||
|
// parser.
|
||||||
|
virtual bool input(int ch) = 0;
|
||||||
|
|
||||||
|
// Probability system used to determine which state machine should
|
||||||
|
// 'win' if several complete
|
||||||
|
enum Probability {
|
||||||
|
Impossible,
|
||||||
|
Unlikely,
|
||||||
|
Likely,
|
||||||
|
Definite
|
||||||
|
};
|
||||||
|
virtual Probability getProbability() = 0;
|
||||||
|
|
||||||
|
// Return a date object.
|
||||||
|
virtual Date getDate(const std::wstring& str) = 0;
|
||||||
|
|
||||||
|
virtual ~ISO8601DateParser() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISO8601DateParser_CalendarBasic : public ISO8601DateParser {
|
||||||
|
private:
|
||||||
|
int numDigits;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISO8601DateParser_CalendarBasic()
|
||||||
|
: numDigits(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual bool input(int ch)
|
||||||
|
{
|
||||||
|
switch(ch) {
|
||||||
|
case '0' ... '9':
|
||||||
|
++numDigits;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
if(!numDigits) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Probability getProbability()
|
||||||
|
{
|
||||||
|
return (numDigits < 8) ? Impossible : Likely;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Date getDate(const std::wstring& str)
|
||||||
|
{
|
||||||
|
int strLen = str.length();
|
||||||
|
return Date(strToInt32(str.substr(0, strLen - 4)),
|
||||||
|
strToInt32(str.substr(strLen - 4, 2)),
|
||||||
|
strToInt32(str.substr(strLen - 2, 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ISO8601DateParser_CalendarBasic() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISO8601DateParser_CalendarExtended : public ISO8601DateParser {
|
||||||
|
private:
|
||||||
|
int numDigits;
|
||||||
|
enum {
|
||||||
|
stateNothing,
|
||||||
|
stateYear,
|
||||||
|
stateMonth,
|
||||||
|
stateDay
|
||||||
|
}state;
|
||||||
|
int year, month, day;
|
||||||
|
bool yearNegative;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISO8601DateParser_CalendarExtended()
|
||||||
|
: numDigits(0), state(stateNothing),
|
||||||
|
year(0), month(0), day(0), yearNegative(false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual bool input(int ch)
|
||||||
|
{
|
||||||
|
switch(state) {
|
||||||
|
case stateNothing:
|
||||||
|
state = stateYear;
|
||||||
|
switch(ch) {
|
||||||
|
case '-':
|
||||||
|
yearNegative = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '0' ... '9':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// fall through
|
||||||
|
|
||||||
|
case stateYear:
|
||||||
|
if(ch == '-' && numDigits >= 4) {
|
||||||
|
state = stateMonth;
|
||||||
|
numDigits = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
year *= 10;
|
||||||
|
year += ch - '0';
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case stateMonth:
|
||||||
|
if(numDigits == 2) {
|
||||||
|
state = stateDay;
|
||||||
|
numDigits = 0;
|
||||||
|
return ch == '-';
|
||||||
|
}
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
month *= 10;
|
||||||
|
month += ch - '0';
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case stateDay:
|
||||||
|
if(numDigits == 2) return false;
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
day *= 10;
|
||||||
|
day += ch - '0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Probability getProbability()
|
||||||
|
{
|
||||||
|
return (state == stateDay && numDigits == 2) ?
|
||||||
|
Definite : Impossible;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Date getDate(const std::wstring&)
|
||||||
|
{
|
||||||
|
return Date(year, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ISO8601DateParser_CalendarExtended() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISO8601DateParser_OrdinalBasic : public ISO8601DateParser {
|
||||||
|
private:
|
||||||
|
int numDigits;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISO8601DateParser_OrdinalBasic()
|
||||||
|
: numDigits(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual bool input(int ch)
|
||||||
|
{
|
||||||
|
switch(ch) {
|
||||||
|
case '0' ... '9':
|
||||||
|
++numDigits;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
if(!numDigits) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Probability getProbability()
|
||||||
|
{
|
||||||
|
if(numDigits < 7) return Impossible;
|
||||||
|
return (numDigits == 7) ? Likely : Unlikely;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Date getDate(const std::wstring& str)
|
||||||
|
{
|
||||||
|
int strLen = str.length();
|
||||||
|
return Date(strToInt32(str.substr(0, strLen - 3)),
|
||||||
|
strToInt32(str.substr(strLen - 3, 3)));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ISO8601DateParser_OrdinalBasic() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISO8601DateParser_OrdinalExtended : public ISO8601DateParser {
|
||||||
|
private:
|
||||||
|
int numDigits;
|
||||||
|
enum {
|
||||||
|
stateNothing,
|
||||||
|
stateYear,
|
||||||
|
stateDay
|
||||||
|
}state;
|
||||||
|
int year, day;
|
||||||
|
bool yearNegative;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISO8601DateParser_OrdinalExtended()
|
||||||
|
: numDigits(0), state(stateNothing),
|
||||||
|
year(0), day(0), yearNegative(false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual bool input(int ch)
|
||||||
|
{
|
||||||
|
switch(state) {
|
||||||
|
case stateNothing:
|
||||||
|
state = stateYear;
|
||||||
|
switch(ch) {
|
||||||
|
case '-':
|
||||||
|
yearNegative = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '0' ... '9':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// fall through
|
||||||
|
|
||||||
|
case stateYear:
|
||||||
|
if(ch == '-' && numDigits >= 4) {
|
||||||
|
state = stateDay;
|
||||||
|
numDigits = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
year *= 10;
|
||||||
|
year += ch - '0';
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case stateDay:
|
||||||
|
if(numDigits == 3) return false;
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
day *= 10;
|
||||||
|
day += ch - '0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Probability getProbability()
|
||||||
|
{
|
||||||
|
return (state == stateDay && numDigits == 3) ?
|
||||||
|
Definite : Impossible;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Date getDate(const std::wstring&)
|
||||||
|
{
|
||||||
|
return Date(year, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ISO8601DateParser_OrdinalExtended() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISO8601DateParser_WeekDateBasic : public ISO8601DateParser {
|
||||||
|
private:
|
||||||
|
int numDigits;
|
||||||
|
enum {
|
||||||
|
stateNothing,
|
||||||
|
stateYear,
|
||||||
|
stateWeeks
|
||||||
|
}state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISO8601DateParser_WeekDateBasic()
|
||||||
|
: numDigits(0), state(stateNothing)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual bool input(int ch)
|
||||||
|
{
|
||||||
|
switch(state) {
|
||||||
|
case stateNothing:
|
||||||
|
state = stateYear;
|
||||||
|
switch(ch) {
|
||||||
|
case '-':
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '0' ... '9':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// fall through
|
||||||
|
|
||||||
|
case stateYear:
|
||||||
|
if(ch == 'W' && numDigits >= 4) {
|
||||||
|
state = stateWeeks;
|
||||||
|
numDigits = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case stateWeeks:
|
||||||
|
if(numDigits++ == 3) return false;
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Probability getProbability()
|
||||||
|
{
|
||||||
|
return (state == stateWeeks && numDigits == 3) ?
|
||||||
|
Definite : Impossible;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Date getDate(const std::wstring& str)
|
||||||
|
{
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
return Date::fromWeek(strToInt32(str.substr(0, strLen - 4)),
|
||||||
|
strToInt32(str.substr(strLen - 3, 2)),
|
||||||
|
str[strLen - 1] - '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ISO8601DateParser_WeekDateBasic() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISO8601DateParser_WeekDateExtended : public ISO8601DateParser {
|
||||||
|
private:
|
||||||
|
int numDigits;
|
||||||
|
enum {
|
||||||
|
stateNothing,
|
||||||
|
stateYear,
|
||||||
|
stateWeekDash,
|
||||||
|
stateWeek,
|
||||||
|
stateDay
|
||||||
|
}state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISO8601DateParser_WeekDateExtended()
|
||||||
|
: numDigits(0), state(stateNothing)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual bool input(int ch)
|
||||||
|
{
|
||||||
|
switch(state) {
|
||||||
|
case stateNothing:
|
||||||
|
state = stateYear;
|
||||||
|
switch(ch) {
|
||||||
|
case '-':
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '0' ... '9':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// fall through
|
||||||
|
|
||||||
|
case stateYear:
|
||||||
|
if(ch == '-' && numDigits >= 4) {
|
||||||
|
state = stateWeekDash;
|
||||||
|
numDigits = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case stateWeekDash:
|
||||||
|
state = stateWeek;
|
||||||
|
return ch == 'W';
|
||||||
|
|
||||||
|
case stateWeek:
|
||||||
|
if(numDigits == 2) {
|
||||||
|
state = stateDay;
|
||||||
|
numDigits = 0;
|
||||||
|
return ch == '-';
|
||||||
|
}
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case stateDay:
|
||||||
|
if(numDigits == 1) return false;
|
||||||
|
if(ch < '0' || ch > '9') return false;
|
||||||
|
++numDigits;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Probability getProbability()
|
||||||
|
{
|
||||||
|
return (state == stateDay && numDigits == 1) ?
|
||||||
|
Definite : Impossible;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Date getDate(const std::wstring& str)
|
||||||
|
{
|
||||||
|
std::wstring::size_type strLen = str.length();
|
||||||
|
return Date::fromWeek(strToInt32(str.substr(0, strLen - 6)),
|
||||||
|
strToInt32(str.substr(strLen - 4, 2)),
|
||||||
|
str[strLen - 1] - '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~ISO8601DateParser_WeekDateExtended() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Date Date::fromString(const std::wstring& str, ISO8601Format format)
|
||||||
|
{
|
||||||
|
// set up a std::list of participating state machines
|
||||||
|
ISO8601DateParser_CalendarBasic machineCalendarBasic;
|
||||||
|
ISO8601DateParser_CalendarExtended machineCalendarExtended;
|
||||||
|
ISO8601DateParser_OrdinalBasic machineOrdinalBasic;
|
||||||
|
ISO8601DateParser_OrdinalExtended machineOrdinalExtended;
|
||||||
|
ISO8601DateParser_WeekDateBasic machineWeekDateBasic;
|
||||||
|
ISO8601DateParser_WeekDateExtended machineWeekDateExtended;
|
||||||
|
|
||||||
|
std::vector<ISO8601DateParser*> stateMachines;
|
||||||
|
switch(format) {
|
||||||
|
case ISO8601FormatDetect:
|
||||||
|
stateMachines.push_back(&machineCalendarBasic);
|
||||||
|
stateMachines.push_back(&machineCalendarExtended);
|
||||||
|
stateMachines.push_back(&machineOrdinalBasic);
|
||||||
|
stateMachines.push_back(&machineOrdinalExtended);
|
||||||
|
stateMachines.push_back(&machineWeekDateBasic);
|
||||||
|
stateMachines.push_back(&machineWeekDateExtended);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601FormatCalendar:
|
||||||
|
stateMachines.push_back(&machineCalendarBasic);
|
||||||
|
stateMachines.push_back(&machineCalendarExtended);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601FormatOrdinal:
|
||||||
|
stateMachines.push_back(&machineOrdinalBasic);
|
||||||
|
stateMachines.push_back(&machineOrdinalExtended);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601FormatWeekDate:
|
||||||
|
stateMachines.push_back(&machineWeekDateBasic);
|
||||||
|
stateMachines.push_back(&machineWeekDateExtended);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass the std::string through all the state machines
|
||||||
|
int numMachines = stateMachines.size();
|
||||||
|
std::wstring::size_type pos = 0, len = str.length();
|
||||||
|
|
||||||
|
for(pos = 0; pos < len; ++pos) {
|
||||||
|
int ch = str[pos];
|
||||||
|
|
||||||
|
for(int iter = 0; iter < numMachines; ++iter) {
|
||||||
|
if(!stateMachines[iter]->input(ch)) {
|
||||||
|
// this machine can't process the input any more
|
||||||
|
stateMachines.erase(stateMachines.begin() + iter);
|
||||||
|
--iter;
|
||||||
|
--numMachines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose the machine with the highest likelihood
|
||||||
|
ISO8601DateParser* bestMachine = 0;
|
||||||
|
ISO8601DateParser::Probability bestProbability = ISO8601DateParser::Impossible;
|
||||||
|
for(int iter = 0; iter < numMachines; ++iter) {
|
||||||
|
ISO8601DateParser::Probability p =
|
||||||
|
stateMachines[iter]->getProbability();
|
||||||
|
if(p > bestProbability) {
|
||||||
|
bestProbability = p;
|
||||||
|
bestMachine = stateMachines[iter];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the date object or fail
|
||||||
|
if(bestMachine) return bestMachine->getDate(str);
|
||||||
|
|
||||||
|
throw ParseError(L"Cannot interpet as ISO8601 date", str, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wostream& operator<<(std::wostream& ostr, const Date& date)
|
||||||
|
{
|
||||||
|
ostr << date.toString();
|
||||||
|
return ostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
|
@ -0,0 +1,288 @@
|
||||||
|
/* lw-support/src/lib/Time/Date.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief A Gregorian date.
|
||||||
|
|
||||||
|
This class holds a single date in the Gregorian calendar. It makes no
|
||||||
|
provision for dates in other formats (i.e. it uses the Proleptic
|
||||||
|
Gregorian Calendar). It also provides for conversion to/from ISO8601
|
||||||
|
formats. It uses astronomical years.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Date {
|
||||||
|
private:
|
||||||
|
int year, ord;
|
||||||
|
void fixupDate(); // fix up after arithmetic
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Constructors etc. ///////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Constructor (calendar date).
|
||||||
|
|
||||||
|
\param year The astronimical year.
|
||||||
|
\param month Month of the year (1-12).
|
||||||
|
\param day Day of the month (1-31).
|
||||||
|
\throws BadDate if \a day or \a month are invalid.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Date(int year, int month, int day);
|
||||||
|
|
||||||
|
/*! \brief Constructor (ordinal date).
|
||||||
|
|
||||||
|
\param year The astronimical year.
|
||||||
|
\param ordinalDay Day of the year (1-366).
|
||||||
|
\throws BadDate if \a ordinalDay is invalid.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Date(int year, int ordinalDay);
|
||||||
|
|
||||||
|
/*! \brief Make object from ISO8601 weekday form.
|
||||||
|
|
||||||
|
\param year The week's year.
|
||||||
|
\param week The week number (1-53).
|
||||||
|
\param weekDay The week day (1-7).
|
||||||
|
\throws BadDate if \a week or \a weekDay are invalid.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static Date fromWeek(int year, int week, int weekDay);
|
||||||
|
|
||||||
|
/// Copy constructor.
|
||||||
|
Date(const Date& date)
|
||||||
|
: year(date.year), ord(date.ord)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Assignment operator.
|
||||||
|
Date& operator=(const Date& date)
|
||||||
|
{
|
||||||
|
year = date.year;
|
||||||
|
ord = date.ord;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create object from current calendar day.
|
||||||
|
static Date now();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // String functions ////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Thrown when format specifiers don't make sense, etc.
|
||||||
|
class BadFormat : public ProgramException { };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Convert to std::string (ISO8601) representation.
|
||||||
|
|
||||||
|
\param format Which ISO8601 format to use.
|
||||||
|
\param precision Which precision to use.
|
||||||
|
\param extended Whether to use basic or extended notation.
|
||||||
|
\throws BadFormat if \a format and/or \a precision don't make sense
|
||||||
|
for this type.
|
||||||
|
\returns The ISO8601 representation of the date.
|
||||||
|
|
||||||
|
This function converts the date into its ISO8601 notation, with a
|
||||||
|
variety of possible formatting options.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring toString(bool extended = false,
|
||||||
|
ISO8601Format format = ISO8601FormatCalendar,
|
||||||
|
ISO8601Precision precision = ISO8601PrecisionFull) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse an ISO8601 date.
|
||||||
|
|
||||||
|
\param str The ISO8601 std::string to parse.
|
||||||
|
\param format Which ISO8601 format to use.
|
||||||
|
\throws ParseError if the std::string cannot be parsed.
|
||||||
|
\throws BadDate if the date is parsed correctly but is not valid.
|
||||||
|
\returns A newly-created date object.
|
||||||
|
|
||||||
|
\note There is some ambiguity between the ISO8601 basic forms, since
|
||||||
|
an 8-digit sequence could either be a basic calendar form or a
|
||||||
|
basic ordinal form with a 5-digit year. For this reason, all
|
||||||
|
sequences of 8 or more digits are interpreted as basic calendar
|
||||||
|
form if \a format is \a ISO8601FormatDetect.
|
||||||
|
|
||||||
|
\note The year part is assumed to always be at least four digits.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static Date fromString(const std::wstring& str,
|
||||||
|
ISO8601Format format = ISO8601FormatDetect);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Access functions ////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns the year.
|
||||||
|
inline int getYear() const
|
||||||
|
{ return year; }
|
||||||
|
|
||||||
|
/// Returns the month.
|
||||||
|
int getMonth() const;
|
||||||
|
|
||||||
|
/// Returns the day.
|
||||||
|
int getDay() const;
|
||||||
|
|
||||||
|
/// Returns the ordinal day.
|
||||||
|
inline int getOrdinal() const
|
||||||
|
{
|
||||||
|
return ord;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the week day (1 = Monday, 7 = Sunday).
|
||||||
|
int getWeekDay() const;
|
||||||
|
|
||||||
|
/*! \brief Returns the week number.
|
||||||
|
|
||||||
|
\returns The ISO8601 week number.
|
||||||
|
\retval 0 if this is week 1 of next year.
|
||||||
|
\retval -n if this is week \a n of last year.
|
||||||
|
|
||||||
|
Returns the day's week number within the year. There are a couple of
|
||||||
|
special return values; a zero means that this is in fact week 1 of
|
||||||
|
next year, and a negative number \a -n means that this is week \a n
|
||||||
|
of last year.
|
||||||
|
|
||||||
|
*/
|
||||||
|
int getWeek() const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Modify functions / operators ////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Advance a number of days.
|
||||||
|
|
||||||
|
\param numDays The number of days to advance by. Can be negative.
|
||||||
|
\returns A reference to the modified object.
|
||||||
|
|
||||||
|
Moves the date forward a number of days, correctly accounting for
|
||||||
|
month and year boundaries.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Date& operator+=(int numDays);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Roll back a number of days.
|
||||||
|
|
||||||
|
\param numDays The number of days to reverse by. Can be negative.
|
||||||
|
\returns A reference to the modified object.
|
||||||
|
|
||||||
|
Moves the date backward a number of days, correctly accounting for
|
||||||
|
month and year boundaries.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Date& operator-=(int numDays);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Advance a day.
|
||||||
|
Date& operator++();
|
||||||
|
|
||||||
|
/// Advance a day.
|
||||||
|
Date operator++(int);
|
||||||
|
|
||||||
|
/// Go back a day.
|
||||||
|
Date& operator--();
|
||||||
|
|
||||||
|
/// Go back a day.
|
||||||
|
Date operator--(int);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Comparison operators ////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Get difference in days.
|
||||||
|
|
||||||
|
\param date The date to subtract from this one.
|
||||||
|
\returns The difference, in days, between two dates.
|
||||||
|
|
||||||
|
*/
|
||||||
|
int operator-(const Date& date) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns true if two dates are equal.
|
||||||
|
inline bool operator==(const Date& date) const
|
||||||
|
{
|
||||||
|
return (year == date.year) && (ord == date.ord);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if two dates are not equal.
|
||||||
|
inline bool operator!=(const Date& date) const
|
||||||
|
{
|
||||||
|
return (year != date.year) || (ord != date.ord);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if \a date is less than \a this.
|
||||||
|
inline bool operator<(const Date& date) const
|
||||||
|
{
|
||||||
|
return (year < date.year) || (year == date.year &&
|
||||||
|
(ord < date.ord));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if \a date is less than or equal to \a this.
|
||||||
|
inline bool operator<=(const Date& date) const
|
||||||
|
{
|
||||||
|
return (year < date.year) || (year == date.year &&
|
||||||
|
(ord <= date.ord));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if \a date is greater than \a this.
|
||||||
|
inline bool operator>(const Date& date) const
|
||||||
|
{
|
||||||
|
return (year > date.year) || (year == date.year &&
|
||||||
|
(ord > date.ord));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if \a date is greater than or equal to \a this.
|
||||||
|
inline bool operator>=(const Date& date) const
|
||||||
|
{
|
||||||
|
return (year > date.year) || (year == date.year &&
|
||||||
|
(ord >= date.ord));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Utility functions ///////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns true if \a year is a leap year.
|
||||||
|
static inline bool isLeapYear(int year)
|
||||||
|
{
|
||||||
|
if(year < 0) year *= -1;
|
||||||
|
return !(year % 400) || ((year % 100) && !(year & 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns number of days in the given \a month and \a year.
|
||||||
|
static inline int daysInMonth(int year, int month)
|
||||||
|
{
|
||||||
|
switch(month) {
|
||||||
|
case 4:
|
||||||
|
case 6:
|
||||||
|
case 9:
|
||||||
|
case 11:
|
||||||
|
return 30;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
return (isLeapYear(year) ? 29 : 28);
|
||||||
|
}
|
||||||
|
return 31;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream insertion operator.
|
||||||
|
std::wostream& operator<<(std::wostream&, const Date&);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,108 @@
|
||||||
|
/* lw-support/src/lib/Time/DateTime.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring DateTime::toString(bool extended, bool decimalTime,
|
||||||
|
ISO8601Format dateFormat, ISO8601Precision precision) const
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
switch(precision) {
|
||||||
|
case ISO8601PrecisionYear:
|
||||||
|
case ISO8601PrecisionWeek:
|
||||||
|
case ISO8601PrecisionMonth:
|
||||||
|
case ISO8601PrecisionDay:
|
||||||
|
o << date.toString(extended, dateFormat, precision);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601PrecisionHour:
|
||||||
|
case ISO8601PrecisionMinute:
|
||||||
|
case ISO8601PrecisionSecond:
|
||||||
|
case ISO8601PrecisionFull:
|
||||||
|
o << date.toString(extended, dateFormat, ISO8601PrecisionFull);
|
||||||
|
o << L'T';
|
||||||
|
o << time.toString(extended, precision, decimalTime);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DateTime DateTime::fromString(const std::wstring& str,
|
||||||
|
ISO8601Format dateFormat)
|
||||||
|
{
|
||||||
|
std::wstring::size_type timeStart = str.find('T');
|
||||||
|
if(timeStart == std::string::npos) {
|
||||||
|
return DateTime(Date::fromString(str, dateFormat));
|
||||||
|
}
|
||||||
|
return DateTime(
|
||||||
|
Date::fromString(str.substr(0, timeStart), dateFormat),
|
||||||
|
DayTime::fromString(str.substr(timeStart + 1, std::wstring::npos))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DateTime& DateTime::addNanoSeconds(int64_t ns)
|
||||||
|
{
|
||||||
|
int dayDelta;
|
||||||
|
time.addNanoSeconds(ns, dayDelta);
|
||||||
|
date += dayDelta;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DateTime& DateTime::addSeconds(int s)
|
||||||
|
{
|
||||||
|
int dayDelta;
|
||||||
|
time.addSeconds(s, dayDelta);
|
||||||
|
date += dayDelta;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DateTime& DateTime::addMinutes(int m)
|
||||||
|
{
|
||||||
|
int dayDelta;
|
||||||
|
time.addMinutes(m, dayDelta);
|
||||||
|
date += dayDelta;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DateTime& DateTime::addHours(int h)
|
||||||
|
{
|
||||||
|
int dayDelta;
|
||||||
|
time.addHours(h, dayDelta);
|
||||||
|
date += dayDelta;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DateTime& DateTime::addDays(int d)
|
||||||
|
{
|
||||||
|
date += d;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wostream& operator<<(std::wostream& ostr, const DateTime& d)
|
||||||
|
{
|
||||||
|
ostr << d.toString();
|
||||||
|
return ostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
/* lw-support/src/lib/Time/DateTime.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Stores a date and a time.
|
||||||
|
|
||||||
|
This class uses (internally) a DayTime and a Date class. These allow it
|
||||||
|
to represent pretty much arbitrary timescales (well, about +/- 2e9 years
|
||||||
|
with the precision of a nanosecond).
|
||||||
|
|
||||||
|
Altough you can individually operate on the internal Date and DayTime
|
||||||
|
instances, this isn't always a good idea. For instance, adding 24 hours
|
||||||
|
onto the DayTime object won't alter the Date. Instead, use the
|
||||||
|
addHours() function and equivalents.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class DateTime {
|
||||||
|
protected:
|
||||||
|
/// The day.
|
||||||
|
Date date;
|
||||||
|
|
||||||
|
/// The time.
|
||||||
|
DayTime time;
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Constructors, etc. //////////////////////////////////////
|
||||||
|
|
||||||
|
/// Construct from date and time.
|
||||||
|
DateTime(const Date& date, const DayTime& time)
|
||||||
|
: date(date), time(time)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Construct (at midnight) from date.
|
||||||
|
DateTime(const Date& date)
|
||||||
|
: date(date), time(0, 0, 0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Copy constructor.
|
||||||
|
DateTime(const DateTime& dateTime)
|
||||||
|
: date(dateTime.date), time(dateTime.time)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Assignment operator.
|
||||||
|
DateTime& operator=(const DateTime& dateTime)
|
||||||
|
{
|
||||||
|
date = dateTime.date;
|
||||||
|
time = dateTime.time;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct from calendar time.
|
||||||
|
static DateTime now()
|
||||||
|
{ return DateTime(Date::now(), DayTime::now()); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // String routines /////////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Convert to ISO8601 std::string.
|
||||||
|
|
||||||
|
\param extended \a true if you want to use the extended form,
|
||||||
|
\a false if not.
|
||||||
|
\param decimalTime \a true if you want to use the decimal time
|
||||||
|
notation.
|
||||||
|
\param precision How precise you want the converted std::string to be.
|
||||||
|
\param dateFormat Which format to use for the date.
|
||||||
|
|
||||||
|
Converts the entire date/time into an ISO8601-formatted std::string. See
|
||||||
|
documentation for Date and DayTime for more information about the
|
||||||
|
parameters and their results.
|
||||||
|
|
||||||
|
\sa lw::Date::toString(), lw::DayTime::toString()
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring toString(bool extended = false, bool decimalTime = false,
|
||||||
|
ISO8601Format dateFormat = ISO8601FormatCalendar,
|
||||||
|
ISO8601Precision precision = ISO8601PrecisionFull) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Create from ISO8601 std::string.
|
||||||
|
|
||||||
|
\param str The ISO8601 std::string to parse.
|
||||||
|
\param dateFormat Which ISO8601 format to use.
|
||||||
|
\throws ParseError if the std::string cannot be parsed.
|
||||||
|
\throws BadDate if the date is parsed correctly but is not valid.
|
||||||
|
\returns A newly-created date object.
|
||||||
|
|
||||||
|
Attempts to parse \a str as an ISO8601 date/time std::string. If the time
|
||||||
|
part is not present, the time will be set to midnight.
|
||||||
|
|
||||||
|
\note There is some ambiguity between the ISO8601 basic date forms,
|
||||||
|
since an 8-digit sequence could either be a basic calendar form
|
||||||
|
or a basic ordinal form with a 5-digit year. For this reason,
|
||||||
|
all sequences of 8 or more digits are interpreted as basic
|
||||||
|
calendar form if \a dateFormat is \a ISO8601FormatDetect.
|
||||||
|
|
||||||
|
\note The year part is assumed to always be at least four digits.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static DateTime fromString(const std::wstring& str,
|
||||||
|
ISO8601Format dateFormat = ISO8601FormatDetect);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Access functions ////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Get date component.
|
||||||
|
const Date& getDate() const
|
||||||
|
{ return date; }
|
||||||
|
|
||||||
|
/// Get date component.
|
||||||
|
Date& getDate()
|
||||||
|
{ return date; }
|
||||||
|
|
||||||
|
/// Get time component.
|
||||||
|
const DayTime& getTime() const
|
||||||
|
{ return time; }
|
||||||
|
|
||||||
|
/// Get time component.
|
||||||
|
DayTime& getTime()
|
||||||
|
{ return time; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Modify functions ////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Set date component.
|
||||||
|
DateTime& setDate(const Date& date)
|
||||||
|
{
|
||||||
|
this->date = date;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set time component.
|
||||||
|
DateTime& setTime(const DayTime& time)
|
||||||
|
{
|
||||||
|
this->time = time;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set date and time.
|
||||||
|
DateTime& set(const Date& date, const DayTime& time)
|
||||||
|
{
|
||||||
|
this->date = date;
|
||||||
|
this->time = time;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Add a number of nanoseconds.
|
||||||
|
DateTime& addNanoSeconds(int64_t ns);
|
||||||
|
|
||||||
|
/// Add a number of seconds.
|
||||||
|
DateTime& addSeconds(int s);
|
||||||
|
|
||||||
|
/// Add a number of minutes.
|
||||||
|
DateTime& addMinutes(int m);
|
||||||
|
|
||||||
|
/// Add a number of hours.
|
||||||
|
DateTime& addHours(int h);
|
||||||
|
|
||||||
|
/// Add a number of days.
|
||||||
|
DateTime& addDays(int d);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Operators ///////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns true if the dates are equal.
|
||||||
|
bool operator==(const DateTime& dateTime) const
|
||||||
|
{ return date == dateTime.date && time == dateTime.time; }
|
||||||
|
|
||||||
|
/// Returns true if the dates are not equal.
|
||||||
|
bool operator!=(const DateTime& dateTime) const
|
||||||
|
{ return date != dateTime.date || time != dateTime.time; }
|
||||||
|
|
||||||
|
/// Returns true if less than \a dateTime.
|
||||||
|
bool operator<(const DateTime& dateTime) const
|
||||||
|
{
|
||||||
|
return date < dateTime.date ||
|
||||||
|
(date == dateTime.date && time < dateTime.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if less than or equal to \a dateTime.
|
||||||
|
bool operator<=(const DateTime& dateTime) const
|
||||||
|
{
|
||||||
|
return date < dateTime.date ||
|
||||||
|
(date == dateTime.date && time <= dateTime.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if greater than \a dateTime.
|
||||||
|
bool operator>(const DateTime& dateTime) const
|
||||||
|
{
|
||||||
|
return date > dateTime.date ||
|
||||||
|
(date == dateTime.date && time > dateTime.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if greater than or equal to \a dateTime.
|
||||||
|
bool operator>=(const DateTime& dateTime) const
|
||||||
|
{
|
||||||
|
return date > dateTime.date ||
|
||||||
|
(date == dateTime.date && time >= dateTime.time);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream insertion operator.
|
||||||
|
std::wostream& operator<<(std::wostream&, const DateTime&);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,264 @@
|
||||||
|
/* lw-support/src/lib/Time/DayTime.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DayTime& DayTime::wrap(int& dayDelta)
|
||||||
|
{
|
||||||
|
int d = 0;
|
||||||
|
dayDelta = 0;
|
||||||
|
if(_ns < 0) {
|
||||||
|
_ns += tenExp9;
|
||||||
|
_s--;
|
||||||
|
}
|
||||||
|
if(_ns >= tenExp9) {
|
||||||
|
_ns -= tenExp9;
|
||||||
|
_s++;
|
||||||
|
}
|
||||||
|
if(_s < 0) {
|
||||||
|
d = 1 + (_s / -60);
|
||||||
|
_s += d * 60;
|
||||||
|
_m -= d;
|
||||||
|
}
|
||||||
|
if(_s >= 60) {
|
||||||
|
d = _s / 60;
|
||||||
|
_s -= d * 60;
|
||||||
|
_m += d;
|
||||||
|
}
|
||||||
|
if(_m < 0) {
|
||||||
|
d = 1 + (_m / -60);
|
||||||
|
_m += d * 60;
|
||||||
|
_h -= d;
|
||||||
|
}
|
||||||
|
if(_m >= 60) {
|
||||||
|
d = _m / 60;
|
||||||
|
_m -= d * 60;
|
||||||
|
_h += d;
|
||||||
|
}
|
||||||
|
if(_h < 0) {
|
||||||
|
d = 1 + (_h / -24);
|
||||||
|
_h += d * 24;
|
||||||
|
dayDelta = -d;
|
||||||
|
}
|
||||||
|
if(_h >= 24) {
|
||||||
|
d = _h / 24;
|
||||||
|
_h -= d * 24;
|
||||||
|
dayDelta = d;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring DayTime::toString(bool extended, ISO8601Precision precision,
|
||||||
|
bool decimal) const
|
||||||
|
{
|
||||||
|
std::wostringstream o, o2;
|
||||||
|
o << std::setfill(L'0');
|
||||||
|
o2 << std::fixed;
|
||||||
|
|
||||||
|
switch(precision) {
|
||||||
|
case ISO8601PrecisionHour:
|
||||||
|
o << std::setw(2) << _h;
|
||||||
|
if(decimal) {
|
||||||
|
double f = (_m / 60.0 + _s / 3600.0) + _ns / 3600e9;
|
||||||
|
o2 << f;
|
||||||
|
o << (o2.str().substr(1, std::wstring::npos));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601PrecisionMinute:
|
||||||
|
o << std::setw(2) << _h;
|
||||||
|
if(extended) o << ':';
|
||||||
|
o << std::setw(2) << _m;
|
||||||
|
if(decimal) {
|
||||||
|
double f = (_s / 60.0) + _ns / 60e9;
|
||||||
|
o2 << f;
|
||||||
|
o << (o2.str().substr(1, std::wstring::npos));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601PrecisionSecond:
|
||||||
|
o << std::setw(2) << _h;
|
||||||
|
if(extended) o << ':';
|
||||||
|
o << std::setw(2) << _m;
|
||||||
|
if(extended) o << ':';
|
||||||
|
o << std::setw(2) << _s;
|
||||||
|
if(decimal) {
|
||||||
|
double f = _ns / 1e9;
|
||||||
|
o2 << f;
|
||||||
|
o << (o2.str().substr(1, std::wstring::npos));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ISO8601PrecisionFull:
|
||||||
|
o << std::setw(2) << _h;
|
||||||
|
if(extended) o << ':';
|
||||||
|
o << std::setw(2) << _m;
|
||||||
|
if(extended) o << ':';
|
||||||
|
o << std::setw(2) << _s;
|
||||||
|
if(_ns) {
|
||||||
|
double f = _ns / 1e9;
|
||||||
|
o2 << f;
|
||||||
|
o << (o2.str().substr(1, std::wstring::npos));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw BadFormat();
|
||||||
|
}
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/* This function parses basic and extended format ISO8601 times (but not
|
||||||
|
the decimal notations, which are handled separately in fromString). It
|
||||||
|
returns the number of elements parsed (1, 2 or 3). This can be used to
|
||||||
|
determine which element a decimal fraction applies to.
|
||||||
|
*/
|
||||||
|
int ISO8601TimeParser(const std::wstring& str, int& h, int& m, int& s)
|
||||||
|
{
|
||||||
|
// The remaining ISO8601 formats can all be disambiguated on length.
|
||||||
|
switch(str.length()) {
|
||||||
|
case 8: // hh:mm:ss
|
||||||
|
if(str[2] != ':' || str[5] != ':') {
|
||||||
|
throw ParseError(L"Not a recognised ISO8601 format.",
|
||||||
|
str, str[2] == ':' ? 5 : 2);
|
||||||
|
}
|
||||||
|
h = strToInt32(str.substr(0, 2));
|
||||||
|
m = strToInt32(str.substr(3, 2));
|
||||||
|
s = strToInt32(str.substr(6, 2));
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
case 6: // hhmmss
|
||||||
|
h = strToInt32(str.substr(0, 2));
|
||||||
|
m = strToInt32(str.substr(2, 2));
|
||||||
|
s = strToInt32(str.substr(4, 2));
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
case 5: // hh:mm
|
||||||
|
if(str[2] != ':') {
|
||||||
|
throw ParseError(L"Not a recognised ISO8601 format.",
|
||||||
|
str, 2);
|
||||||
|
}
|
||||||
|
h = strToInt32(str.substr(0, 2));
|
||||||
|
m = strToInt32(str.substr(3, 2));
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
case 4: // hhmm
|
||||||
|
h = strToInt32(str.substr(0, 2));
|
||||||
|
m = strToInt32(str.substr(2, 2));
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
case 2: // hh
|
||||||
|
h = strToInt32(str);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ParseError(L"Not a recognised ISO8601 format.", str, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DayTime DayTime::fromString(const std::wstring& str)
|
||||||
|
{
|
||||||
|
int h = 0, m = 0, s = 0, ns = 0;
|
||||||
|
std::wstring::size_type decimal = std::wstring::npos;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check for decimal time.
|
||||||
|
if( (decimal = str.find('.')) != std::wstring::npos ||
|
||||||
|
(decimal = str.find(',')) != std::wstring::npos)
|
||||||
|
{
|
||||||
|
// get fractional part
|
||||||
|
double fraction = strToDouble(
|
||||||
|
str.substr(decimal, std::wstring::npos));
|
||||||
|
|
||||||
|
switch(ISO8601TimeParser(str.substr(0, decimal), h, m, s)) {
|
||||||
|
case 1:
|
||||||
|
m = (int)(fraction * 60);
|
||||||
|
s = (int)fmod(fraction * 3600, 60);
|
||||||
|
ns = (int)fmod(fraction * 3600 * tenExp9, tenExp9);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
s = (int)(fraction * 60);
|
||||||
|
ns = (int)fmod(fraction * 60 * tenExp9, tenExp9);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
ns = (int)(fraction * tenExp9);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Interpret as non-decimal time
|
||||||
|
ISO8601TimeParser(str, h, m, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DayTime(h, m, s, ns);
|
||||||
|
}
|
||||||
|
catch(Exception& ex) {
|
||||||
|
// build a suitable error message
|
||||||
|
std::wostringstream chainStr;
|
||||||
|
chainStr << "DayTime::fromString(\"" << str << "\")";
|
||||||
|
|
||||||
|
ex.chain(chainStr.str());
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DayTime DayTime::now()
|
||||||
|
{
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
time_t t = tv.tv_sec;
|
||||||
|
struct tm tt;
|
||||||
|
localtime_r(&t, &tt);
|
||||||
|
return DayTime(tt.tm_hour, tt.tm_min, tt.tm_sec, tv.tv_usec * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DayTime& DayTime::addNanoSeconds(int64_t ns)
|
||||||
|
{
|
||||||
|
lldiv_t dv = lldiv(ns, tenExp9);
|
||||||
|
_s += dv.quot;
|
||||||
|
_ns += dv.rem;
|
||||||
|
int dummy;
|
||||||
|
return wrap(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DayTime& DayTime::addNanoSeconds(int64_t ns, int& dayDelta)
|
||||||
|
{
|
||||||
|
lldiv_t dv = lldiv(ns, tenExp9);
|
||||||
|
_s += dv.quot;
|
||||||
|
_ns += dv.rem;
|
||||||
|
return wrap(dayDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wostream& operator<<(std::wostream& ostr, const DayTime& d)
|
||||||
|
{
|
||||||
|
ostr << d.toString();
|
||||||
|
return ostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
/* lw-support/src/lib/Time/DayTime.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Represents a 24-hour day time.
|
||||||
|
|
||||||
|
This class is used to represent a day time (i.e. between 00:00:00 and
|
||||||
|
23:59:59). It will "wrap around" if you modify it so that it goes
|
||||||
|
outside this range. The function forms will allow you to retrieve the
|
||||||
|
number of days difference in such cases. There is no support for
|
||||||
|
leap seconds. The class also has a fractional second member (in
|
||||||
|
nanoseconds).
|
||||||
|
|
||||||
|
This class is intended for representing a specific time, not an elapsed
|
||||||
|
time. Use the ElapsedTime class for that.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class DayTime {
|
||||||
|
private:
|
||||||
|
int _h, _m, _s, _ns;
|
||||||
|
|
||||||
|
DayTime& wrap(int& dayDelta);
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Constructors ////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Construct from time.
|
||||||
|
|
||||||
|
\param h The hour (0-23).
|
||||||
|
\param m The minute (0-59).
|
||||||
|
\param s The second (0-59).
|
||||||
|
\param ns The nanosecond (a positive value < 1s).
|
||||||
|
\throws BadTimeValue if any value is out of range.
|
||||||
|
|
||||||
|
*/
|
||||||
|
DayTime(int h, int m, int s = 0, int ns = 0)
|
||||||
|
: _h(h), _m(m), _s(s), _ns(ns)
|
||||||
|
{
|
||||||
|
if(_h < 0 || _h > 23 || _m < 0 || _m > 59 || _s < 0 || _s > 59
|
||||||
|
|| _ns < 0 || _ns >= tenExp9)
|
||||||
|
throw BadTimeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Construct from elapsed time.
|
||||||
|
|
||||||
|
\param time The ElapsedTime object to construct from.
|
||||||
|
\throws BadTimeValue if \a time represents a period >= 86400s.
|
||||||
|
|
||||||
|
*/
|
||||||
|
DayTime(const ElapsedTime& time)
|
||||||
|
{
|
||||||
|
uint64_t p = time.to_s();
|
||||||
|
if(p >= 86400) throw BadTimeValue();
|
||||||
|
_h = p / 3600;
|
||||||
|
_m = (p / 60) % 60;
|
||||||
|
_s = p % 60;
|
||||||
|
_ns = time.to_ns() % tenExp9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copy constructor.
|
||||||
|
DayTime(const DayTime& other)
|
||||||
|
: _h(other._h), _m(other._m), _s(other._s), _ns(other._ns)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Construct from calendar time.
|
||||||
|
static DayTime now();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Setting functions ///////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Set time.
|
||||||
|
|
||||||
|
\param h The hour (0-23).
|
||||||
|
\param m The minute (0-59).
|
||||||
|
\param s The second (0-59).
|
||||||
|
\param ns The nanosecond (a positive value < 1s).
|
||||||
|
\throws BadTimeValue if any value is out of range.
|
||||||
|
\returns A reference to itself.
|
||||||
|
|
||||||
|
*/
|
||||||
|
DayTime& set(int h, int m, int s = 0, int ns = 0)
|
||||||
|
{
|
||||||
|
if(h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59
|
||||||
|
|| ns < 0 || ns >= tenExp9)
|
||||||
|
throw BadTimeValue();
|
||||||
|
_h = h;
|
||||||
|
_m = m;
|
||||||
|
_s = s;
|
||||||
|
_ns = ns;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set time.
|
||||||
|
DayTime& operator=(const DayTime& other)
|
||||||
|
{
|
||||||
|
_h = other._h;
|
||||||
|
_m = other._m;
|
||||||
|
_s = other._s;
|
||||||
|
_ns = other._ns;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Set time.
|
||||||
|
|
||||||
|
\param time The ElapsedTime object to construct from.
|
||||||
|
\throws BadTimeValue if \a time represents a period >= 86400s.
|
||||||
|
\returns A reference to itself.
|
||||||
|
|
||||||
|
*/
|
||||||
|
DayTime& operator=(const ElapsedTime& time)
|
||||||
|
{
|
||||||
|
uint64_t p = time.to_s();
|
||||||
|
if(p >= 86400) throw BadTimeValue();
|
||||||
|
_h = p / 3600;
|
||||||
|
_m = (p / 60) % 60;
|
||||||
|
_s = p % 60;
|
||||||
|
_ns = time.to_ns() % tenExp9;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Query functions /////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Return hour (0-23).
|
||||||
|
int h() const
|
||||||
|
{ return _h; }
|
||||||
|
|
||||||
|
/// Return minute (0-59).
|
||||||
|
int m() const
|
||||||
|
{ return _m; }
|
||||||
|
|
||||||
|
/// Return second (0-59).
|
||||||
|
int s() const
|
||||||
|
{ return _s; }
|
||||||
|
|
||||||
|
/// Return nanosecond (<1s)
|
||||||
|
int ns() const
|
||||||
|
{ return _ns; }
|
||||||
|
|
||||||
|
/// Convert to an ElapsedTime object.
|
||||||
|
ElapsedTime toElapsedTime() const
|
||||||
|
{
|
||||||
|
if(_ns) return ElapsedTime(_h * 3600 + _m * 60 + _s,
|
||||||
|
_ns / 1000, _ns % 1000);
|
||||||
|
else return ElapsedTime(_h * 3600 + _m * 60 + _s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert into seconds since midnight.
|
||||||
|
int numSeconds() const
|
||||||
|
{ return _h * 3600 + _m * 60 + _s; }
|
||||||
|
|
||||||
|
/// Convert into nanoseconds since midnight.
|
||||||
|
int64_t numNanoSeconds() const
|
||||||
|
{ return (_h * 3600 + _m * 60 + _s) * tenExp9 + _ns; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Arithmetic functions ////////////////////////////////////
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addNanoSeconds(int64_t ns);
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addNanoSeconds(int64_t ns, int& dayDelta);
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addSeconds(int s)
|
||||||
|
{
|
||||||
|
int dummy;
|
||||||
|
_s += s;
|
||||||
|
return wrap(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addSeconds(int s, int& dayDelta)
|
||||||
|
{
|
||||||
|
_s += s;
|
||||||
|
return wrap(dayDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addMinutes(int m)
|
||||||
|
{
|
||||||
|
int dummy;
|
||||||
|
_m += m;
|
||||||
|
return wrap(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addMinutes(int m, int& dayDelta)
|
||||||
|
{
|
||||||
|
_m += m;
|
||||||
|
return wrap(dayDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addHours(int h)
|
||||||
|
{
|
||||||
|
int dummy;
|
||||||
|
_h += h;
|
||||||
|
return wrap(dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& addHours(int h, int& dayDelta)
|
||||||
|
{
|
||||||
|
_h += h;
|
||||||
|
return wrap(dayDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // String routines /////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Thrown when format specifiers don't make sense, etc.
|
||||||
|
class BadFormat : public ProgramException { };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Parse a std::string object to get a day time.
|
||||||
|
|
||||||
|
\param str The std::string to parse.
|
||||||
|
\throws ParseError if a parsing error occurs.
|
||||||
|
\returns A new DayTime object constructed from the std::string data.
|
||||||
|
|
||||||
|
Parses an ISO8601 std::string, accepting either basic or extended format
|
||||||
|
notations, at any precision. Handles decimal notation as well.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static DayTime fromString(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Convert to std::string.
|
||||||
|
|
||||||
|
\param extended True if you want the extended ISO8601
|
||||||
|
representation; false if not.
|
||||||
|
\param precision The precision to which you want to go. This takes
|
||||||
|
a different meaning if \a decimal is true.
|
||||||
|
\param decimal True if you want to use decimal time notation; false
|
||||||
|
if not.
|
||||||
|
\returns A UCS-4 std::string representation of the day time.
|
||||||
|
|
||||||
|
Creates a std::string representation of the time in ISO8601 form. If you
|
||||||
|
choose to use decimal time, then the full precision will be printed,
|
||||||
|
but the \a precision argument specifies where to start the fraction.
|
||||||
|
If you use lw::ISO8601PrecisionFull, and \a decimal is set to false,
|
||||||
|
the decimal part will still be printed if and only if the number of
|
||||||
|
nanoseconds is non-zero.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring toString(bool extended = true,
|
||||||
|
ISO8601Precision precision = ISO8601PrecisionSecond,
|
||||||
|
bool decimal = false) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Operators (comparison) //////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns true if \a this is equal to \a other.
|
||||||
|
bool operator==(const DayTime& other) const
|
||||||
|
{ return _h == other._h && _m == other._m && _s == other._s; }
|
||||||
|
|
||||||
|
/// Returns true if \a this is not equal to \a other.
|
||||||
|
bool operator!=(const DayTime& other) const
|
||||||
|
{ return _h != other._h || _m != other._m || _s != other._s; }
|
||||||
|
|
||||||
|
/// Returns true if \a this is earlier than \a other.
|
||||||
|
bool operator<(const DayTime& other) const
|
||||||
|
{ return numNanoSeconds() < other.numNanoSeconds(); }
|
||||||
|
|
||||||
|
/// Returns true if \a this is earlier than or equal to \a other.
|
||||||
|
bool operator<=(const DayTime& other) const
|
||||||
|
{ return numNanoSeconds() <= other.numNanoSeconds(); }
|
||||||
|
|
||||||
|
/// Returns true if \a this is later than \a other.
|
||||||
|
bool operator>(const DayTime& other) const
|
||||||
|
{ return numNanoSeconds() > other.numNanoSeconds(); }
|
||||||
|
|
||||||
|
/// Returns true if \a this is later than or equal to \a other.
|
||||||
|
bool operator>=(const DayTime& other) const
|
||||||
|
{ return numNanoSeconds() >= other.numNanoSeconds(); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Operators (arithmetic) //////////////////////////////////
|
||||||
|
|
||||||
|
/// Advance time.
|
||||||
|
DayTime& operator+=(int s)
|
||||||
|
{ return addSeconds(s); }
|
||||||
|
|
||||||
|
/// Retard time.
|
||||||
|
DayTime& operator-=(int s)
|
||||||
|
{ return addSeconds(-s); }
|
||||||
|
|
||||||
|
/// Advanced time.
|
||||||
|
DayTime operator+(int s) const
|
||||||
|
{
|
||||||
|
DayTime t(*this);
|
||||||
|
return t.addSeconds(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retarded time.
|
||||||
|
DayTime operator-(int s) const
|
||||||
|
{
|
||||||
|
DayTime t(*this);
|
||||||
|
return t.addSeconds(-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns time difference in nanoseconds.
|
||||||
|
int64_t operator-(const DayTime& other) const
|
||||||
|
{ return numNanoSeconds() - other.numNanoSeconds(); }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream insertion operator.
|
||||||
|
std::wostream& operator<<(std::wostream&, const DayTime& d);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
/* lw-support/src/lib/Time/ElapsedTime.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring ElapsedTime::toString(int decPlaces, bool exp) const
|
||||||
|
{
|
||||||
|
std::wostringstream o;
|
||||||
|
|
||||||
|
o << std::setprecision(decPlaces);
|
||||||
|
|
||||||
|
if(exp) {
|
||||||
|
o << std::scientific;
|
||||||
|
o << (time / 1e9) << 's';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
o << std::fixed;
|
||||||
|
if(time < 100) {
|
||||||
|
o << time << "ns";
|
||||||
|
|
||||||
|
} else if(time < 1e5) {
|
||||||
|
o << (time / 1e3) << "us";
|
||||||
|
|
||||||
|
} else if(time < 1e8) {
|
||||||
|
o << (time / 1e6) << "ns";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
o << (time / 1e9) << 's';
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ElapsedTime ElapsedTime::fromString(const std::wstring& str)
|
||||||
|
{
|
||||||
|
std::wstring::size_type strSize = str.length();
|
||||||
|
std::wistringstream istr(str);
|
||||||
|
double d;
|
||||||
|
|
||||||
|
istr >> d >> std::ws;
|
||||||
|
int afterNumPos = (strSize - istr.rdbuf()->in_avail());
|
||||||
|
if(istr.bad() || istr.fail()) {
|
||||||
|
throw ParseError(L"Not a number.", str, afterNumPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(d < 0 || !isfinite(d)) {
|
||||||
|
throw ParseError(L"Not a valid time period.", str, afterNumPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!istr.eof()) {
|
||||||
|
std::wstring unit;
|
||||||
|
istr >> unit >> std::ws;
|
||||||
|
|
||||||
|
if(unit == L"s") {
|
||||||
|
d *= 1e9;
|
||||||
|
|
||||||
|
} else if(unit == L"ms") {
|
||||||
|
d *= 1e6;
|
||||||
|
|
||||||
|
} else if(unit == L"us") {
|
||||||
|
d *= 1e3;
|
||||||
|
|
||||||
|
} else if(unit == L"ns") {
|
||||||
|
// nothing to do
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw ParseError(L"Unrecognised unit.", str, afterNumPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!istr.eof()) {
|
||||||
|
throw ParseError(L"Data after unit.", str,
|
||||||
|
(strSize - istr.rdbuf()->in_avail()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ElapsedTime((uint64_t)(d), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ElapsedTime::ElapsedTime(struct timeval* tv)
|
||||||
|
: time(tv->tv_sec * tenExp9 + tv->tv_usec * tenExp3)
|
||||||
|
{
|
||||||
|
if(tv->tv_sec < 0 || tv->tv_usec < 0 || tv->tv_usec >= tenExp6) {
|
||||||
|
throw BadTimeValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ElapsedTime::ElapsedTime(struct timespec* ts)
|
||||||
|
: time(ts->tv_sec * tenExp9 + ts->tv_nsec)
|
||||||
|
{
|
||||||
|
if(ts->tv_sec < 0 || ts->tv_nsec < 0 || ts->tv_nsec >= tenExp9) {
|
||||||
|
throw BadTimeValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wostream& operator<<(std::wostream& ostr, const ElapsedTime& t)
|
||||||
|
{
|
||||||
|
ostr << t.toString();
|
||||||
|
return ostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,394 @@
|
||||||
|
/* lw-support/src/lib/Time/ElapsedTime.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Integer constant: 1e9.
|
||||||
|
const int64_t tenExp9 = 1000000000;
|
||||||
|
|
||||||
|
/// Integer constant: 1e6.
|
||||||
|
const int64_t tenExp6 = 1000000;
|
||||||
|
|
||||||
|
/// Integer constant: 1e3.
|
||||||
|
const int64_t tenExp3 = 1000;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief An elapsed time period.
|
||||||
|
|
||||||
|
This class is used to represent an elapsed time period. It has a
|
||||||
|
resolution of nanoseconds, and uses a 64-bit integer (thus providing
|
||||||
|
a maximum representable period of 1.84e10 seconds, or about 584 years).
|
||||||
|
It is intended as a direct replacement for the C library's <tt>struct
|
||||||
|
timeval</tt> and <tt>struct timespec</tt>. It cannot represent negative
|
||||||
|
time periods.
|
||||||
|
|
||||||
|
\sa lw::StopWatch, lw::DayTime
|
||||||
|
|
||||||
|
*/
|
||||||
|
class ElapsedTime {
|
||||||
|
private:
|
||||||
|
uint64_t time;
|
||||||
|
|
||||||
|
// constructor with dummy argument to avoid collisions
|
||||||
|
explicit ElapsedTime(uint64_t ns, bool)
|
||||||
|
: time(ns)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Constructors ////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Default constructor.
|
||||||
|
|
||||||
|
\param s The number of seconds (must be >= 0)
|
||||||
|
\param ms The number of milliseconds (must be >= 0 and < 1000)
|
||||||
|
\param us The number of microseconds (must be >= 0 and < 1000)
|
||||||
|
\param ns The number of nanoseconds (must be >= 0 and < 1000)
|
||||||
|
\throws BadTimeValue if any one of the arguments is out of range.
|
||||||
|
|
||||||
|
Constructs an elapsed time period. This constructor is designed for
|
||||||
|
intuitive use -- the more arguments you specify, the more accurate
|
||||||
|
the result. Seconds are the most significant argument.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ElapsedTime(int s = 0, int ms = 0, int us = 0, int ns = 0)
|
||||||
|
: time(s * tenExp9 + ms * tenExp6 + us * tenExp3 + ns)
|
||||||
|
{
|
||||||
|
if(s < 0 || ms < 0 || ms >= tenExp3 || us < 0 || us >= tenExp3
|
||||||
|
|| ns < 0 || ns >= tenExp3)
|
||||||
|
{
|
||||||
|
throw BadTimeValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Construct from floating point value in seconds.
|
||||||
|
|
||||||
|
\param s The fractional number of seconds.
|
||||||
|
\throws BadTimeValue if \a s is negative.
|
||||||
|
|
||||||
|
Rounds the fractional time to the nearest nanosecond.
|
||||||
|
|
||||||
|
*/
|
||||||
|
explicit ElapsedTime(long double s)
|
||||||
|
: time(uint64_t(s * tenExp9))
|
||||||
|
{
|
||||||
|
if(s < 0) throw BadTimeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Construct from floating point value in seconds.
|
||||||
|
|
||||||
|
\param s The fractional number of seconds.
|
||||||
|
\throws BadTimeValue if \a s is negative.
|
||||||
|
|
||||||
|
Rounds the fractional time to the nearest nanosecond.
|
||||||
|
|
||||||
|
*/
|
||||||
|
explicit ElapsedTime(double s)
|
||||||
|
: time(uint64_t(s * tenExp9))
|
||||||
|
{
|
||||||
|
if(s < 0) throw BadTimeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Construct from floating point value in seconds.
|
||||||
|
|
||||||
|
\param s The fractional number of seconds.
|
||||||
|
\throws BadTimeValue if \a s is negative.
|
||||||
|
|
||||||
|
Rounds the fractional time to the nearest nanosecond.
|
||||||
|
|
||||||
|
*/
|
||||||
|
explicit ElapsedTime(float s)
|
||||||
|
: time(uint64_t(s * tenExp9))
|
||||||
|
{
|
||||||
|
if(s < 0) throw BadTimeValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Copy constructor.
|
||||||
|
ElapsedTime(const ElapsedTime& other)
|
||||||
|
: time(other.time)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Construct from a struct timeval.
|
||||||
|
|
||||||
|
\param tv The timeval to construct from.
|
||||||
|
\throws BadTimeValue if \a tv's members are out of range.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ElapsedTime(struct timeval* tv);
|
||||||
|
|
||||||
|
/*! \brief Construct from a struct spec.
|
||||||
|
|
||||||
|
\param ts The timespec to construct from.
|
||||||
|
\throws BadTimeValue if \a ts's members are out of range.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ElapsedTime(struct timespec* ts);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Construct from nanoseconds.
|
||||||
|
static ElapsedTime from_ns(uint64_t ns)
|
||||||
|
{ return ElapsedTime(ns, true); }
|
||||||
|
|
||||||
|
/// Explicitly construct from microseconds.
|
||||||
|
static ElapsedTime from_us(uint64_t us)
|
||||||
|
{ return ElapsedTime(us * tenExp3, true); }
|
||||||
|
|
||||||
|
/// Explicitly construct from milliseconds.
|
||||||
|
static ElapsedTime from_ms(uint64_t ms)
|
||||||
|
{ return ElapsedTime(ms * tenExp6, true); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Query functions /////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns true if the time period is zero.
|
||||||
|
inline bool isZero() const { return !time; }
|
||||||
|
|
||||||
|
/// Returns the time in nanoseconds.
|
||||||
|
uint64_t to_ns() const { return time; }
|
||||||
|
|
||||||
|
/// Returns the time in microseconds.
|
||||||
|
uint64_t to_us() const { return time / tenExp3; }
|
||||||
|
|
||||||
|
/// Returns the time in milliseconds.
|
||||||
|
uint64_t to_ms() const { return time / tenExp6; }
|
||||||
|
|
||||||
|
/// Returns the time in seconds.
|
||||||
|
uint64_t to_s() const { return time / tenExp9; }
|
||||||
|
|
||||||
|
/// Returns fractional time.
|
||||||
|
long double toLongDouble() const { return time / 1e9; }
|
||||||
|
|
||||||
|
/// Returns fractional time.
|
||||||
|
double toDouble() const { return time / 1e9; }
|
||||||
|
|
||||||
|
/// Returns fractional time.
|
||||||
|
float toFloat() const { return time / 1e9; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Set functions ///////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Assignment operator.
|
||||||
|
ElapsedTime& operator=(const ElapsedTime& other)
|
||||||
|
{
|
||||||
|
time = other.time;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the elapsed time.
|
||||||
|
ElapsedTime clear()
|
||||||
|
{
|
||||||
|
time = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the elapsed time (nanoseconds).
|
||||||
|
ElapsedTime set_ns(uint64_t ns)
|
||||||
|
{
|
||||||
|
time = ns;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the elapsed time (microseconds).
|
||||||
|
ElapsedTime set_us(uint64_t us)
|
||||||
|
{
|
||||||
|
time = us * tenExp3;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the elapsed time (milliseconds).
|
||||||
|
ElapsedTime set_ms(uint64_t ms)
|
||||||
|
{
|
||||||
|
time = ms * tenExp6;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the elapsed time (seconds).
|
||||||
|
ElapsedTime set_s(uint64_t s)
|
||||||
|
{
|
||||||
|
time = s * tenExp9;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Sets the elapsed time from a fraction.
|
||||||
|
|
||||||
|
\param s The fractional number of seconds.
|
||||||
|
\throws BadTimeValue if \a s is negative.
|
||||||
|
|
||||||
|
Rounds the fractional time to the nearest nanosecond.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ElapsedTime set(long double s)
|
||||||
|
{
|
||||||
|
if(s < 0) throw BadTimeValue();
|
||||||
|
time = (uint64_t)s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Sets the elapsed time from a fraction.
|
||||||
|
|
||||||
|
\param s The fractional number of seconds.
|
||||||
|
\throws BadTimeValue if \a s is negative.
|
||||||
|
|
||||||
|
Rounds the fractional time to the nearest nanosecond.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ElapsedTime set(double s)
|
||||||
|
{
|
||||||
|
if(s < 0) throw BadTimeValue();
|
||||||
|
time = (uint64_t)s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Sets the elapsed time from a fraction.
|
||||||
|
|
||||||
|
\param s The fractional number of seconds.
|
||||||
|
\throws BadTimeValue if \a s is negative.
|
||||||
|
|
||||||
|
Rounds the fractional time to the nearest nanosecond.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ElapsedTime set(float s)
|
||||||
|
{
|
||||||
|
if(s < 0) throw BadTimeValue();
|
||||||
|
time = (uint64_t)s;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // String Routines /////////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Returns a std::string representation of the elapsed time.
|
||||||
|
|
||||||
|
\param decPlaces The number of decimal places.
|
||||||
|
\param exp True if you want exponential (scientific) notation.
|
||||||
|
|
||||||
|
This function will return a std::string representation of an elapsed time
|
||||||
|
period. The formatting can be specified. Units (<tt>s, ms, us,
|
||||||
|
ns</tt>) will be printed after the number. In scientific notation,
|
||||||
|
the unit is always seconds.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring toString(int decPlaces = 1, bool exp = false) const;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Constructs a time period from a std::string representation.
|
||||||
|
|
||||||
|
\param str The std::string to interpret.
|
||||||
|
\throws ParseError if a parsing error occurs.
|
||||||
|
|
||||||
|
This function will try to interpret a std::string as a time period. It
|
||||||
|
accepts an integral or floating point number (possibly in C's
|
||||||
|
exponential notation), possibly followed by a unit. Acceptable
|
||||||
|
units are <tt>s, ms, us, ns</tt>. If omitted, seconds are assumed.
|
||||||
|
Any whitespace in the std::string will be ignored.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static ElapsedTime fromString(const std::wstring& str);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Operators (comparison) //////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns true if the time period is zero.
|
||||||
|
inline bool operator!() const { return !time; }
|
||||||
|
|
||||||
|
/// Returns true if the time period is non-zero.
|
||||||
|
inline operator bool() const { return time; }
|
||||||
|
|
||||||
|
/// Returns true if two time periods are equal.
|
||||||
|
inline bool operator==(const ElapsedTime& other) const
|
||||||
|
{ return time == other.time; }
|
||||||
|
|
||||||
|
/// Returns true if two time periods are not equal.
|
||||||
|
inline bool operator!=(const ElapsedTime& other) const
|
||||||
|
{ return time != other.time; }
|
||||||
|
|
||||||
|
/// Less than comparison.
|
||||||
|
inline bool operator<(const ElapsedTime& other) const
|
||||||
|
{ return time < other.time; }
|
||||||
|
|
||||||
|
/// Less than or equal to comparison.
|
||||||
|
inline bool operator<=(const ElapsedTime& other) const
|
||||||
|
{ return time <= other.time; }
|
||||||
|
|
||||||
|
/// Greater than comparison.
|
||||||
|
inline bool operator>(const ElapsedTime& other) const
|
||||||
|
{ return time > other.time; }
|
||||||
|
|
||||||
|
/// Greater than or equal to comparison.
|
||||||
|
inline bool operator>=(const ElapsedTime& other) const
|
||||||
|
{ return time >= other.time; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Operators (arithmetic) //////////////////////////////////
|
||||||
|
|
||||||
|
/// Addition operation.
|
||||||
|
inline ElapsedTime& operator+=(const ElapsedTime& other)
|
||||||
|
{
|
||||||
|
time += other.time;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Subtraction operation.
|
||||||
|
|
||||||
|
\param other The time period to subtract from this one.
|
||||||
|
\throws BadTimeOverflow if \a other is a greater time period than
|
||||||
|
\a this
|
||||||
|
\returns A reference to the newly-modified object.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline ElapsedTime& operator-=(const ElapsedTime& other)
|
||||||
|
{
|
||||||
|
if(other.time > time)
|
||||||
|
throw BadTimeOverflow();
|
||||||
|
time -= other.time;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Addition operation.
|
||||||
|
inline ElapsedTime operator+(const ElapsedTime& other) const
|
||||||
|
{
|
||||||
|
return ElapsedTime(time + other.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! \brief Subtraction operation.
|
||||||
|
|
||||||
|
\param other The time period to subtract from this one.
|
||||||
|
\throws BadTimeOverflow if \a other is a greater time period than
|
||||||
|
\a this
|
||||||
|
\returns The time period difference.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline ElapsedTime operator-(const ElapsedTime& other) const
|
||||||
|
{
|
||||||
|
if(other.time > time)
|
||||||
|
throw BadTimeOverflow();
|
||||||
|
return ElapsedTime(time - other.time, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Stream insertion operator.
|
||||||
|
std::wostream& operator<<(std::wostream&, const ElapsedTime&);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* lw-support/src/lib/Time/StopWatch.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
StopWatch::StopWatch()
|
||||||
|
: hasStarted(true)
|
||||||
|
{
|
||||||
|
if(clock_gettime(CLOCK_REALTIME, &started)) throw SystemError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void StopWatch::start()
|
||||||
|
{
|
||||||
|
hasStarted = true;
|
||||||
|
if(clock_gettime(CLOCK_REALTIME, &started)) throw SystemError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void StopWatch::stop()
|
||||||
|
{
|
||||||
|
hasStarted = false;
|
||||||
|
if(clock_gettime(CLOCK_REALTIME, &stopped)) throw SystemError();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ElapsedTime StopWatch::elapsed() const
|
||||||
|
{
|
||||||
|
if(hasStarted && clock_gettime(CLOCK_REALTIME, &stopped))
|
||||||
|
throw SystemError();
|
||||||
|
return ElapsedTime::from_ns((uint64_t)((stopped.tv_sec - started.tv_sec) * tenExp9
|
||||||
|
+ (stopped.tv_nsec - started.tv_nsec)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* lw-support/src/lib/Time/StopWatch.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Stop watch timer class.
|
||||||
|
|
||||||
|
This is a simple stopwatch class, which uses the ElapsedTime class to
|
||||||
|
record elapsed time to the nearest nanosecond (ostensibly; this depends
|
||||||
|
on operating system support).
|
||||||
|
|
||||||
|
\sa ElapsedTime
|
||||||
|
|
||||||
|
*/
|
||||||
|
class StopWatch {
|
||||||
|
private:
|
||||||
|
struct timespec started;
|
||||||
|
mutable struct timespec stopped;
|
||||||
|
bool hasStarted;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor. Starts timer.
|
||||||
|
StopWatch();
|
||||||
|
|
||||||
|
/// Starts (or restarts) the timer.
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/// Stops the timer.
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
/*! \brief Retrieves elapsed time.
|
||||||
|
|
||||||
|
\returns The elapsed time.
|
||||||
|
|
||||||
|
If the timer is currently running, it returns the time elapsed since
|
||||||
|
it was started. If the timer is stopped, it returns the time elapsed
|
||||||
|
between starting (or restarting) and stopping the timer.
|
||||||
|
|
||||||
|
*/
|
||||||
|
ElapsedTime elapsed() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,16 +1,32 @@
|
||||||
/* lw-support/src/liblw-support/TopHeader.h
|
/* lw-support/src/lib/TopHeader.h
|
||||||
*
|
*
|
||||||
* (c)2006, Laurence Withers, <l@lwithers.me.uk>.
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
* Released under the GNU GPLv2. See file COPYING or
|
* COPYING for more information / terms of license.
|
||||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef HEADER_liblw_support
|
// This file contains the include guard, and also any includes that are
|
||||||
#define HEADER_liblw_support
|
// needed for the header file itself. Includes that are only needed in
|
||||||
|
// the source go into TopSource.cpp
|
||||||
|
|
||||||
|
#ifndef HEADER_LW_Support
|
||||||
|
#define HEADER_LW_Support
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// standard includes, or includes needed for type declarations
|
// standard includes, or includes needed for type declarations
|
||||||
|
#include <string>
|
||||||
|
#include <exception>
|
||||||
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <map>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
/* options for text editors
|
#include <sys/epoll.h>
|
||||||
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
#include <sys/socket.h>
|
||||||
vim: expandtab:ts=4:sw=4
|
|
||||||
*/
|
#include <netinet/in.h>
|
||||||
|
|
|
@ -1,15 +1,36 @@
|
||||||
/* lw-support/src/liblw-support/TopSource.cpp
|
/* lw-support/src/lib/TopSource.cpp
|
||||||
*
|
*
|
||||||
* (c)2006, Laurence Withers, <l@lwithers.me.uk>.
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
* Released under the GNU GPLv2. See file COPYING or
|
* COPYING for more information / terms of license.
|
||||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "lw-support"
|
#include "lw-support"
|
||||||
|
|
||||||
// Below are all the includes used throughout the library.
|
// Below are all the includes used throughout the library.
|
||||||
|
|
||||||
/* options for text editors
|
#include <iostream>
|
||||||
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
#include <iomanip>
|
||||||
vim: expandtab:ts=4:sw=4
|
|
||||||
*/
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <execinfo.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
|
// external library includes
|
||||||
|
|
||||||
|
// Some useful, universal constants.
|
||||||
|
|
||||||
|
#define pipeRead 0
|
||||||
|
#define pipeWrite 1
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
/* lw-support/src/lib/Util/Completion.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CompletionTask::CompletionTask(CompletionCallback* callback)
|
||||||
|
: callback(callback)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CompletionTask::~CompletionTask()
|
||||||
|
{
|
||||||
|
if(callback) {
|
||||||
|
try {
|
||||||
|
callback->aborted(this, lw::Exception(L"Task still active when destructor called."));
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionTask::taskCompleted()
|
||||||
|
{
|
||||||
|
CompletionCallback* cb = callback;
|
||||||
|
if(!callback) throw TaskAlreadyCompleted();
|
||||||
|
callback = 0;
|
||||||
|
cb->completed(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionTask::taskAborted(const Exception& e)
|
||||||
|
{
|
||||||
|
CompletionCallback* cb = callback;
|
||||||
|
if(!callback) throw TaskAlreadyCompleted();
|
||||||
|
callback = 0;
|
||||||
|
cb->aborted(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CompletionList::CompletionList()
|
||||||
|
: abortingTask(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CompletionList::~CompletionList()
|
||||||
|
{
|
||||||
|
abortAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionList::abortAll()
|
||||||
|
{
|
||||||
|
while(!tasks.empty()) {
|
||||||
|
abortingTask = tasks.front();
|
||||||
|
tasks.pop_front();
|
||||||
|
try {
|
||||||
|
abortingTask->taskAbort(lw::Exception(L"Task list shutdown."));
|
||||||
|
}
|
||||||
|
catch(...) { }
|
||||||
|
if(abortingTask) dbg_taskNotAborted(abortingTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionList::registerTask(CompletionTask* task)
|
||||||
|
{
|
||||||
|
NullPointer::check(task, L"task", L"CompletionList::registerTask()");
|
||||||
|
tasks.push_back(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool CompletionList::empty()
|
||||||
|
{
|
||||||
|
return tasks.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionList::completed(CompletionData* data)
|
||||||
|
{
|
||||||
|
CompletionTask* task = dynamic_cast<CompletionTask*>(data);
|
||||||
|
if(!task) {
|
||||||
|
dbg_dataNotTask(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
taskIter iter = find(tasks.begin(), tasks.end(), data);
|
||||||
|
if(iter == tasks.end()) {
|
||||||
|
dbg_taskNotActive(task);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tasks.erase(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionList::aborted(CompletionData* data, const lw::Exception&)
|
||||||
|
{
|
||||||
|
if(abortingTask == data) abortingTask = 0;
|
||||||
|
completed(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionList::dbg_dataNotTask(CompletionData* data)
|
||||||
|
{
|
||||||
|
stderr << lw::Log::start(L"[DEBUG] CompletionList")
|
||||||
|
<< L"Data passed to CompletionList::completed() or aborted() was not derived from\n"
|
||||||
|
<< L"CompletionTask, as required."
|
||||||
|
<< L"\n this = "
|
||||||
|
<< this
|
||||||
|
<< L"\n data = "
|
||||||
|
<< data
|
||||||
|
<< lw::Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionList::dbg_taskNotAborted(CompletionTask* task)
|
||||||
|
{
|
||||||
|
stderr << lw::Log::start(L"[DEBUG] CompletionList")
|
||||||
|
<< L"Task not aborted."
|
||||||
|
<< L"\n this = "
|
||||||
|
<< this
|
||||||
|
<< L"\n task = "
|
||||||
|
<< task
|
||||||
|
<< lw::Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CompletionList::dbg_taskNotActive(CompletionTask* task)
|
||||||
|
{
|
||||||
|
stderr << lw::Log::start(L"[DEBUG] CompletionList")
|
||||||
|
<< L"Task passed to CompletionList::completed() or aborted() not active."
|
||||||
|
<< L"\n this = "
|
||||||
|
<< this
|
||||||
|
<< L"\n task = "
|
||||||
|
<< task
|
||||||
|
<< lw::Log::end;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
/* lw-support/src/lib/Util/Completion.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Task completion callback data base class.
|
||||||
|
class CompletionData {
|
||||||
|
public:
|
||||||
|
/// Destructor (does nothing).
|
||||||
|
virtual ~CompletionData()
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Completion callback interface.
|
||||||
|
|
||||||
|
Certain tasks use a callback to signal when they have been completed. This interface supports this
|
||||||
|
by providing a common callback function. To implement this, you derive your signal's target class
|
||||||
|
from lw::CompletionCallback and override the two functions.
|
||||||
|
|
||||||
|
The task class must take a pointer to a CompletionCallback instance as a parameter. When its task is
|
||||||
|
done, it will call the completed() function; if the task must be stopped early, it will call the
|
||||||
|
aborted() function. This function also provides an indication of what caused the task to abort. The
|
||||||
|
task class may be derived from CompletionTask, but this is not a requirement.
|
||||||
|
|
||||||
|
It is possible to associate arbitrary data with the task. This must be generated in or passed to the
|
||||||
|
task object. It will then be passed as the \a data parameter to the callback function. The data
|
||||||
|
%class must be derived from the lw::CompletionData class.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class CompletionCallback {
|
||||||
|
public:
|
||||||
|
/// Destructor (does nothing).
|
||||||
|
virtual ~CompletionCallback()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Task complete signal.
|
||||||
|
|
||||||
|
\param data Arbitrary data associated with the task.
|
||||||
|
|
||||||
|
This function is called to signal that the task has been completed, presumably successfully.
|
||||||
|
If any data was associated with the task, it will be passed in through the \a data pointer,
|
||||||
|
although it would be valid for this to be zero.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void completed(CompletionData* data) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Task aborted signal.
|
||||||
|
|
||||||
|
\param data Arbitrary data associated with the task.
|
||||||
|
\param e The exception that caused the task to abort.
|
||||||
|
|
||||||
|
This function is called to signal that the task has been aborted without completing.
|
||||||
|
If any data was associated with the task, it will be passed in through the \a data pointer,
|
||||||
|
although it would be valid for this to be zero.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void aborted(CompletionData* data, const lw::Exception& e) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Task already completed.
|
||||||
|
class TaskAlreadyCompleted : public lw::ProgramException { };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Completion task suggested interface.
|
||||||
|
|
||||||
|
This class is the suggested (but not required) interface for a task that uses the completion
|
||||||
|
notifier interface provided by CompletionCallback. It is a safe and effective way of ensuring that
|
||||||
|
all required completion events are generated and only one event is ever generated. It is also the
|
||||||
|
required interface for the CompletionList interface.
|
||||||
|
|
||||||
|
Instances of this class will pass a pointer to themselves as the \a data parameter to the callback
|
||||||
|
functions. If the callback class throws an exception, this will be propagated back to the function
|
||||||
|
that generated the signal (or discarded if it was the destructor).
|
||||||
|
|
||||||
|
*/
|
||||||
|
class CompletionTask : public CompletionData {
|
||||||
|
private:
|
||||||
|
CompletionCallback* callback;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*! \brief Report task completed.
|
||||||
|
|
||||||
|
\throws lw::TaskAlreadyCompleted if the task is already done.
|
||||||
|
|
||||||
|
This function will generate the signal to the callback class. The data passed will be a pointer
|
||||||
|
to the instance itself. Once signalled, no further signal can be sent. Any attempt to send more
|
||||||
|
than one signal will result in an lw::TaskAlreadyCompleted exception.
|
||||||
|
|
||||||
|
\sa lw::CompletionCallback::completed()
|
||||||
|
|
||||||
|
*/
|
||||||
|
void taskCompleted();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Report task aborted.
|
||||||
|
|
||||||
|
\param e Exception giving reason for task abort.
|
||||||
|
\throws lw::TaskAlreadyCompleted if the task is already done.
|
||||||
|
|
||||||
|
This function will generate the signal to the callback class. The data passed will be a pointer
|
||||||
|
to the instance itself. Once signalled, no further signal can be sent. Any attempt to send more
|
||||||
|
than one signal will result in an lw::TaskAlreadyCompleted exception.
|
||||||
|
|
||||||
|
\sa lw::CompletionCallback::completed()
|
||||||
|
|
||||||
|
*/
|
||||||
|
void taskAborted(const lw::Exception& e);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param callback The callback object.
|
||||||
|
\throws lw::NullPointer if \a callback is zero.
|
||||||
|
|
||||||
|
Stores the callback object for later signalling.
|
||||||
|
|
||||||
|
*/
|
||||||
|
CompletionTask(CompletionCallback* callback);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Destructor. Aborts task if not completed.
|
||||||
|
|
||||||
|
The destructor will check to see if the task has been completed or not. If it has, then it will
|
||||||
|
take no action; if it has not, the task will be aborted with a TaskAborted exception.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual ~CompletionTask();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Abort the task.
|
||||||
|
|
||||||
|
\param e Exception giving reason for task abort.
|
||||||
|
\throws lw::TaskAlreadyCompleted if the task is already done.
|
||||||
|
|
||||||
|
This function is called by the destructor if the task is still in progress when the object is
|
||||||
|
destroyed. It may also be made available to other functions in your class and potentially users
|
||||||
|
of your class (lw::CompletionList for example).
|
||||||
|
|
||||||
|
On calling this function, the task must be aborted and the taskAborted() signal must be sent
|
||||||
|
before returning from the function.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual void taskAbort(const lw::Exception& e) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief List of tasks to be completed.
|
||||||
|
|
||||||
|
This class is a useful utility which maintains a list of tasks in progress. As each task is
|
||||||
|
completed or aborted, it is removed from the list. When the object is destroyed, or when abortAll()
|
||||||
|
is called, it will call the lw::CompletionTask::taskAbort() function for every task still in the
|
||||||
|
list.
|
||||||
|
|
||||||
|
This class is not thread-safe. A set of three debug functions are provided to cater for
|
||||||
|
unexpected conditions. dbg_taskNotAborted() is called if lw::CompletionTask::abortTask() does not
|
||||||
|
result in an immediate call to completed() or aborted(). dbg_dataNotTask() is called if completed()
|
||||||
|
or aborted() are passed a CompletionData pointer that is not a CompletionTask pointer.
|
||||||
|
dbg_taskNotActive() is called if completed() or aborted() are passed a CompletionTask pointer that
|
||||||
|
is not in the active task list. Their default behaviour is to print an error message through
|
||||||
|
lw::stderr, but they can be overridden.
|
||||||
|
|
||||||
|
\todo We use an unsorted list here; sort it.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class CompletionList : public CompletionCallback,
|
||||||
|
private Uncopyable
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::list<CompletionTask*> tasks;
|
||||||
|
CompletionTask* abortingTask;
|
||||||
|
typedef std::list<CompletionTask*>::iterator taskIter;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Debug function.
|
||||||
|
virtual void dbg_taskNotAborted(CompletionTask* task);
|
||||||
|
/// Debug function.
|
||||||
|
virtual void dbg_dataNotTask(CompletionData* data);
|
||||||
|
/// Debug function.
|
||||||
|
virtual void dbg_taskNotActive(CompletionTask* task);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor.
|
||||||
|
CompletionList();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Destructor. Aborts all active tasks.
|
||||||
|
|
||||||
|
The destructor will abort all active tasks by calling abortAll() if necessary.
|
||||||
|
|
||||||
|
*/
|
||||||
|
virtual ~CompletionList();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Register a new task.
|
||||||
|
|
||||||
|
\param task The task to be completed.
|
||||||
|
\throws lw::NullPointer if \a task is zero.
|
||||||
|
|
||||||
|
Registers a new task for completion. The task must have been instantiated to use this
|
||||||
|
list instance as its callback (or at least the callback signals must reach this object). If the
|
||||||
|
task is still active when the list instance is destroyed, or abortAll() is called, the task's
|
||||||
|
own abort function will be called.
|
||||||
|
|
||||||
|
\note There is no guarding against adding the same task twice. Doing so will break things.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void registerTask(CompletionTask* task);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Abort all active tasks.
|
||||||
|
|
||||||
|
Attempts to abort all active tasks by calling lw::CompletionTask::abortTask() for each such
|
||||||
|
task. Any exception thrown from this function will be ignored; however, if there are any tasks
|
||||||
|
left unregistered at the end of the function, dbg_taskNotAborted() will be called for each
|
||||||
|
remaining task.
|
||||||
|
|
||||||
|
*/
|
||||||
|
void abortAll();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns true if there are no active tasks.
|
||||||
|
bool empty();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// implemented virtuals from lw::CompletionCallback
|
||||||
|
virtual void completed(CompletionData* data);
|
||||||
|
virtual void aborted(CompletionData* data, const lw::Exception& e);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* lw-support/src/lib/Util/FIFO.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void FIFO::reallocBuffer(size_t required)
|
||||||
|
{
|
||||||
|
// sanity checks
|
||||||
|
if(required < bufferSize) return;
|
||||||
|
if(required > maxSize) throw FIFOOverflow();
|
||||||
|
|
||||||
|
// try to reduce frequency of small allocations
|
||||||
|
required = std::min(required + 128, maxSize);
|
||||||
|
|
||||||
|
// try to allocate memory
|
||||||
|
char* newBuffer = 0;
|
||||||
|
try {
|
||||||
|
newBuffer = new char[required];
|
||||||
|
}
|
||||||
|
catch(std::bad_alloc&) {
|
||||||
|
throw FIFOOutOfMemory(required);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy across old data
|
||||||
|
if(data) {
|
||||||
|
memcpy(newBuffer, data, dataSize);
|
||||||
|
data = newBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// free old memory
|
||||||
|
delete [] buffer;
|
||||||
|
buffer = newBuffer;
|
||||||
|
bufferSize = required;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
/* lw-support/src/lib/Util/FIFO.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief FIFO memory buffer.
|
||||||
|
|
||||||
|
This class is an implementation of a FIFO memory buffer. It has an
|
||||||
|
absolute maximum upper limit on how much memory has been consumed, and
|
||||||
|
you can specify how much of that limit is initially allocated. The
|
||||||
|
functions are all inline for speed.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class FIFO {
|
||||||
|
private:
|
||||||
|
// These refer to the block of memory that is allocated.
|
||||||
|
char* buffer;
|
||||||
|
size_t bufferSize, maxSize;
|
||||||
|
|
||||||
|
// These refer to the data in the buffer.
|
||||||
|
char* data; // set to zero if dataSize == 0
|
||||||
|
size_t dataSize;
|
||||||
|
|
||||||
|
// Reallocate buffer ('required' is the new total buffer size that
|
||||||
|
// is needed).
|
||||||
|
void reallocBuffer(size_t required);
|
||||||
|
|
||||||
|
// Fast realloc check, also moves data if that would save an
|
||||||
|
// allocation.
|
||||||
|
inline void ensureBuffer(size_t required)
|
||||||
|
{
|
||||||
|
if(required > bufferSize) {
|
||||||
|
reallocBuffer(required);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data && (data + required) > (buffer + bufferSize)) {
|
||||||
|
memmove(buffer, data, dataSize);
|
||||||
|
data = buffer;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
//BEGIN // Constructors etc. ///////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Constructor.
|
||||||
|
|
||||||
|
\param hint This hints at how much memory should be allocated.
|
||||||
|
\param maxSize The absolute maximum number of bytes to allocate.
|
||||||
|
|
||||||
|
Sets up the FIFO buffer. The absolute maximum size must be specified
|
||||||
|
here (it can be changed later). A hint as to how much memory to
|
||||||
|
initially allocate must also be provided.
|
||||||
|
|
||||||
|
*/
|
||||||
|
FIFO(size_t hint, size_t maxSize)
|
||||||
|
: buffer((hint && maxSize) ? new char[std::min(hint, maxSize)] : 0),
|
||||||
|
bufferSize(hint), maxSize(maxSize),
|
||||||
|
data(0), dataSize(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Destructor (frees allocated memory).
|
||||||
|
~FIFO()
|
||||||
|
{ delete [] buffer; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Change absolute maximum size.
|
||||||
|
|
||||||
|
\param newMaxSize The buffer's new absolute maximum size in bytes.
|
||||||
|
\throws FIFOOverflow if the buffer currently contains more than
|
||||||
|
\a newMaxSize bytes.
|
||||||
|
|
||||||
|
This function will change the upper size limit of the FIFO memory
|
||||||
|
buffer. It can be used to make the buffer larger at any time. It can
|
||||||
|
also be used to make the buffer smaller (possibly freeing up memory
|
||||||
|
in the process); but you cannot make the buffer so small that its
|
||||||
|
contents no longer fit.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline void setMaxSize(size_t newMaxSize)
|
||||||
|
{
|
||||||
|
if(newMaxSize < dataSize) throw FIFOOverflow();
|
||||||
|
maxSize = newMaxSize;
|
||||||
|
|
||||||
|
// might have to reallocate the buffer if it is too big
|
||||||
|
if(bufferSize > newMaxSize) reallocBuffer(newMaxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Query the absolute maximum size.
|
||||||
|
inline size_t getMaxSize() const
|
||||||
|
{
|
||||||
|
return maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Buffering functions /////////////////////////////////////
|
||||||
|
|
||||||
|
/*! \brief Gets ready to add some data.
|
||||||
|
|
||||||
|
\param amt The maximum amount of data the buffer will be required to
|
||||||
|
accept.
|
||||||
|
\returns A pointer to the memory location at which the data should
|
||||||
|
be written.
|
||||||
|
\throws FIFOOverflow if the buffer is too small.
|
||||||
|
|
||||||
|
This function ensures that the buffer has enough space to accept at
|
||||||
|
least \a amt bytes of additional data. This might require allocating
|
||||||
|
more memory or moving the current buffer data around. If you call
|
||||||
|
this twice in a row, then the first call is totally forgotten. It
|
||||||
|
doesn't change the object's internal state; it just makes sure that
|
||||||
|
you can add \a amt bytes if you want to, and tells you where.
|
||||||
|
|
||||||
|
\sa addedData()
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline char* getBuffer(size_t amt)
|
||||||
|
{
|
||||||
|
ensureBuffer(amt + dataSize);
|
||||||
|
return data ? data + dataSize : buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Records how much data was added to the buffer.
|
||||||
|
|
||||||
|
\param amt The amount of data that was actually added to the buffer.
|
||||||
|
|
||||||
|
Once you have prepared the buffer with getBuffer(), you must add the
|
||||||
|
data. This function records how much data was added (so that new
|
||||||
|
data will be added after it).
|
||||||
|
|
||||||
|
\warning No checking is done on \a amt, so make sure you put the
|
||||||
|
right amount. Also make sure that getBuffer() and addedData()
|
||||||
|
calls occur in pairs.
|
||||||
|
|
||||||
|
\sa getBuffer(), addData()
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline void addedData(size_t amt)
|
||||||
|
{
|
||||||
|
if(!data) data = buffer;
|
||||||
|
dataSize += amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Adds some data to the buffer.
|
||||||
|
|
||||||
|
\param amt The number of bytes to add.
|
||||||
|
\param newData The data to add.
|
||||||
|
\throws FIFOOverflow if the buffer is too small.
|
||||||
|
|
||||||
|
Adds a specific block of data to the buffer. This can only be used
|
||||||
|
if the data is coming from somewhere else in memory (e.g. an
|
||||||
|
mmap()ed file), and you know in advance how much there is.
|
||||||
|
|
||||||
|
\sa addedData()
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline void addData(const char* newData, size_t amt)
|
||||||
|
{
|
||||||
|
ensureBuffer(amt + dataSize);
|
||||||
|
if(data) {
|
||||||
|
memcpy(data + dataSize, newData, amt);
|
||||||
|
dataSize += amt;
|
||||||
|
} else {
|
||||||
|
memcpy(data = buffer, newData, amt);
|
||||||
|
dataSize = amt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Adds a byte of data to the buffer.
|
||||||
|
|
||||||
|
\param byte The byte of data to add.
|
||||||
|
\throws FIFOOverflow if the buffer is too small.
|
||||||
|
|
||||||
|
Adds a single byte of data to the buffer. This is the most efficient
|
||||||
|
method for byte-oriented buffering.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline void addByte(char byte)
|
||||||
|
{
|
||||||
|
ensureBuffer(dataSize + 1);
|
||||||
|
if(data) {
|
||||||
|
data[dataSize++] = byte;
|
||||||
|
} else {
|
||||||
|
*(data = buffer) = byte;
|
||||||
|
dataSize = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Reading functions ///////////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns how many bytes of data there are in the buffer.
|
||||||
|
inline size_t getSize() const
|
||||||
|
{
|
||||||
|
return dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns how many bytes of data there are in the buffer.
|
||||||
|
inline size_t size() const
|
||||||
|
{
|
||||||
|
return dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns how many bytes are available to the buffer.
|
||||||
|
inline size_t getRemaining() const
|
||||||
|
{
|
||||||
|
return maxSize - dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns how many (preallocated) bytes are available to the buffer.
|
||||||
|
inline size_t getRemainingPreallocated() const
|
||||||
|
{
|
||||||
|
return bufferSize - dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns pointer to start of buffer (0 if empty).
|
||||||
|
inline const char* getData() const
|
||||||
|
{
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Retrieves some data from the buffer.
|
||||||
|
|
||||||
|
\param amt The number of bytes to retrieve.
|
||||||
|
\throws FIFOUnderflow if the buffer contains less than \a amt bytes.
|
||||||
|
\returns A pointer to the start of the data (0 if \a amt = 0).
|
||||||
|
|
||||||
|
This function allows you to get data out of the buffer. It will
|
||||||
|
record how many bytes were used, so that space will be free the next
|
||||||
|
time anyone wants to use it.
|
||||||
|
|
||||||
|
\warning The pointer returned may become invalid as soon as you
|
||||||
|
perform any other operation on the FIFO, so you must finish
|
||||||
|
using the data it points to before calling any other functions.
|
||||||
|
|
||||||
|
\sa getSize()
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline char* getData(size_t amt)
|
||||||
|
{
|
||||||
|
// sanity checking
|
||||||
|
if(!amt) return 0;
|
||||||
|
if(amt > dataSize) throw FIFOUnderflow();
|
||||||
|
|
||||||
|
// eat data
|
||||||
|
char* result = data;
|
||||||
|
if(!(dataSize -= amt)) data = 0;
|
||||||
|
else data += amt;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Gets a single byte of data from the buffer.
|
||||||
|
|
||||||
|
\returns The byte of data removed from the buffer.
|
||||||
|
\throws FIFOUnderflow if the buffer is empty.
|
||||||
|
\returns A single byte of data.
|
||||||
|
|
||||||
|
Retrieves a single byte of data from the buffer. This is the most
|
||||||
|
efficient method for byte-oriented buffering.
|
||||||
|
|
||||||
|
*/
|
||||||
|
inline char getByte()
|
||||||
|
{
|
||||||
|
if(!dataSize) throw FIFOUnderflow();
|
||||||
|
char byte = *data;
|
||||||
|
if(!--dataSize) data = 0;
|
||||||
|
else ++data;
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//BEGIN // Convenience functions ///////////////////////////////////
|
||||||
|
|
||||||
|
/// Returns true if the buffer is empty.
|
||||||
|
inline bool isEmpty() const
|
||||||
|
{
|
||||||
|
return !dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the buffer is empty.
|
||||||
|
inline bool empty() const
|
||||||
|
{
|
||||||
|
return !dataSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the buffer is full.
|
||||||
|
inline bool isFull() const
|
||||||
|
{
|
||||||
|
return dataSize == maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Empties the buffer.
|
||||||
|
inline void clear()
|
||||||
|
{
|
||||||
|
data = 0;
|
||||||
|
dataSize = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* lw-support/src/lib/Util/Key.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
Key::keyType Key::globalKey = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wstring Key::toString() const
|
||||||
|
{
|
||||||
|
std::wostringstream str;
|
||||||
|
|
||||||
|
str << L"{KEY{!"
|
||||||
|
<< key
|
||||||
|
<< L"}}";
|
||||||
|
|
||||||
|
return str.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
lw::Key Key::fromString(const std::wstring& strIn)
|
||||||
|
{
|
||||||
|
std::wstring str = stripWhitespace(strIn);
|
||||||
|
std::wstring::size_type end = str.size();
|
||||||
|
|
||||||
|
if(end < 9 || str.substr(0, 6) != L"!KEY{!" || str.substr(end - 2, 2) != L"}}")
|
||||||
|
throw ParseError(L"Not a valid key.", str, 0);
|
||||||
|
return Key(strToUInt64(str.substr(6, end - 8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/* lw-support/src/lib/Util/Key.h
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Key object.
|
||||||
|
|
||||||
|
The key object is used to store some form of identifying key. Typically this will be used to store a
|
||||||
|
unique key number.
|
||||||
|
|
||||||
|
\note We use a 64-bit integer to store the key. It is somewhat conceivable that this could be
|
||||||
|
wrapped around at some point. If you're really generating keys that frequently, don't use this
|
||||||
|
class and instead write a 128-bit or wider class.
|
||||||
|
|
||||||
|
*/
|
||||||
|
class Key {
|
||||||
|
public:
|
||||||
|
/// Constructor. Generates an invalid key.
|
||||||
|
Key()
|
||||||
|
: key(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Copy constructor.
|
||||||
|
Key(const Key& other)
|
||||||
|
: key(other.key)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/// Assignment operator.
|
||||||
|
Key& operator=(Key other)
|
||||||
|
{ key = other.key; return *this; }
|
||||||
|
|
||||||
|
/// Compares two keys for equality.
|
||||||
|
bool operator==(Key other) const
|
||||||
|
{ return key == other.key; }
|
||||||
|
|
||||||
|
/// Compares two keys for inequality.
|
||||||
|
bool operator!=(Key other) const
|
||||||
|
{ return key != other.key; }
|
||||||
|
|
||||||
|
/// Allows ordering by key.
|
||||||
|
bool operator<(Key other) const
|
||||||
|
{ return key < other.key; }
|
||||||
|
|
||||||
|
/// Returns status of key.
|
||||||
|
bool operator!() const
|
||||||
|
{ return !key; }
|
||||||
|
|
||||||
|
/// Returns status of key.
|
||||||
|
operator void*() const
|
||||||
|
{ return key ? (void*)this : 0; }
|
||||||
|
|
||||||
|
/// Returns status of key.
|
||||||
|
bool valid() const
|
||||||
|
{ return key; }
|
||||||
|
|
||||||
|
/// Makes the key invalid.
|
||||||
|
void clear()
|
||||||
|
{ key = 0; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*! \brief Represent the key as a string.
|
||||||
|
|
||||||
|
\returns A string representing the value of the key.
|
||||||
|
|
||||||
|
This function allows the key to be represented as a string. The string will always be started
|
||||||
|
with the sequence \c {KEY{ and it will always end with \c }} but its contents are implementation
|
||||||
|
defined. The string generated will consist only of ASCII characters and all characters will be
|
||||||
|
printable.
|
||||||
|
|
||||||
|
*/
|
||||||
|
std::wstring toString() const;
|
||||||
|
|
||||||
|
/// Construct a key from a string.
|
||||||
|
static lw::Key fromString(const std::wstring& str);
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef uint_fast64_t keyType;
|
||||||
|
keyType key;
|
||||||
|
|
||||||
|
// Explicitly construct a key from a value.
|
||||||
|
Key(keyType key) : key(key) { }
|
||||||
|
|
||||||
|
static keyType globalKey;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Generate a program-wide unique key.
|
||||||
|
static Key get()
|
||||||
|
{ ++globalKey; return Key(globalKey); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,355 @@
|
||||||
|
/* lw-support/src/lib/Util/Log.cpp
|
||||||
|
*
|
||||||
|
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||||
|
* COPYING for more information / terms of license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace lw {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log stderr(std::wcerr, true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log::Log(std::wostream& outStream, bool enabled)
|
||||||
|
: out(outStream), wstr(0), enabled(enabled), streamWidth(78),
|
||||||
|
dateExtended(true), dateDecimal(false), dateFormat(lw::ISO8601FormatCalendar),
|
||||||
|
datePrecision(lw::ISO8601PrecisionFull)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log::Log(const std::string& fileName, bool enabled)
|
||||||
|
: out(file), wstr(0), enabled(enabled), streamWidth(78),
|
||||||
|
dateExtended(true), dateDecimal(false), dateFormat(lw::ISO8601FormatCalendar),
|
||||||
|
datePrecision(lw::ISO8601PrecisionFull)
|
||||||
|
{
|
||||||
|
file.open(fileName.c_str());
|
||||||
|
// FIXME: better exception required
|
||||||
|
if(!file) throw lw::FileNotFound(fileName.c_str())
|
||||||
|
.chain(L"Log::Log()");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log::~Log()
|
||||||
|
{
|
||||||
|
delete wstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void Log::checkBox()
|
||||||
|
{
|
||||||
|
if(!wstr) throw BoxException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::setStreamWidth(size_t streamWidth)
|
||||||
|
{
|
||||||
|
this->streamWidth = streamWidth;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::setDateFormat(bool dateExtended, bool dateDecimal,
|
||||||
|
lw::ISO8601Format dateFormat, lw::ISO8601Precision datePrecision)
|
||||||
|
{
|
||||||
|
this->dateExtended = dateExtended;
|
||||||
|
this->dateDecimal = dateDecimal;
|
||||||
|
this->dateFormat = dateFormat;
|
||||||
|
this->datePrecision = datePrecision;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::startBox(const std::wstring& subject)
|
||||||
|
{
|
||||||
|
if(wstr) throw BoxException();
|
||||||
|
wstr = new std::wostringstream();
|
||||||
|
|
||||||
|
wstr->clear();
|
||||||
|
*wstr << L"==== "
|
||||||
|
<< DateTime::now().toString(dateExtended, dateDecimal,
|
||||||
|
dateFormat, datePrecision)
|
||||||
|
<< L" ====";
|
||||||
|
|
||||||
|
int remaining = streamWidth - (wstr->str().length() + subject.length() + 6);
|
||||||
|
if(remaining > 0) *wstr << std::wstring(remaining, '=');
|
||||||
|
*wstr << L' '
|
||||||
|
<< subject
|
||||||
|
<< L" ====\n\n";
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::enable(bool enabled)
|
||||||
|
{
|
||||||
|
this->enabled = enabled;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::disable()
|
||||||
|
{
|
||||||
|
enabled = false;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::endBox()
|
||||||
|
{
|
||||||
|
checkBox();
|
||||||
|
if(enabled) out << wstr->str() << L"\n\n" << std::endl;
|
||||||
|
delete wstr;
|
||||||
|
wstr = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::abortBox()
|
||||||
|
{
|
||||||
|
delete wstr;
|
||||||
|
wstr = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::hexDumpData(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
checkBox();
|
||||||
|
|
||||||
|
const size_t blocksPerLine = std::max(streamWidth / 34, size_t(1)),
|
||||||
|
charsPerLine = blocksPerLine * 8,
|
||||||
|
upperBlockSize = charsPerLine * ((amt + charsPerLine - 1) / charsPerLine);
|
||||||
|
size_t blocksThisLine = 0, lineStartPos = 0;
|
||||||
|
wchar_t ascii[charsPerLine + 1];
|
||||||
|
ascii[charsPerLine] = 0;
|
||||||
|
|
||||||
|
*wstr << L"Hex dump, "
|
||||||
|
<< amt
|
||||||
|
<< " bytes:\n"
|
||||||
|
<< std::hex
|
||||||
|
<< std::setfill(L'0');
|
||||||
|
|
||||||
|
for(size_t pos = 0; pos < amt; ++pos) {
|
||||||
|
uint8_t ch = data[pos];
|
||||||
|
if(!(pos & 7)) {
|
||||||
|
*wstr << L' ';
|
||||||
|
if(blocksThisLine++ == blocksPerLine) {
|
||||||
|
blocksThisLine = 1;
|
||||||
|
*wstr << ascii << L"\n ";
|
||||||
|
lineStartPos = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*wstr << std::setw(2) << uint(ch) << L' ';
|
||||||
|
ascii[pos - lineStartPos] = wchar_t(isprint(ch) ? ch : '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t pos = amt; pos < upperBlockSize; ++pos) {
|
||||||
|
if(!(pos & 7)) {
|
||||||
|
*wstr << L' ';
|
||||||
|
if(blocksThisLine++ == blocksPerLine) break;
|
||||||
|
}
|
||||||
|
*wstr << L" ";
|
||||||
|
ascii[pos - lineStartPos] = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
*wstr << L' ' << ascii << L'\n';
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log& Log::asciiDumpData(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
checkBox();
|
||||||
|
|
||||||
|
*wstr << L"- ASCII dump, "
|
||||||
|
<< amt
|
||||||
|
<< " bytes: -----------------\n"
|
||||||
|
<< std::hex
|
||||||
|
<< std::setfill(L'0');
|
||||||
|
|
||||||
|
for(size_t pos = 0; pos < amt; ++pos) {
|
||||||
|
switch(data[pos]) {
|
||||||
|
case '\r':
|
||||||
|
*wstr << L"\\r";
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
*wstr << L"\\n\n";
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '\t':
|
||||||
|
*wstr << L"\\t";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isprint(data[pos])) {
|
||||||
|
*wstr << wchar_t(data[pos]);
|
||||||
|
} else {
|
||||||
|
*wstr << L'<'
|
||||||
|
<< std::setw(2)
|
||||||
|
<< uint(uint8_t(data[pos]))
|
||||||
|
<< L'>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*wstr << L"\n- End of dump ---------------------\n";
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log::LogManipArgStart Log::start(const std::wstring& subject)
|
||||||
|
{
|
||||||
|
return LogManipArgStart(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log::LogManipArgHex Log::hexDump(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
return LogManipArgHex(data, amt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Log::LogManipArgAscii Log::asciiDump(const char* data, size_t amt)
|
||||||
|
{
|
||||||
|
return LogManipArgAscii(data, amt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
Log& operator<<(Log& log, const Log::LogManip& l)
|
||||||
|
{
|
||||||
|
log.checkBox();
|
||||||
|
|
||||||
|
switch(l) {
|
||||||
|
case Log::end:
|
||||||
|
log.endBox();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Log::abort:
|
||||||
|
log.abortBox();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
Log& operator<<(Log& log, const lw::Log::LogManipArgStart& l)
|
||||||
|
{
|
||||||
|
log.startBox(l.subject);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
Log& operator<<(Log& log, const lw::Log::LogManipArgHex& l)
|
||||||
|
{
|
||||||
|
log.hexDumpData(l.data, l.amt);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
Log& operator<<(Log& log, const lw::Log::LogManipArgAscii& l)
|
||||||
|
{
|
||||||
|
log.asciiDumpData(l.data, l.amt);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
Log& operator<<(Log& log, const char* const& str)
|
||||||
|
{
|
||||||
|
if(!str) {
|
||||||
|
*(log.wstr) << L"{{{null}}}";
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// attempt UTF-8 conversion
|
||||||
|
*(log.wstr) << lw::utf8ToUcs4(str);
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
// `safe' conversion
|
||||||
|
*(log.wstr) << L"{{{unknown encoding}}}";
|
||||||
|
for(const char* q = str; *q; ++q) {
|
||||||
|
switch(*q) {
|
||||||
|
case '\n':
|
||||||
|
case '\t':
|
||||||
|
case ' ' ... '~': // 32 ... 126
|
||||||
|
log.wstr->put(*q);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.wstr->put('?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template<>
|
||||||
|
Log& operator<<(Log& log, const std::string& str)
|
||||||
|
{
|
||||||
|
return (log << str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
lw::Log& operator<<(lw::Log& log, const std::exception& e)
|
||||||
|
{
|
||||||
|
return (log << e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
lw::Log& operator<<(lw::Log& log, const lw::Exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << L"[DEBUG] in lw::Exception-specific insertion operator\n";
|
||||||
|
return (log << e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::wostream& operator<<(std::wostream& ostr, const std::exception& e)
|
||||||
|
{
|
||||||
|
ostr << lw::strToUcs4(e.what());
|
||||||
|
return ostr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* options for text editors
|
||||||
|
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||||
|
*/
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue