mirror of
https://github.com/cookiengineer/audacity
synced 2025-10-20 17:41:13 +02:00
New library lib-exceptions
This commit is contained in:
@@ -9,6 +9,7 @@ set( LIBRARIES
|
||||
lib-uuid
|
||||
lib-components
|
||||
lib-basic-ui
|
||||
lib-exceptions
|
||||
)
|
||||
|
||||
if ( ${_OPT}has_networking )
|
||||
|
116
libraries/lib-exceptions/AudacityException.cpp
Normal file
116
libraries/lib-exceptions/AudacityException.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file AudacityException.cpp
|
||||
@brief Implements AudacityException and related
|
||||
|
||||
Paul Licameli
|
||||
|
||||
***********************************************************************/
|
||||
|
||||
#include "AudacityException.h"
|
||||
|
||||
#include <wx/atomic.h>
|
||||
|
||||
#include "BasicUI.h"
|
||||
|
||||
AudacityException::~AudacityException()
|
||||
{
|
||||
}
|
||||
|
||||
void AudacityException::EnqueueAction(
|
||||
std::exception_ptr pException,
|
||||
std::function<void(AudacityException*)> delayedHandler)
|
||||
{
|
||||
BasicUI::CallAfter( [
|
||||
pException = std::move(pException), delayedHandler = std::move(delayedHandler)
|
||||
] {
|
||||
try {
|
||||
std::rethrow_exception(pException);
|
||||
}
|
||||
catch( AudacityException &e )
|
||||
{ delayedHandler( &e ); }
|
||||
} );
|
||||
}
|
||||
|
||||
wxAtomicInt sOutstandingMessages {};
|
||||
|
||||
MessageBoxException::MessageBoxException(
|
||||
ExceptionType exceptionType_, const TranslatableString& caption_)
|
||||
: caption { caption_ }
|
||||
, exceptionType { exceptionType_ }
|
||||
{
|
||||
if (!caption.empty())
|
||||
wxAtomicInc( sOutstandingMessages );
|
||||
else
|
||||
// invalidate me
|
||||
moved = true;
|
||||
}
|
||||
|
||||
// The class needs a copy constructor to be throwable
|
||||
// (or will need it, by C++14 rules). But the copy
|
||||
// needs to act like a move constructor. There must be unique responsibility
|
||||
// for each exception thrown to decrease the global count when it is handled.
|
||||
MessageBoxException::MessageBoxException( const MessageBoxException& that )
|
||||
{
|
||||
caption = that.caption;
|
||||
moved = that.moved;
|
||||
helpUrl = that.helpUrl;
|
||||
exceptionType = that.exceptionType;
|
||||
that.moved = true;
|
||||
}
|
||||
|
||||
MessageBoxException::~MessageBoxException()
|
||||
{
|
||||
if (!moved)
|
||||
// If exceptions are used properly, you should never reach this,
|
||||
// because moved should become true earlier in the object's lifetime.
|
||||
wxAtomicDec( sOutstandingMessages );
|
||||
}
|
||||
|
||||
SimpleMessageBoxException::~SimpleMessageBoxException()
|
||||
{
|
||||
}
|
||||
|
||||
TranslatableString SimpleMessageBoxException::ErrorMessage() const
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
// This is meant to be invoked via wxEvtHandler::CallAfter
|
||||
void MessageBoxException::DelayedHandlerAction()
|
||||
{
|
||||
if (!moved) {
|
||||
// This test prevents accumulation of multiple messages between idle
|
||||
// times of the main even loop. Only the last queued exception
|
||||
// displays its message. We assume that multiple messages have a
|
||||
// common cause such as exhaustion of disk space so that the others
|
||||
// give the user no useful added information.
|
||||
|
||||
using namespace BasicUI;
|
||||
if ( wxAtomicDec( sOutstandingMessages ) == 0 ) {
|
||||
if (exceptionType != ExceptionType::Internal
|
||||
&& ErrorHelpUrl().IsEmpty()) {
|
||||
// We show BadEnvironment and BadUserAction in a similar way
|
||||
ShowMessageBox(
|
||||
ErrorMessage(),
|
||||
MessageBoxOptions{}
|
||||
.Caption(caption.empty() ? DefaultCaption() : caption)
|
||||
.IconStyle(Icon::Error) );
|
||||
}
|
||||
else {
|
||||
using namespace BasicUI;
|
||||
auto type = exceptionType == ExceptionType::Internal
|
||||
? ErrorDialogType::ModalErrorReport : ErrorDialogType::ModalError;
|
||||
ShowErrorDialog( {},
|
||||
(caption.empty() ? DefaultCaption() : caption),
|
||||
ErrorMessage(),
|
||||
ErrorHelpUrl(),
|
||||
ErrorDialogOptions{ type } );
|
||||
}
|
||||
}
|
||||
|
||||
moved = true;
|
||||
}
|
||||
}
|
231
libraries/lib-exceptions/AudacityException.h
Normal file
231
libraries/lib-exceptions/AudacityException.h
Normal file
@@ -0,0 +1,231 @@
|
||||
#ifndef __AUDACITY_EXCEPTION__
|
||||
#define __AUDACITY_EXCEPTION__
|
||||
|
||||
/*!********************************************************************
|
||||
|
||||
Audacity: A Digital Audio Editor
|
||||
|
||||
@file AudacityException.h
|
||||
@brief Declare abstract class AudacityException, some often-used subclasses, and @ref GuardedCall
|
||||
|
||||
Paul Licameli
|
||||
**********************************************************************/
|
||||
|
||||
#include "MemoryX.h"
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
|
||||
#include "Internat.h"
|
||||
|
||||
//! A type of an exception
|
||||
enum class ExceptionType
|
||||
{
|
||||
Internal, //!< Indicates internal failure from Audacity.
|
||||
BadUserAction, //!< Indicates that the user performed an action that is not allowed.
|
||||
BadEnvironment, //!< Indicates problems with environment, such as a full disk
|
||||
};
|
||||
|
||||
//! Base class for exceptions specially processed by the application
|
||||
/*! Objects of this type can be thrown and caught in any thread, stored, and then used by the main
|
||||
thread in later idle time to explain the error condition to the user.
|
||||
*/
|
||||
class AudacityException /* not final */
|
||||
{
|
||||
public:
|
||||
AudacityException() {}
|
||||
virtual ~AudacityException() = 0;
|
||||
|
||||
//! Action to do in the main thread at idle time of the event loop.
|
||||
virtual void DelayedHandlerAction() = 0;
|
||||
|
||||
EXCEPTIONS_API static void EnqueueAction(
|
||||
std::exception_ptr pException,
|
||||
std::function<void(AudacityException*)> delayedHandler);
|
||||
|
||||
protected:
|
||||
//! Make this protected to prevent slicing copies
|
||||
AudacityException( const AudacityException& ) = default;
|
||||
|
||||
//! Don't allow moves of this class or subclasses
|
||||
// see https://bugzilla.audacityteam.org/show_bug.cgi?id=2442
|
||||
AudacityException( AudacityException&& ) PROHIBITED;
|
||||
|
||||
//! Disallow assignment
|
||||
AudacityException &operator = ( const AudacityException & ) PROHIBITED;
|
||||
};
|
||||
|
||||
//! Abstract AudacityException subclass displays a message, specified by further subclass
|
||||
/*! At most one message will be displayed for each pass through the main event idle loop,
|
||||
no matter how many exceptions were caught. */
|
||||
class EXCEPTIONS_API MessageBoxException /* not final */
|
||||
: public AudacityException
|
||||
{
|
||||
//! Privatize the inherited function
|
||||
using AudacityException::DelayedHandlerAction;
|
||||
|
||||
//! Do not allow subclasses to change behavior, except by overriding ErrorMessage().
|
||||
void DelayedHandlerAction() final;
|
||||
|
||||
protected:
|
||||
//! If default-constructed with empty caption, it makes no message box.
|
||||
explicit MessageBoxException(
|
||||
ExceptionType exceptionType, //!< Exception type
|
||||
const TranslatableString &caption //!< Shown in message box's frame; not the actual message
|
||||
);
|
||||
~MessageBoxException() override;
|
||||
|
||||
MessageBoxException( const MessageBoxException& );
|
||||
|
||||
//! %Format the error message for this exception.
|
||||
virtual TranslatableString ErrorMessage() const = 0;
|
||||
virtual wxString ErrorHelpUrl() const { return helpUrl; };
|
||||
|
||||
private:
|
||||
TranslatableString caption; //!< Stored caption
|
||||
ExceptionType exceptionType; //!< Exception type
|
||||
|
||||
mutable bool moved { false }; //!< Whether @c *this has been the source of a copy
|
||||
protected:
|
||||
mutable wxString helpUrl{ "" };
|
||||
};
|
||||
|
||||
//! A MessageBoxException that shows a given, unvarying string.
|
||||
class EXCEPTIONS_API SimpleMessageBoxException /* not final */
|
||||
: public MessageBoxException
|
||||
{
|
||||
public:
|
||||
explicit SimpleMessageBoxException(
|
||||
ExceptionType exceptionType, //!< Exception type
|
||||
const TranslatableString &message_, //<! Message to show
|
||||
const TranslatableString &caption = XO("Message"), //<! Short caption in frame around message
|
||||
const wxString &helpUrl_ = "" // Optional URL for help.
|
||||
)
|
||||
: MessageBoxException { exceptionType, caption }
|
||||
, message{ message_ }
|
||||
{
|
||||
helpUrl = helpUrl_;
|
||||
}
|
||||
~SimpleMessageBoxException() override;
|
||||
|
||||
SimpleMessageBoxException( const SimpleMessageBoxException& ) = default;
|
||||
SimpleMessageBoxException &operator = (
|
||||
SimpleMessageBoxException && ) PROHIBITED;
|
||||
|
||||
// Format a default, internationalized error message for this exception.
|
||||
virtual TranslatableString ErrorMessage() const override;
|
||||
|
||||
private:
|
||||
TranslatableString message; //!< Stored message
|
||||
};
|
||||
|
||||
|
||||
//! A default template parameter for @ref GuardedCall
|
||||
struct DefaultDelayedHandlerAction
|
||||
{
|
||||
void operator () (AudacityException *pException) const
|
||||
{
|
||||
if ( pException )
|
||||
pException->DelayedHandlerAction();
|
||||
}
|
||||
};
|
||||
|
||||
//! A default template parameter for @ref GuardedCall<R>
|
||||
/*! @tparam R return type from GuardedCall (or convertible to it) */
|
||||
template <typename R> struct SimpleGuard
|
||||
{
|
||||
explicit SimpleGuard(
|
||||
R value //!< The value to return from GurdedCall when an exception is handled
|
||||
)
|
||||
: m_value{ value } {}
|
||||
R operator () ( AudacityException * ) const { return m_value; }
|
||||
const R m_value;
|
||||
};
|
||||
|
||||
//! Specialization of SimpleGuard, also defining a default value
|
||||
template<> struct SimpleGuard<bool>
|
||||
{
|
||||
explicit SimpleGuard(
|
||||
bool value //!< The value to return from @ref GaurdedCall when an exception is handled
|
||||
)
|
||||
: m_value{ value } {}
|
||||
bool operator () ( AudacityException * ) const { return m_value; }
|
||||
static SimpleGuard Default()
|
||||
{ return SimpleGuard{ false }; }
|
||||
const bool m_value;
|
||||
};
|
||||
|
||||
//! Specialization of SimpleGuard, also defining a default value
|
||||
template<> struct SimpleGuard<void>
|
||||
{
|
||||
SimpleGuard() {}
|
||||
void operator () ( AudacityException * ) const {}
|
||||
static SimpleGuard Default() { return {}; }
|
||||
};
|
||||
|
||||
//! Convert a value to a handler function returning that value, suitable for @ref GuardedCall<R>
|
||||
template < typename R >
|
||||
SimpleGuard< R > MakeSimpleGuard( R value )
|
||||
{ return SimpleGuard< R >{ value }; }
|
||||
|
||||
//! Convert a value to a no-op handler function, suitable for @ref GuardedCall<void>
|
||||
inline SimpleGuard< void > MakeSimpleGuard() { return {}; }
|
||||
|
||||
/*!
|
||||
Executes a given function (typically a lamba), in any thread.
|
||||
|
||||
If there is any exception, can invoke another given function as handler, which may rethrow that or
|
||||
another exception, but usually just returns the value for the GuardedCall.
|
||||
|
||||
If AudacityException is handled, then it queues up a delayed handler action for execution later in
|
||||
the event loop at idle time, on the main thread; typically this informs the user of the error.
|
||||
|
||||
The default delayed handler action is simply to invoke a method of the AudacityException, but this
|
||||
too can be specified otherwise by a third function.
|
||||
|
||||
@tparam R Return type, defaulted to void, or else the only explicit template parameter
|
||||
@tparam F1 deduced type of body function; takes no arguments, returns @b R
|
||||
@tparam F2 deduced type of handler function, or defaulted to @ref SimpleGuard<R>;
|
||||
takes pointer to AudacityException, which is null when some other type of exception is caught;
|
||||
return value is converted to @b R
|
||||
@tparam F3 deduced type of delayed handler function, if a nondefault argument is given;
|
||||
takes pointer to AudacityException, return value is unused
|
||||
*/
|
||||
template <
|
||||
typename R = void,
|
||||
|
||||
typename F1, // function object with signature R()
|
||||
|
||||
typename F2 = SimpleGuard< R > // function object
|
||||
// with signature R( AudacityException * )
|
||||
>
|
||||
//! Execute some code on any thread; catch any AudacityException; enqueue error report on the main thread
|
||||
R GuardedCall(
|
||||
const F1 &body, //!< typically a lambda
|
||||
const F2 &handler = F2::Default(), //!< default just returns false or void; see also @ref MakeSimpleGuard
|
||||
std::function<void(AudacityException*)> delayedHandler
|
||||
= DefaultDelayedHandlerAction{} /*!<called later in the main thread,
|
||||
passing it a stored exception; usually defaulted */
|
||||
)
|
||||
{
|
||||
try { return body(); }
|
||||
catch ( AudacityException &e ) {
|
||||
|
||||
auto end = finally([&]{
|
||||
// At this point, e is the "current" exception, but not "uncaught"
|
||||
// unless it was rethrown by handler. handler might also throw some
|
||||
// other exception object.
|
||||
if (!std::uncaught_exception()) {
|
||||
auto pException = std::current_exception(); // This points to e
|
||||
AudacityException::EnqueueAction(
|
||||
pException, std::move(delayedHandler));
|
||||
}
|
||||
});
|
||||
|
||||
return handler( &e );
|
||||
}
|
||||
catch ( ... ) {
|
||||
return handler( nullptr );
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
34
libraries/lib-exceptions/CMakeLists.txt
Normal file
34
libraries/lib-exceptions/CMakeLists.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
#[[
|
||||
Toolkit neutral library for exceptions.
|
||||
|
||||
Abstract class AudacityException with a member function for a delayed
|
||||
handler action, enqueued in the main thread;
|
||||
|
||||
Some commonly useful subclasses, with delayed handlers that are no-ops or
|
||||
are displays of messages to the user;
|
||||
|
||||
Function template GuardedCall which stops propagation of exceptions and
|
||||
enqueues the delayed action.
|
||||
|
||||
But this library does NOT define a top-level handler for the whole application,
|
||||
to catch all otherwise uncaught exceptions. That is a responsibility of high
|
||||
level code.
|
||||
]]#
|
||||
|
||||
list( APPEND SOURCES
|
||||
AudacityException.cpp
|
||||
AudacityException.h
|
||||
InconsistencyException.cpp
|
||||
InconsistencyException.h
|
||||
UserException.cpp
|
||||
UserException.h
|
||||
)
|
||||
set( LIBRARIES
|
||||
lib-utility-interface
|
||||
lib-basic-ui-interface
|
||||
PRIVATE
|
||||
wxBase
|
||||
)
|
||||
audacity_library( lib-exceptions "${SOURCES}" "${LIBRARIES}"
|
||||
"" ""
|
||||
)
|
35
libraries/lib-exceptions/InconsistencyException.cpp
Normal file
35
libraries/lib-exceptions/InconsistencyException.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
@file InconsistencyException.cpp
|
||||
@brief Implements InconsistencyException
|
||||
|
||||
|
||||
Created by Paul Licameli on 11/27/16.
|
||||
|
||||
*/
|
||||
|
||||
#include "InconsistencyException.h"
|
||||
#include <wx/filename.h>
|
||||
|
||||
InconsistencyException::~InconsistencyException()
|
||||
{
|
||||
}
|
||||
|
||||
TranslatableString InconsistencyException::ErrorMessage() const
|
||||
{
|
||||
// Shorten the path
|
||||
wxString path { file };
|
||||
auto sub = wxString{ wxFILE_SEP_PATH } + "src" + wxFILE_SEP_PATH;
|
||||
auto index = path.Find(sub);
|
||||
if (index != wxNOT_FOUND)
|
||||
path = path.Mid(index + sub.size());
|
||||
|
||||
#ifdef __func__
|
||||
return
|
||||
XO("Internal error in %s at %s line %d.\nPlease inform the Audacity team at https://forum.audacityteam.org/.")
|
||||
.Format( func, path, line );
|
||||
#else
|
||||
return
|
||||
XO("Internal error at %s line %d.\nPlease inform the Audacity team at https://forum.audacityteam.org/.")
|
||||
.Format( path, line );
|
||||
#endif
|
||||
}
|
81
libraries/lib-exceptions/InconsistencyException.h
Normal file
81
libraries/lib-exceptions/InconsistencyException.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*!
|
||||
@file InconsistencyException.h
|
||||
@brief MessageBoxException for violation of preconditions or assertions
|
||||
|
||||
Created by Paul Licameli on 11/27/16.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __AUDACITY_INCONSISTENCY_EXCEPTION__
|
||||
#define __AUDACITY_INCONSISTENCY_EXCEPTION__
|
||||
|
||||
#include "AudacityException.h"
|
||||
|
||||
//! Exception that should be impossible in production, thrown only from provably unreachable places
|
||||
/*! Some errors that formerly were assertion violations now throw exceptions,
|
||||
even in production code. These may be violations of function preconditions
|
||||
or the results of logical errors internal to functions. These conditions
|
||||
are supposed to be deducible statically as never happening.
|
||||
|
||||
The error message identifies source file and line number, possibly the function too (depending on
|
||||
the compiler), and suggests that the user inform the development team.
|
||||
*/
|
||||
class EXCEPTIONS_API InconsistencyException final : public MessageBoxException
|
||||
{
|
||||
public:
|
||||
InconsistencyException ()
|
||||
: MessageBoxException{ ExceptionType::Internal, XO ("Internal Error") }
|
||||
{}
|
||||
|
||||
//! Don't call this directly but use @ref CONSTRUCT_INCONSISTENCY_EXCEPTION or @ref THROW_INCONSISTENCY_EXCEPTION
|
||||
explicit InconsistencyException(
|
||||
const char *fn, //!< file name supplied by preprocessor
|
||||
const char *f, //!< function name supplied by preprocessor
|
||||
unsigned l //!< line number supplied by preprocessor
|
||||
)
|
||||
: MessageBoxException { ExceptionType::Internal, XO("Internal Error") }
|
||||
, func { fn }, file { f }, line { l }
|
||||
{}
|
||||
|
||||
InconsistencyException(InconsistencyException&& that)
|
||||
: MessageBoxException(std::move(that))
|
||||
, func{ that.func }
|
||||
, file{ that.file }
|
||||
, line{ that.line }
|
||||
{}
|
||||
|
||||
~InconsistencyException() override;
|
||||
|
||||
unsigned GetLine() const { return line; }
|
||||
|
||||
private:
|
||||
// Format a default, internationalized error message for this exception.
|
||||
TranslatableString ErrorMessage() const override;
|
||||
|
||||
const char *func {};
|
||||
const char *file {};
|
||||
unsigned line {};
|
||||
};
|
||||
|
||||
#ifdef __func__
|
||||
|
||||
#define CONSTRUCT_INCONSISTENCY_EXCEPTION \
|
||||
InconsistencyException( __func__, __FILE__ , __LINE__ )
|
||||
|
||||
#else
|
||||
|
||||
/*! @def CONSTRUCT_INCONSISTENCY_EXCEPTION
|
||||
@brief Construct InconsistencyException, using C++ preprocessor to identify the source code location
|
||||
|
||||
For cases where the exception object is not immediately thrown */
|
||||
#define CONSTRUCT_INCONSISTENCY_EXCEPTION \
|
||||
InconsistencyException( "", __FILE__ , __LINE__ )
|
||||
|
||||
#endif
|
||||
|
||||
/*! @def THROW_INCONSISTENCY_EXCEPTION
|
||||
@brief Throw InconsistencyException, using C++ preprocessor to identify the source code location
|
||||
*/
|
||||
#define THROW_INCONSISTENCY_EXCEPTION throw CONSTRUCT_INCONSISTENCY_EXCEPTION
|
||||
|
||||
#endif
|
17
libraries/lib-exceptions/UserException.cpp
Normal file
17
libraries/lib-exceptions/UserException.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
/*!
|
||||
@file UserException.cpp
|
||||
@brief implements UserException
|
||||
|
||||
Created by Paul Licameli on 11/27/16.
|
||||
|
||||
*/
|
||||
|
||||
#include "UserException.h"
|
||||
|
||||
UserException::~UserException()
|
||||
{
|
||||
}
|
||||
|
||||
void UserException::DelayedHandlerAction()
|
||||
{
|
||||
}
|
26
libraries/lib-exceptions/UserException.h
Normal file
26
libraries/lib-exceptions/UserException.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*!
|
||||
@file UserException.h
|
||||
@brief An AudacityException with no visible message
|
||||
|
||||
Created by Paul Licameli on 11/27/16.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __AUDACITY_USER_EXCEPTION__
|
||||
#define __AUDACITY_USER_EXCEPTION__
|
||||
|
||||
#include "AudacityException.h"
|
||||
|
||||
//! Can be thrown when user cancels operations, as with a progress dialog. Delayed handler does nothing
|
||||
/*! This class does not inherit from MessageBoxException. */
|
||||
class EXCEPTIONS_API UserException final : public AudacityException
|
||||
{
|
||||
public:
|
||||
UserException() {}
|
||||
|
||||
~UserException() override;
|
||||
|
||||
void DelayedHandlerAction() override;
|
||||
};
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user