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>.
|
||||
* Released under the GNU GPLv2. See file COPYING or
|
||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
||||
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||
* COPYING for more information / terms of license.
|
||||
*/
|
||||
|
||||
/*! \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>.
|
||||
* Released under the GNU GPLv2. See file COPYING or
|
||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
||||
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||
* COPYING for more information / terms of license.
|
||||
*/
|
||||
|
||||
// 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
|
||||
|
||||
/* 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>.
|
||||
* Released under the GNU GPLv2. See file COPYING or
|
||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
||||
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||
* COPYING for more information / terms of license.
|
||||
*/
|
||||
|
||||
#ifndef HEADER_liblw_support
|
||||
#define HEADER_liblw_support
|
||||
// This file contains the include guard, and also any includes that are
|
||||
// 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
|
||||
#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
|
||||
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||
vim: expandtab:ts=4:sw=4
|
||||
*/
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#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>.
|
||||
* Released under the GNU GPLv2. See file COPYING or
|
||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
||||
* (c)2005, Laurence Withers. Released under the GNU GPL. See file
|
||||
* COPYING for more information / terms of license.
|
||||
*/
|
||||
|
||||
#include "lw-support"
|
||||
|
||||
// Below are all the includes used throughout the library.
|
||||
|
||||
/* options for text editors
|
||||
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
||||
vim: expandtab:ts=4:sw=4
|
||||
*/
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#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