Clean up some header files
This commit is contained in:
parent
dfa543c6b2
commit
b015451c38
6 changed files with 472 additions and 439 deletions
22
settings.cpp
22
settings.cpp
|
@ -22,7 +22,7 @@ namespace
|
||||||
std::string settingvalue = t.tail();
|
std::string settingvalue = t.tail();
|
||||||
if(settingname == "")
|
if(settingname == "")
|
||||||
throw std::runtime_error("Setting name required.");
|
throw std::runtime_error("Setting name required.");
|
||||||
setting_set(settingname, settingvalue);
|
setting::set(settingname, settingvalue);
|
||||||
out(win) << "Setting '" << settingname << "' set to '" << settingvalue << "'" << std::endl;
|
out(win) << "Setting '" << settingname << "' set to '" << settingvalue << "'" << std::endl;
|
||||||
}
|
}
|
||||||
std::string get_short_help() throw(std::bad_alloc) { return "set a setting"; }
|
std::string get_short_help() throw(std::bad_alloc) { return "set a setting"; }
|
||||||
|
@ -44,7 +44,7 @@ namespace
|
||||||
std::string settingname = t;
|
std::string settingname = t;
|
||||||
if(settingname == "" || t)
|
if(settingname == "" || t)
|
||||||
throw std::runtime_error("Expected setting name and nothing else");
|
throw std::runtime_error("Expected setting name and nothing else");
|
||||||
setting_blank(settingname);
|
setting::blank(settingname);
|
||||||
out(win) << "Setting '" << settingname << "' unset" << std::endl;
|
out(win) << "Setting '" << settingname << "' unset" << std::endl;
|
||||||
}
|
}
|
||||||
std::string get_short_help() throw(std::bad_alloc) { return "unset a setting"; }
|
std::string get_short_help() throw(std::bad_alloc) { return "unset a setting"; }
|
||||||
|
@ -65,8 +65,8 @@ namespace
|
||||||
std::string settingname = t;
|
std::string settingname = t;
|
||||||
if(settingname == "" || t.tail() != "")
|
if(settingname == "" || t.tail() != "")
|
||||||
throw std::runtime_error("Expected setting name and nothing else");
|
throw std::runtime_error("Expected setting name and nothing else");
|
||||||
if(!setting_isblank(settingname))
|
if(setting::is_set(settingname))
|
||||||
out(win) << "Setting '" << settingname << "' has value '" << setting_get(settingname)
|
out(win) << "Setting '" << settingname << "' has value '" << setting::get(settingname)
|
||||||
<< "'" << std::endl;
|
<< "'" << std::endl;
|
||||||
else
|
else
|
||||||
out(win) << "Setting '" << settingname << "' unset" << std::endl;
|
out(win) << "Setting '" << settingname << "' unset" << std::endl;
|
||||||
|
@ -87,7 +87,7 @@ namespace
|
||||||
{
|
{
|
||||||
if(args != "")
|
if(args != "")
|
||||||
throw std::runtime_error("This command does not take arguments");
|
throw std::runtime_error("This command does not take arguments");
|
||||||
setting_print_all(win);
|
setting::print_all(win);
|
||||||
}
|
}
|
||||||
std::string get_short_help() throw(std::bad_alloc) { return "Show value of all settings"; }
|
std::string get_short_help() throw(std::bad_alloc) { return "Show value of all settings"; }
|
||||||
std::string get_long_help() throw(std::bad_alloc)
|
std::string get_long_help() throw(std::bad_alloc)
|
||||||
|
@ -112,7 +112,7 @@ setting::~setting() throw()
|
||||||
settings->erase(settingname);
|
settings->erase(settingname);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setting_set(const std::string& _setting, const std::string& value) throw(std::bad_alloc, std::runtime_error)
|
void setting::set(const std::string& _setting, const std::string& value) throw(std::bad_alloc, std::runtime_error)
|
||||||
{
|
{
|
||||||
if(!settings || !settings->count(_setting))
|
if(!settings || !settings->count(_setting))
|
||||||
throw std::runtime_error("No such setting '" + _setting + "'");
|
throw std::runtime_error("No such setting '" + _setting + "'");
|
||||||
|
@ -125,7 +125,7 @@ void setting_set(const std::string& _setting, const std::string& value) throw(st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setting_blank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
|
void setting::blank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
|
||||||
{
|
{
|
||||||
if(!settings || !settings->count(_setting))
|
if(!settings || !settings->count(_setting))
|
||||||
throw std::runtime_error("No such setting '" + _setting + "'");
|
throw std::runtime_error("No such setting '" + _setting + "'");
|
||||||
|
@ -138,21 +138,21 @@ void setting_blank(const std::string& _setting) throw(std::bad_alloc, std::runti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string setting_get(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
|
std::string setting::get(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
|
||||||
{
|
{
|
||||||
if(!settings || !settings->count(_setting))
|
if(!settings || !settings->count(_setting))
|
||||||
throw std::runtime_error("No such setting '" + _setting + "'");
|
throw std::runtime_error("No such setting '" + _setting + "'");
|
||||||
return (*settings)[_setting]->get();
|
return (*settings)[_setting]->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setting_isblank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
|
bool setting::is_set(const std::string& _setting) throw(std::bad_alloc, std::runtime_error)
|
||||||
{
|
{
|
||||||
if(!settings || !settings->count(_setting))
|
if(!settings || !settings->count(_setting))
|
||||||
throw std::runtime_error("No such setting '" + _setting + "'");
|
throw std::runtime_error("No such setting '" + _setting + "'");
|
||||||
return !((*settings)[_setting]->is_set());
|
return (*settings)[_setting]->is_set();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setting_print_all(window* win) throw(std::bad_alloc)
|
void setting::print_all(window* win) throw(std::bad_alloc)
|
||||||
{
|
{
|
||||||
if(!settings)
|
if(!settings)
|
||||||
return;
|
return;
|
||||||
|
|
195
settings.hpp
195
settings.hpp
|
@ -6,113 +6,149 @@
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief A setting.
|
* A setting.
|
||||||
*/
|
*/
|
||||||
class setting
|
class setting
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* \brief Create new setting.
|
* Create new setting.
|
||||||
*
|
*
|
||||||
* \param name Name of the setting.
|
* parameter name: Name of the setting.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
setting(const std::string& name) throw(std::bad_alloc);
|
setting(const std::string& name) throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Remove the setting.
|
* Remove the setting.
|
||||||
*/
|
*/
|
||||||
~setting() throw();
|
~setting() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Blank a setting.
|
* Set the setting to special blank state. Not all settings can be blanked.
|
||||||
*
|
*
|
||||||
* Set the setting to special blank state.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*
|
* throws std::runtime_error: Blanking this setting is not allowed (currently).
|
||||||
* \throws std::bad_alloc Not enough memory.
|
|
||||||
* \throws std::runtime_error Blanking this setting is not allowed (currently).
|
|
||||||
*/
|
*/
|
||||||
virtual void blank() throw(std::bad_alloc, std::runtime_error) = 0;
|
virtual void blank() throw(std::bad_alloc, std::runtime_error) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Is this setting set (not blanked)?
|
* Look up setting and try to blank it.
|
||||||
*
|
*
|
||||||
* \return True if setting is not blanked, false if it is blanked.
|
* parameter name: Name of setting to blank.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
* throws std::runtime_error: Blanking this setting is not allowed (currently). Or setting does not exist.
|
||||||
|
*/
|
||||||
|
static void blank(const std::string& name) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this setting set (not blanked)?
|
||||||
|
*
|
||||||
|
* returns: True if setting is not blanked, false if it is blanked.
|
||||||
*/
|
*/
|
||||||
virtual bool is_set() throw() = 0;
|
virtual bool is_set() throw() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Set value of setting.
|
* Look up a setting and see if it is set (not blanked)?
|
||||||
*
|
*
|
||||||
* \param value New value for setting.
|
* parameter name: Name of setting to check.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: True if setting is not blanked, false if it is blanked.
|
||||||
* \throws std::runtime_error Setting the setting to this value is not allowed (currently).
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
* throws std::runtime_error: Setting does not exist.
|
||||||
|
*/
|
||||||
|
static bool is_set(const std::string& name) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value of setting.
|
||||||
|
*
|
||||||
|
* parameter value: New value for setting.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
* throws std::runtime_error: Setting the setting to this value is not allowed (currently).
|
||||||
*/
|
*/
|
||||||
virtual void set(const std::string& value) throw(std::bad_alloc, std::runtime_error) = 0;
|
virtual void set(const std::string& value) throw(std::bad_alloc, std::runtime_error) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Get the value of setting.
|
* Look up setting and set it.
|
||||||
*
|
*
|
||||||
* \return The setting value.
|
* parameter name: Name of the setting.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* parameter value: New value for setting.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
* throws std::runtime_error: Setting the setting to this value is not allowed (currently). Or setting does not exist.
|
||||||
|
*/
|
||||||
|
static void set(const std::string& name, const std::string& value) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of setting.
|
||||||
|
*
|
||||||
|
* returns: The setting value.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
virtual std::string get() throw(std::bad_alloc) = 0;
|
virtual std::string get() throw(std::bad_alloc) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up setting an get value of it.
|
||||||
|
*
|
||||||
|
* returns: The setting value.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
* throws std::runtime_error: Setting does not exist.
|
||||||
|
*/
|
||||||
|
static std::string get(const std::string& name) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print all settings and values.
|
||||||
|
*
|
||||||
|
* parameter win: The graphics system handle.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
*/
|
||||||
|
static void print_all(window* win) throw(std::bad_alloc);
|
||||||
protected:
|
protected:
|
||||||
std::string settingname;
|
std::string settingname;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Look up setting and call set() on it.
|
* Setting having numeric value.
|
||||||
*
|
|
||||||
* \param _setting The setting to set.
|
|
||||||
* \param value The value to set it into.
|
|
||||||
* \throws std::bad_alloc Not enough memory.
|
|
||||||
* \throws std::runtime_error Setting the setting to this value is not allowed (currently), or no such setting.
|
|
||||||
*/
|
*/
|
||||||
void setting_set(const std::string& _setting, const std::string& value) throw(std::bad_alloc, std::runtime_error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Look up setting and call blank() on it.
|
|
||||||
*
|
|
||||||
* \param _setting The setting to blank.
|
|
||||||
* \throws std::bad_alloc Not enough memory.
|
|
||||||
* \throws std::runtime_error Blanking this setting is not allowed (currently), or no such setting.
|
|
||||||
*/
|
|
||||||
void setting_blank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Look up setting and call get() on it.
|
|
||||||
*
|
|
||||||
* \param _setting The setting to get.
|
|
||||||
* \return The setting value.
|
|
||||||
* \throws std::bad_alloc Not enough memory.
|
|
||||||
* \throws std::runtime_error No such setting.
|
|
||||||
*/
|
|
||||||
std::string setting_get(const std::string& _setting) throw(std::bad_alloc, std::runtime_error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Look up setting and call is_set() on it.
|
|
||||||
*
|
|
||||||
* \param _setting The setting to get.
|
|
||||||
* \return Flase if setting is not blanked, true if it is blanked (note: this is reverse of is_set().
|
|
||||||
* \throws std::bad_alloc Not enough memory.
|
|
||||||
* \throws std::runtime_error No such setting.
|
|
||||||
*/
|
|
||||||
bool setting_isblank(const std::string& _setting) throw(std::bad_alloc, std::runtime_error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Print all settings and values.
|
|
||||||
*/
|
|
||||||
void setting_print_all(window* win) throw(std::bad_alloc);
|
|
||||||
|
|
||||||
class numeric_setting : public setting
|
class numeric_setting : public setting
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Create a new numeric setting.
|
||||||
|
*
|
||||||
|
* parameter sname: Name of the setting.
|
||||||
|
* parameter minv: Minimum value for the setting.
|
||||||
|
* parameter maxv: Maximum value for the setting.
|
||||||
|
* parameter dflt: Default (initial) value for the setting.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
*/
|
||||||
numeric_setting(const std::string& sname, int32_t minv, int32_t maxv, int32_t dflt) throw(std::bad_alloc);
|
numeric_setting(const std::string& sname, int32_t minv, int32_t maxv, int32_t dflt) throw(std::bad_alloc);
|
||||||
|
/**
|
||||||
|
* Raises std::runtime_error as these settings can't be blanked.
|
||||||
|
*/
|
||||||
void blank() throw(std::bad_alloc, std::runtime_error);
|
void blank() throw(std::bad_alloc, std::runtime_error);
|
||||||
|
/**
|
||||||
|
* Returns true (these settings are always set).
|
||||||
|
*/
|
||||||
bool is_set() throw();
|
bool is_set() throw();
|
||||||
|
/**
|
||||||
|
* Set the value of setting. Accepts only numeric values.
|
||||||
|
*
|
||||||
|
* parameter value: New value.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
* throws std::runtime_error: Invalid value.
|
||||||
|
*/
|
||||||
void set(const std::string& value) throw(std::bad_alloc, std::runtime_error);
|
void set(const std::string& value) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
/**
|
||||||
|
* Gets the value of the setting.
|
||||||
|
*
|
||||||
|
* returns: Value of setting as string.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
*/
|
||||||
std::string get() throw(std::bad_alloc);
|
std::string get() throw(std::bad_alloc);
|
||||||
|
/**
|
||||||
|
* Get the value of setting as numeric.
|
||||||
|
*
|
||||||
|
* returns: Value of the setting as numeric.
|
||||||
|
*/
|
||||||
operator int32_t() throw();
|
operator int32_t() throw();
|
||||||
private:
|
private:
|
||||||
int32_t value;
|
int32_t value;
|
||||||
|
@ -120,14 +156,51 @@ private:
|
||||||
int32_t maximum;
|
int32_t maximum;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting having boolean value.
|
||||||
|
*/
|
||||||
class boolean_setting : public setting
|
class boolean_setting : public setting
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Create a new boolean setting.
|
||||||
|
*
|
||||||
|
* parameter sname: Name of the setting.
|
||||||
|
* parameter dflt: Default (initial) value for the setting.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
*/
|
||||||
boolean_setting(const std::string& sname, bool dflt) throw(std::bad_alloc);
|
boolean_setting(const std::string& sname, bool dflt) throw(std::bad_alloc);
|
||||||
|
/**
|
||||||
|
* Raises std::runtime_error as these settings can't be blanked.
|
||||||
|
*/
|
||||||
void blank() throw(std::bad_alloc, std::runtime_error);
|
void blank() throw(std::bad_alloc, std::runtime_error);
|
||||||
|
/**
|
||||||
|
* Returns true (these settings are always set).
|
||||||
|
*/
|
||||||
bool is_set() throw();
|
bool is_set() throw();
|
||||||
|
/**
|
||||||
|
* Set the value of setting.
|
||||||
|
*
|
||||||
|
* The following values are accepted as true: true, yes, on, 1, enable and enabled.
|
||||||
|
* The following values are accepted as false: false, no, off, 0, disable and disabled.
|
||||||
|
*
|
||||||
|
* parameter value: New value.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
* throws std::runtime_error: Invalid value.
|
||||||
|
*/
|
||||||
void set(const std::string& value) throw(std::bad_alloc, std::runtime_error);
|
void set(const std::string& value) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
/**
|
||||||
|
* Gets the value of the setting.
|
||||||
|
*
|
||||||
|
* returns: Value of setting as string.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
|
*/
|
||||||
std::string get() throw(std::bad_alloc);
|
std::string get() throw(std::bad_alloc);
|
||||||
|
/**
|
||||||
|
* Get the value of setting as boolean.
|
||||||
|
*
|
||||||
|
* returns: Value of the setting as boolean.
|
||||||
|
*/
|
||||||
operator bool() throw();
|
operator bool() throw();
|
||||||
private:
|
private:
|
||||||
bool value;
|
bool value;
|
||||||
|
|
110
videodumper.hpp
110
videodumper.hpp
|
@ -15,69 +15,69 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Class of thread.
|
* Class of thread.
|
||||||
*/
|
*/
|
||||||
typedef std::thread thread_class;
|
typedef std::thread thread_class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Class of condition variables.
|
* Class of condition variables.
|
||||||
*/
|
*/
|
||||||
typedef std::condition_variable cv_class;
|
typedef std::condition_variable cv_class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Class of mutexes.
|
* Class of mutexes.
|
||||||
*/
|
*/
|
||||||
typedef std::mutex mutex_class;
|
typedef std::mutex mutex_class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Class of unique mutexes (for condition variable waiting).
|
* Class of unique mutexes (for condition variable waiting).
|
||||||
*/
|
*/
|
||||||
typedef std::unique_lock<std::mutex> umutex_class;
|
typedef std::unique_lock<std::mutex> umutex_class;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Class of thread.
|
* Class of thread.
|
||||||
*/
|
*/
|
||||||
struct thread_class
|
struct thread_class
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* \brief Does nothing.
|
* Does nothing.
|
||||||
*/
|
*/
|
||||||
template<typename T, typename... args>
|
template<typename T, typename... args>
|
||||||
thread_class(T obj, args... a) {}
|
thread_class(T obj, args... a) {}
|
||||||
/**
|
/**
|
||||||
* \brief Does nothing.
|
* Does nothing.
|
||||||
*/
|
*/
|
||||||
void join() {}
|
void join() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Class of mutexes.
|
* Class of mutexes.
|
||||||
*/
|
*/
|
||||||
typedef struct mutex_class
|
typedef struct mutex_class
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* \brief Does nothing.
|
* Does nothing.
|
||||||
*/
|
*/
|
||||||
void lock() {}
|
void lock() {}
|
||||||
/**
|
/**
|
||||||
* \brief Does nothing.
|
* Does nothing.
|
||||||
*/
|
*/
|
||||||
void unlock() {}
|
void unlock() {}
|
||||||
} umutex_class;
|
} umutex_class;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Class of condition variables.
|
* Class of condition variables.
|
||||||
*/
|
*/
|
||||||
struct cv_class
|
struct cv_class
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* \brief Does nothing.
|
* Does nothing.
|
||||||
*/
|
*/
|
||||||
void wait(umutex_class& m) {}
|
void wait(umutex_class& m) {}
|
||||||
/**
|
/**
|
||||||
* \brief Does nothing.
|
* Does nothing.
|
||||||
*/
|
*/
|
||||||
void notify_all() {}
|
void notify_all() {}
|
||||||
};
|
};
|
||||||
|
@ -85,91 +85,91 @@ struct cv_class
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Size of audio buffer (enough to buffer 3 frames).
|
* Size of audio buffer (enough to buffer 3 frames).
|
||||||
*/
|
*/
|
||||||
#define AVIDUMPER_AUDIO_BUFFER 4096
|
#define AVIDUMPER_AUDIO_BUFFER 4096
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Information about frame in AVI.
|
* Information about frame in AVI.
|
||||||
*/
|
*/
|
||||||
struct avi_frame
|
struct avi_frame
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* \brief Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* \param _flags Flags for frame.
|
* parameter _flags: Flags for frame.
|
||||||
* \param _type AVI type for frame (big-endian!).
|
* parameter _type: AVI type for frame (big-endian!).
|
||||||
* \param _offset Offset of frame from start of MOVI.
|
* parameter _offset: Offset of frame from start of MOVI.
|
||||||
* \param _size Size of frame data.
|
* parameter _size: Size of frame data.
|
||||||
*/
|
*/
|
||||||
avi_frame(uint32_t _flags, uint32_t _type, uint32_t _offset, uint32_t _size);
|
avi_frame(uint32_t _flags, uint32_t _type, uint32_t _offset, uint32_t _size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Write the index entry for frame.
|
* Write the index entry for frame.
|
||||||
*
|
*
|
||||||
* \param buf Buffer to write to.
|
* parameter buf: Buffer to write to.
|
||||||
*/
|
*/
|
||||||
void write(uint8_t* buf);
|
void write(uint8_t* buf);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Flags.
|
* Flags.
|
||||||
*/
|
*/
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Chunk type.
|
* Chunk type.
|
||||||
*/
|
*/
|
||||||
uint32_t type;
|
uint32_t type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Chunk offset.
|
* Chunk offset.
|
||||||
*/
|
*/
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Chunk size.
|
* Chunk size.
|
||||||
*/
|
*/
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Parameters for AVI dumping.
|
* Parameters for AVI dumping.
|
||||||
*/
|
*/
|
||||||
struct avi_info
|
struct avi_info
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* \brief Zlib compression level (0-9).
|
* Zlib compression level (0-9).
|
||||||
*/
|
*/
|
||||||
unsigned compression_level;
|
unsigned compression_level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Audio drop counter increments by this much every frame.
|
* Audio drop counter increments by this much every frame.
|
||||||
*/
|
*/
|
||||||
uint64_t audio_drop_counter_inc;
|
uint64_t audio_drop_counter_inc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Audio drop counter modulus (when audio drop counter warps around, sample is dropped).
|
* Audio drop counter modulus (when audio drop counter warps around, sample is dropped).
|
||||||
*/
|
*/
|
||||||
uint64_t audio_drop_counter_max;
|
uint64_t audio_drop_counter_max;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Audio sampling rate to write to AVI.
|
* Audio sampling rate to write to AVI.
|
||||||
*/
|
*/
|
||||||
uint32_t audio_sampling_rate;
|
uint32_t audio_sampling_rate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Native audio sampling rate to write to auxillary SOX file.
|
* Native audio sampling rate to write to auxillary SOX file.
|
||||||
*/
|
*/
|
||||||
double audio_native_sampling_rate;
|
double audio_native_sampling_rate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Interval of keyframes (WARNING: >1 gives non-keyframes which AVISource() doesn't like).
|
* Interval of keyframes (WARNING: >1 gives non-keyframes which AVISource() doesn't like).
|
||||||
*/
|
*/
|
||||||
uint32_t keyframe_interval;
|
uint32_t keyframe_interval;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief The actual AVI dumper.
|
* The actual AVI dumper.
|
||||||
*/
|
*/
|
||||||
class avidumper
|
class avidumper
|
||||||
{
|
{
|
||||||
|
@ -178,55 +178,53 @@ public:
|
||||||
~avidumper() throw();
|
~avidumper() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Wait for encode thread to become idle.
|
|
||||||
*
|
|
||||||
* Waits for the encode thread. Not needed: Operations that need to synchronize synchronize themselves.
|
* Waits for the encode thread. Not needed: Operations that need to synchronize synchronize themselves.
|
||||||
*/
|
*/
|
||||||
void wait_idle() throw();
|
void wait_idle() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Dump a frame (new segment starts if needed).
|
* Dump a frame (new segment starts if needed). Pixel byte order is BGRx.
|
||||||
*
|
*
|
||||||
* \param data The frame data.
|
* parameter data: The frame data.
|
||||||
* \param width Width of frame.
|
* parameter width: Width of frame.
|
||||||
* \param height Height of frame.
|
* parameter height: Height of frame.
|
||||||
* \param fps_n Numerator of fps value.
|
* parameter fps_n: Numerator of fps value.
|
||||||
* \param fps_d Denomerator of fps value.
|
* parameter fps_d: Denomerator of fps value.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Error dumping frame.
|
* throws std::runtime_error: Error dumping frame.
|
||||||
*/
|
*/
|
||||||
void on_frame(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
|
void on_frame(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
|
||||||
throw(std::bad_alloc, std::runtime_error);
|
throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Dump an audio sample
|
* Dump an audio sample
|
||||||
*
|
*
|
||||||
* \param left Signed sample for left channel (-32768 - 327678).
|
* parameter left: Signed sample for left channel (-32768 - 327678).
|
||||||
* \param right Signed sample for right channel (-32768 - 327678).
|
* parameter right: Signed sample for right channel (-32768 - 327678).
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Error dumping sample.
|
* throws std::runtime_error: Error dumping sample.
|
||||||
*/
|
*/
|
||||||
void on_sample(short left, short right) throw(std::bad_alloc, std::runtime_error);
|
void on_sample(short left, short right) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Notify end of dump.
|
* Notify end of dump.
|
||||||
*
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Error closing dump.
|
* throws std::runtime_error: Error closing dump.
|
||||||
*/
|
*/
|
||||||
void on_end() throw(std::bad_alloc, std::runtime_error);
|
void on_end() throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Act as encode thread.
|
|
||||||
*
|
|
||||||
* Causes current thread to become encode thread. Do not call this, the code internally uses it.
|
* Causes current thread to become encode thread. Do not call this, the code internally uses it.
|
||||||
*
|
*
|
||||||
* \return Return status for the thread.
|
* returns: Return status for the thread.
|
||||||
*/
|
*/
|
||||||
int encode_thread();
|
int encode_thread();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Set capture errored flag.
|
* Set capture errored flag.
|
||||||
|
*
|
||||||
|
* parameter err: The error message.
|
||||||
*/
|
*/
|
||||||
void set_capture_error(const char* err) throw();
|
void set_capture_error(const char* err) throw();
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -11,54 +11,54 @@
|
||||||
#include "videodumper.hpp"
|
#include "videodumper.hpp"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief End dumping.
|
|
||||||
*
|
|
||||||
* Forcibly ends dumping. Mainly useful for quitting.
|
* Forcibly ends dumping. Mainly useful for quitting.
|
||||||
*
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Failed to end dump.
|
* throws std::runtime_error: Failed to end dump.
|
||||||
*/
|
*/
|
||||||
void end_vid_dump() throw(std::bad_alloc, std::runtime_error);
|
void end_vid_dump() throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Dump a frame.
|
|
||||||
*
|
|
||||||
* Dumps a frame. Does nothing if dumping is not in progress.
|
* Dumps a frame. Does nothing if dumping is not in progress.
|
||||||
*
|
*
|
||||||
* \param ls Screen to dump.
|
* parameter ls: Screen to dump.
|
||||||
* \param rq Render queue to run.
|
* parameter rq: Render queue to run.
|
||||||
* \param left Left border.
|
* parameter left: Left border.
|
||||||
* \param right Right border.
|
* parameter right: Right border.
|
||||||
* \param top Top border.
|
* parameter top: Top border.
|
||||||
* \param bottom Bottom border.
|
* parameter bottom: Bottom border.
|
||||||
* \param region True if PAL, false if NTSC.
|
* parameter region: True if PAL, false if NTSC.
|
||||||
* \param win Graphics system handle.
|
* parameter win: Graphics system handle.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Failed to dump frame.
|
* throws std::runtime_error: Failed to dump frame.
|
||||||
*/
|
*/
|
||||||
void dump_frame(lcscreen& ls, render_queue* rq, uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
|
void dump_frame(lcscreen& ls, render_queue* rq, uint32_t left, uint32_t right, uint32_t top, uint32_t bottom,
|
||||||
bool region, window* win) throw(std::bad_alloc, std::runtime_error);
|
bool region, window* win) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Dump sample of audio.
|
|
||||||
*
|
|
||||||
* Dumps one sample of audio. Does nothing if dumping is not in progress.
|
* Dumps one sample of audio. Does nothing if dumping is not in progress.
|
||||||
*
|
*
|
||||||
* \param l_sample Left channel sample (-32768-32767)
|
* parameter l_sample Left channel sample (-32768-32767)
|
||||||
* \param r_sample Right channel sample (-32768-32767)
|
* parameter r_sample Right channel sample (-32768-32767)
|
||||||
* \param win Graphics System handle.
|
* parameter win Graphics System handle.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Failed to dump sample.
|
* throws std::runtime_error: Failed to dump sample.
|
||||||
*/
|
*/
|
||||||
void dump_audio_sample(int16_t l_sample, int16_t r_sample, window* win) throw(std::bad_alloc, std::runtime_error);
|
void dump_audio_sample(int16_t l_sample, int16_t r_sample, window* win) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Is the dump in progress?
|
* Is the dump in progress?
|
||||||
|
*
|
||||||
|
* returns: True if dump is in progress, false if not.
|
||||||
*/
|
*/
|
||||||
bool dump_in_progress() throw();
|
bool dump_in_progress() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Fill rendering shifts.
|
* Fill rendering shifts.
|
||||||
|
*
|
||||||
|
* parameter r: Shift for red component is written here.
|
||||||
|
* parameter g: Shift for green component is written here.
|
||||||
|
* parameter b: Shift for blue component is written here.
|
||||||
*/
|
*/
|
||||||
void video_fill_shifts(uint32_t& r, uint32_t& g, uint32_t& b);
|
void video_fill_shifts(uint32_t& r, uint32_t& g, uint32_t& b);
|
||||||
|
|
||||||
|
|
137
window.hpp
137
window.hpp
|
@ -17,155 +17,159 @@ class window_internal;
|
||||||
class window;
|
class window;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Handle to the graphics system.
|
|
||||||
*
|
|
||||||
* This is a handle to graphics system. Note that creating multiple contexts produces undefined results.
|
* This is a handle to graphics system. Note that creating multiple contexts produces undefined results.
|
||||||
*/
|
*/
|
||||||
class window
|
class window
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Create a graphics system handle, initializing the graphics system.
|
||||||
|
*/
|
||||||
window();
|
window();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a graphics system handle, shutting down the graphics system.
|
||||||
|
*/
|
||||||
~window();
|
~window();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Add messages to message queue.
|
|
||||||
*
|
|
||||||
* Adds a messages to mesage queue to be shown.
|
* Adds a messages to mesage queue to be shown.
|
||||||
*
|
*
|
||||||
* \param msg The messages to add (split by '\n').
|
* parameter msg: The messages to add (split by '\n').
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
void message(const std::string& msg) throw(std::bad_alloc);
|
void message(const std::string& msg) throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Get output stream printing into message queue.
|
* Get output stream printing into message queue.
|
||||||
*
|
*
|
||||||
* \return The output stream.
|
* Note that lines printed there should be terminated by '\n'.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
*
|
||||||
|
* returns: The output stream.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
std::ostream& out() throw(std::bad_alloc);
|
std::ostream& out() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Display a modal message.
|
|
||||||
*
|
|
||||||
* Displays a modal message, not returning until the message is acknowledged. Keybindings are not available, but
|
* Displays a modal message, not returning until the message is acknowledged. Keybindings are not available, but
|
||||||
* should quit be generated somehow, modal message will be closed and command callback triggered.
|
* should quit be generated somehow, modal message will be closed and command callback triggered.
|
||||||
*
|
*
|
||||||
* \param msg The message to show.
|
* parameter msg: The message to show.
|
||||||
* \param confirm If true, ask for Ok/cancel type input.
|
* parameter confirm: If true, ask for Ok/cancel type input.
|
||||||
* \return If confirm is true, true if ok was chosen, false if cancel was chosen. Otherwise always false.
|
* returns: If confirm is true, true if ok was chosen, false if cancel was chosen. Otherwise always false.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
bool modal_message(const std::string& msg, bool confirm = false) throw(std::bad_alloc);
|
bool modal_message(const std::string& msg, bool confirm = false) throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Signal that the emulator state is too screwed up to continue.
|
|
||||||
*
|
|
||||||
* Displays fatal error message, quitting after the user acks it.
|
* Displays fatal error message, quitting after the user acks it.
|
||||||
*/
|
*/
|
||||||
void fatal_error() throw();
|
void fatal_error() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Bind a key.
|
* Bind a key.
|
||||||
*
|
*
|
||||||
* \param mod Set of modifiers.
|
* parameter mod: Set of modifiers.
|
||||||
* \param modmask Modifier mask (set of modifiers).
|
* parameter modmask: Modifier mask (set of modifiers).
|
||||||
* \param keyname Name of key or pseudo-key.
|
* parameter keyname: Name of key or pseudo-key.
|
||||||
* \param command Command to run.
|
* parameter command: Command to run.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Invalid key or modifier name, or conflict.
|
* throws std::runtime_error: Invalid key or modifier name, or conflict.
|
||||||
*/
|
*/
|
||||||
void bind(std::string mod, std::string modmask, std::string keyname, std::string command)
|
void bind(std::string mod, std::string modmask, std::string keyname, std::string command)
|
||||||
throw(std::bad_alloc, std::runtime_error);
|
throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Unbind a key.
|
* Unbind a key.
|
||||||
*
|
*
|
||||||
* \param mod Set of modifiers.
|
* parameter mod: Set of modifiers.
|
||||||
* \param modmask Modifier mask (set of modifiers).
|
* parameter modmask: Modifier mask (set of modifiers).
|
||||||
* \param keyname Name of key or pseudo-key.
|
* parameter keyname: Name of key or pseudo-key.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Invalid key or modifier name, or not bound.
|
* throws std::runtime_error: Invalid key or modifier name, or not bound.
|
||||||
*/
|
*/
|
||||||
void unbind(std::string mod, std::string modmask, std::string keyname) throw(std::bad_alloc,
|
void unbind(std::string mod, std::string modmask, std::string keyname) throw(std::bad_alloc,
|
||||||
std::runtime_error);
|
std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Dump bindings into this window.
|
* Dump bindings into this window.
|
||||||
*
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
//Dump bindings.
|
|
||||||
void dumpbindings() throw(std::bad_alloc);
|
void dumpbindings() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Process inputs, calling command handler if needed.
|
|
||||||
*
|
|
||||||
* Processes inputs. If in non-modal mode (normal mode without pause), this returns quickly. Otherwise it waits
|
* Processes inputs. If in non-modal mode (normal mode without pause), this returns quickly. Otherwise it waits
|
||||||
* for modal mode to exit.
|
* for modal mode to exit.
|
||||||
*
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
void poll_inputs() throw(std::bad_alloc);
|
void poll_inputs() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Get emulator status area
|
* Get emulator status area
|
||||||
*
|
*
|
||||||
* \return Emulator status area.
|
* returns: Emulator status area.
|
||||||
*/
|
*/
|
||||||
std::map<std::string, std::string>& get_emustatus() throw();
|
std::map<std::string, std::string>& get_emustatus() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Notify that the screen has been updated.
|
* Notify that the screen has been updated.
|
||||||
*
|
*
|
||||||
* \param full Do full refresh.
|
* parameter full: Do full refresh if true.
|
||||||
*/
|
*/
|
||||||
void notify_screen_update(bool full = false) throw();
|
void notify_screen_update(bool full = false) throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Set the screen to use as main surface.
|
* Set the screen to use as main surface.
|
||||||
*
|
*
|
||||||
* \param scr The screen to use.
|
* parameter scr: The screen to use.
|
||||||
*/
|
*/
|
||||||
void set_main_surface(screen& scr) throw();
|
void set_main_surface(screen& scr) throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Enable/Disable pause mode.
|
* Enable/Disable pause mode.
|
||||||
*
|
*
|
||||||
* \param enable Enable pause if true, disable otherwise.
|
* parameter enable: Enable pause if true, disable otherwise.
|
||||||
*/
|
*/
|
||||||
void paused(bool enable) throw();
|
void paused(bool enable) throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Wait specified number of milliseconds (polling for input).
|
* Wait specified number of milliseconds (polling for input).
|
||||||
*
|
*
|
||||||
* \param msec Number of ms to wait.
|
* parameter msec: Number of ms to wait.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
void wait_msec(uint64_t msec) throw(std::bad_alloc);
|
void wait_msec(uint64_t msec) throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Enable or disable sound.
|
* Cancel pending wait_msec, making it return now.
|
||||||
*
|
|
||||||
* \param enable Enable sounds if true, otherwise disable sounds.
|
|
||||||
*/
|
|
||||||
void sound_enable(bool enable) throw();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Input audio sample (at 32040.5Hz).
|
|
||||||
*
|
|
||||||
* \param left Left sample.
|
|
||||||
* \param right Right sample.
|
|
||||||
*/
|
|
||||||
void play_audio_sample(uint16_t left, uint16_t right) throw();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \brief Cancel pending wait, making it return now.
|
|
||||||
*/
|
*/
|
||||||
void cancel_wait() throw();
|
void cancel_wait() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Set window compensation parameters.
|
* Enable or disable sound.
|
||||||
|
*
|
||||||
|
* parameter enable: Enable sounds if true, otherwise disable sounds.
|
||||||
|
*/
|
||||||
|
void sound_enable(bool enable) throw();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input audio sample (at 32040.5Hz).
|
||||||
|
*
|
||||||
|
* parameter left: Left sample.
|
||||||
|
* parameter right: Right sample.
|
||||||
|
*/
|
||||||
|
void play_audio_sample(uint16_t left, uint16_t right) throw();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set window main screen compensation parameters. This is used for mouse click reporting.
|
||||||
|
*
|
||||||
|
* parameter xoffset: X coordinate of origin.
|
||||||
|
* parameter yoffset: Y coordinate of origin.
|
||||||
|
* parameter hscl: Horizontal scaling factor.
|
||||||
|
* parameter vscl: Vertical scaling factor.
|
||||||
*/
|
*/
|
||||||
void set_window_compensation(uint32_t xoffset, uint32_t yoffset, uint32_t hscl, uint32_t vscl);
|
void set_window_compensation(uint32_t xoffset, uint32_t yoffset, uint32_t hscl, uint32_t vscl);
|
||||||
private:
|
private:
|
||||||
|
@ -175,11 +179,10 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Get number of msec since some undetermined epoch.
|
* Get number of msec since some undetermined epoch.
|
||||||
*
|
*
|
||||||
* \return The number of milliseconds.
|
* returns: The number of milliseconds.
|
||||||
*/
|
*/
|
||||||
uint64_t get_ticks_msec() throw();
|
uint64_t get_ticks_msec() throw();
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
241
zip.hpp
241
zip.hpp
|
@ -10,16 +10,12 @@
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Read files from ZIP archive
|
|
||||||
*
|
|
||||||
* This class opens ZIP archive and offers methods to read members off it.
|
* This class opens ZIP archive and offers methods to read members off it.
|
||||||
*/
|
*/
|
||||||
class zip_reader
|
class zip_reader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* \brief ZIP file iterator
|
|
||||||
*
|
|
||||||
* This iterator iterates members of ZIP archive.
|
* This iterator iterates members of ZIP archive.
|
||||||
*/
|
*/
|
||||||
template<typename T, typename V>
|
template<typename T, typename V>
|
||||||
|
@ -27,34 +23,20 @@ public:
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* \brief C++ iterators stuff.
|
* C++ iterators stuff.
|
||||||
*/
|
*/
|
||||||
typedef std::bidirectional_iterator_tag iterator_category;
|
typedef std::bidirectional_iterator_tag iterator_category;
|
||||||
/**
|
|
||||||
* \brief C++ iterators stuff.
|
|
||||||
*/
|
|
||||||
typedef V value_type;
|
typedef V value_type;
|
||||||
/**
|
|
||||||
* \brief C++ iterators stuff.
|
|
||||||
*/
|
|
||||||
typedef int difference_type;
|
typedef int difference_type;
|
||||||
/**
|
|
||||||
* \brief C++ iterators stuff.
|
|
||||||
*/
|
|
||||||
typedef const V& reference;
|
typedef const V& reference;
|
||||||
/**
|
|
||||||
* \brief C++ iterators stuff.
|
|
||||||
*/
|
|
||||||
typedef const V* pointer;
|
typedef const V* pointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Construct new iterator with specified names
|
|
||||||
*
|
|
||||||
* This constructs new iteration sequence. Only the first component (keys) are taken into
|
* This constructs new iteration sequence. Only the first component (keys) are taken into
|
||||||
* account, the second component (values) are ignored.
|
* account, the second component (values) are ignored.
|
||||||
*
|
*
|
||||||
* \param _itr The underlying map iterator.
|
* parameter _itr: The underlying map iterator.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
iterator_class(T _itr) throw(std::bad_alloc)
|
iterator_class(T _itr) throw(std::bad_alloc)
|
||||||
: itr(_itr)
|
: itr(_itr)
|
||||||
|
@ -62,9 +44,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Get name of current member.
|
* Get name of current member.
|
||||||
* \return Name of member.
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: Name of member.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
reference operator*() throw(std::bad_alloc)
|
reference operator*() throw(std::bad_alloc)
|
||||||
{
|
{
|
||||||
|
@ -72,9 +55,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Get name of current member.
|
* Get name of current member.
|
||||||
* \return Name of member.
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: Name of member.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
pointer operator->() throw(std::bad_alloc)
|
pointer operator->() throw(std::bad_alloc)
|
||||||
{
|
{
|
||||||
|
@ -82,9 +66,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Are these two iterators the same?
|
* Are these two iterators the same?
|
||||||
* \param i The another iterator
|
*
|
||||||
* \return True if iterators are the same, false otherwise.
|
* parameter i: The another iterator
|
||||||
|
* returns: True if iterators are the same, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool operator==(const iterator_class<T, V>& i) const throw()
|
bool operator==(const iterator_class<T, V>& i) const throw()
|
||||||
{
|
{
|
||||||
|
@ -92,9 +77,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Are these two iterators diffrent?
|
* Are these two iterators diffrent?
|
||||||
* \param i The another iterator
|
*
|
||||||
* \return True if iterators are diffrent, false otherwise.
|
* paramer i: The another iterator
|
||||||
|
* returns: True if iterators are diffrent, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool operator!=(const iterator_class<T, V>& i) const throw()
|
bool operator!=(const iterator_class<T, V>& i) const throw()
|
||||||
{
|
{
|
||||||
|
@ -102,9 +88,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Advance iterator one step.
|
* Advance iterator one step.
|
||||||
* \return The old value of iterator.
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: The old value of iterator.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
const iterator_class<T, V> operator++(int) throw(std::bad_alloc)
|
const iterator_class<T, V> operator++(int) throw(std::bad_alloc)
|
||||||
{
|
{
|
||||||
|
@ -114,9 +101,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Regress iterator one step.
|
* Regress iterator one step.
|
||||||
* \return The old value of iterator.
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: The old value of iterator.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
const iterator_class<T, V> operator--(int) throw(std::bad_alloc)
|
const iterator_class<T, V> operator--(int) throw(std::bad_alloc)
|
||||||
{
|
{
|
||||||
|
@ -126,8 +114,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Advance iterator one step.
|
* Advance iterator one step.
|
||||||
* \return Reference to this iterator.
|
*
|
||||||
|
* returns: Reference to this iterator.
|
||||||
*/
|
*/
|
||||||
iterator_class<T, V>& operator++() throw()
|
iterator_class<T, V>& operator++() throw()
|
||||||
{
|
{
|
||||||
|
@ -136,8 +125,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Regress iterator one step.
|
* Regress iterator one step.
|
||||||
* \return Reference to this iterator.
|
*
|
||||||
|
* returns: Reference to this iterator.
|
||||||
*/
|
*/
|
||||||
iterator_class<T, V>& operator--() throw()
|
iterator_class<T, V>& operator--() throw()
|
||||||
{
|
{
|
||||||
|
@ -149,103 +139,94 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief ZIP file forward iterator
|
|
||||||
*
|
|
||||||
* This iterator iterates members of ZIP archive in forward order.
|
* This iterator iterates members of ZIP archive in forward order.
|
||||||
*/
|
*/
|
||||||
typedef iterator_class<std::map<std::string, unsigned long long>::iterator, std::string> iterator;
|
typedef iterator_class<std::map<std::string, unsigned long long>::iterator, std::string> iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief ZIP file reverse iterator
|
|
||||||
*
|
|
||||||
* This iterator iterates members of ZIP archive in reverse order
|
* This iterator iterates members of ZIP archive in reverse order
|
||||||
*/
|
*/
|
||||||
typedef iterator_class<std::map<std::string, unsigned long long>::reverse_iterator, std::string>
|
typedef iterator_class<std::map<std::string, unsigned long long>::reverse_iterator, std::string>
|
||||||
riterator;
|
riterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Open a ZIP archive.
|
* Opens specified ZIP archive for reading.
|
||||||
*
|
*
|
||||||
* Opens specified ZIP archive.
|
* parameter zipfile: The name of ZIP file to open.
|
||||||
* \param zipfile The ZIP file to open.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::runtime_error: Can't open the ZIP file.
|
||||||
* \throws std::runtime_error Can't open the ZIP file.
|
|
||||||
*/
|
*/
|
||||||
zip_reader(const std::string& zipfile) throw(std::bad_alloc, std::runtime_error);
|
zip_reader(const std::string& zipfile) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Destructor
|
|
||||||
*
|
|
||||||
* Destroy the ZIP reader. Opened input streams continue to be valid.
|
* Destroy the ZIP reader. Opened input streams continue to be valid.
|
||||||
*/
|
*/
|
||||||
~zip_reader() throw();
|
~zip_reader() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Find the name of first member.
|
|
||||||
*
|
|
||||||
* Gives the name of the first member, or "" if empty archive.
|
* Gives the name of the first member, or "" if empty archive.
|
||||||
*
|
*
|
||||||
* \return The member name
|
* returns: The member name
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
std::string find_first() throw(std::bad_alloc);
|
std::string find_first() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Find the next member.
|
|
||||||
*
|
|
||||||
* Gives the name of the next member after specified, or "" if that member is the last.
|
* Gives the name of the next member after specified, or "" if that member is the last.
|
||||||
*
|
*
|
||||||
* \param name The name to start the search from.
|
* parameter name: The name to start the search from.
|
||||||
* \return The member name
|
* returns: The member name
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
std::string find_next(const std::string& name) throw(std::bad_alloc);
|
std::string find_next(const std::string& name) throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Starting iterator
|
* Starting iterator
|
||||||
* \return The iterator pointing to first name.
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: The iterator pointing to first name.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
iterator begin() throw(std::bad_alloc);
|
iterator begin() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Ending iterator
|
* Ending iterator (one past the end).
|
||||||
* \return The iterator pointing to one past the last name.
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: The iterator pointing to one past the last name.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
iterator end() throw(std::bad_alloc);
|
iterator end() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Starting reverse iterator
|
* Starting reverse iterator
|
||||||
* \return The iterator pointing to last name and acting in reverse.
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* returns: The iterator pointing to last name and acting in reverse.
|
||||||
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
riterator rbegin() throw(std::bad_alloc);
|
riterator rbegin() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Ending reverse iterator
|
* Ending reverse iterator (one past the start).
|
||||||
* \return The iterator pointing to one before the first name and acting in reverse.
|
* returrns: The iterator pointing to one before the first name and acting in reverse.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
*/
|
*/
|
||||||
riterator rend() throw(std::bad_alloc);
|
riterator rend() throw(std::bad_alloc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Does the member exist?
|
* Check if member with specified name exists.
|
||||||
* \param name The name of the member.
|
*
|
||||||
* \return True if specified member exists, false otherwise.
|
* parameter name: The name of the member to check
|
||||||
|
* returns: True if specified member exists, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool has_member(const std::string& name) throw();
|
bool has_member(const std::string& name) throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Open member
|
* Opens specified member. The resulting stream is not seekable, allocated using new and continues to be valid
|
||||||
|
* after ZIP reader has been destroyed.
|
||||||
*
|
*
|
||||||
* Opens specified member. The stream is not seekable, allocated using new and continues to be valid after
|
* parameter name: The name of member to open.
|
||||||
* ZIP reader has been destroyed.
|
* returns: The stream corresponding to member.
|
||||||
*
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \param name The name of member to open.
|
* throws std::runtime_error: The specified member does not exist
|
||||||
* \return The stream corresponding to member.
|
|
||||||
* \throws std::bad_alloc Not enough memory.
|
|
||||||
* \throws std::runtime_error The specified member does not exist
|
|
||||||
*/
|
*/
|
||||||
std::istream& operator[](const std::string& name) throw(std::bad_alloc, std::runtime_error);
|
std::istream& operator[](const std::string& name) throw(std::bad_alloc, std::runtime_error);
|
||||||
private:
|
private:
|
||||||
|
@ -257,115 +238,93 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Open file relative to another
|
|
||||||
*
|
|
||||||
* Opens the file named by name parameter, which is interpretted relative to file designated by referencing_path.
|
* Opens the file named by name parameter, which is interpretted relative to file designated by referencing_path.
|
||||||
* The file can be inside ZIP archive. The resulting stream may or may not be seekable.
|
* The file can be inside ZIP archive. The resulting stream may or may not be seekable.
|
||||||
*
|
*
|
||||||
* If referencing_path is "", then name is traditional relative/absolute path. Otherwise if name is relative,
|
* If referencing_path is "", then name is traditional relative/absolute path. Otherwise if name is relative,
|
||||||
* it is relative to directory containing referencing_path, not current directory.
|
* it is relative to directory containing referencing_path, not current directory.
|
||||||
*
|
*
|
||||||
* \param name The name of file to open.
|
* parameter name: The name of file to open.
|
||||||
* \param referencing_path The path to file name is interpretted against.
|
* parameter referencing_path: The path to file name is interpretted against.
|
||||||
* \return The new stream, allocated by new.
|
* returns: The new stream, allocated by new.
|
||||||
* \throw std::bad_alloc Not enough memory.
|
* throw std::bad_alloc: Not enough memory.
|
||||||
* \throw std::runtime_error The file does not exist or can't be opened.
|
* throw std::runtime_error: The file does not exist or can't be opened.
|
||||||
*/
|
*/
|
||||||
std::istream& open_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
|
std::istream& open_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
|
||||||
std::runtime_error);
|
std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Read file relative to another.
|
* As open_file_relative, but instead of returning handle to file, reads the entiere contents of the file and returns
|
||||||
|
* that.
|
||||||
*
|
*
|
||||||
* Reads the entiere content of file named by name parameter, which is interpretted relative to file designated by
|
* parameter name: As in open_file_relative
|
||||||
* referencing_path. The file can be inside ZIP archive.
|
* parameter referencing_path: As in open_file_relative.
|
||||||
*
|
* returns: The file contents.
|
||||||
* If referencing_path is "", then name is traditional relative/absolute path. Otherwise if name is relative,
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* it is relative to directory containing referencing_path, not current directory.
|
* throws std::runtime_error: The file does not exist or can't be opened.
|
||||||
*
|
|
||||||
* \param name The name of file to read.
|
|
||||||
* \param referencing_path The path to file name is interpretted against.
|
|
||||||
* \return The file contents.
|
|
||||||
* \throw std::bad_alloc Not enough memory.
|
|
||||||
* \throw std::runtime_error The file does not exist or can't be opened.
|
|
||||||
*/
|
*/
|
||||||
std::vector<char> read_file_relative(const std::string& name, const std::string& referencing_path)
|
std::vector<char> read_file_relative(const std::string& name, const std::string& referencing_path)
|
||||||
throw(std::bad_alloc, std::runtime_error);
|
throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Resolve full path of file relative to another.
|
|
||||||
*
|
|
||||||
* Resolves the final file path that open_file_relative/read_file_relative would open.
|
* Resolves the final file path that open_file_relative/read_file_relative would open.
|
||||||
*
|
*
|
||||||
* \param name The name of file to read.
|
* parameter name: As in open_file_relative
|
||||||
* \param referencing_path The path to file name is interpretted against.
|
* parameter referencing_path: As in open_file_relative
|
||||||
* \return The file absolute path.
|
* returns: The file absolute path.
|
||||||
* \throw std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throw std::runtime_error Bad path.
|
* throws std::runtime_error: Bad path.
|
||||||
*/
|
*/
|
||||||
std::string resolve_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
|
std::string resolve_file_relative(const std::string& name, const std::string& referencing_path) throw(std::bad_alloc,
|
||||||
std::runtime_error);
|
std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Write a ZIP archive
|
* This class handles writing a ZIP archives.
|
||||||
*
|
|
||||||
* This class handles writing a ZIP archive.
|
|
||||||
*/
|
*/
|
||||||
class zip_writer
|
class zip_writer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* \brief Create new empty ZIP archive.
|
|
||||||
*
|
|
||||||
* Creates new empty ZIP archive. The members will be compressed according to specified compression.
|
* Creates new empty ZIP archive. The members will be compressed according to specified compression.
|
||||||
*
|
*
|
||||||
* \param zipfile The zipfile to create.
|
* parameter zipfile: The zipfile to create.
|
||||||
* \param _compression Compression. 0 is uncompressed, 1-9 are deflate compression levels.
|
* parameter _compression: Compression. 0 is uncompressed, 1-9 are deflate compression levels.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::runtime_error Can't open archive or invalid argument.
|
* throws std::runtime_error: Can't open archive or invalid argument.
|
||||||
*/
|
*/
|
||||||
zip_writer(const std::string& zipfile, unsigned _compression) throw(std::bad_alloc, std::runtime_error);
|
zip_writer(const std::string& zipfile, unsigned _compression) throw(std::bad_alloc, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Destroy ZIP writer.
|
|
||||||
*
|
|
||||||
* Destroys ZIP writer, aborting the transaction (unless commit() has been called).
|
* Destroys ZIP writer, aborting the transaction (unless commit() has been called).
|
||||||
*/
|
*/
|
||||||
~zip_writer() throw();
|
~zip_writer() throw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Commit the transaction
|
|
||||||
*
|
|
||||||
* Commits the ZIP file. Does atomic replace of existing file if possible.
|
* Commits the ZIP file. Does atomic replace of existing file if possible.
|
||||||
*
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::logic_error Existing file open.
|
* throws std::logic_error: Existing file open.
|
||||||
* \throws std::runtime_error Can't commit archive (OS error or member open).
|
* throws std::runtime_error: Can't commit archive (OS error or member open).
|
||||||
*/
|
*/
|
||||||
void commit() throw(std::bad_alloc, std::logic_error, std::runtime_error);
|
void commit() throw(std::bad_alloc, std::logic_error, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Create a new member
|
|
||||||
*
|
|
||||||
* Create a new member inside ZIP file. No existing member may be open.
|
* Create a new member inside ZIP file. No existing member may be open.
|
||||||
*
|
*
|
||||||
* \param name The name for new member.
|
* parameter name: The name for new member.
|
||||||
* \return Writing stream for the file (don't free).
|
* returns: Writing stream for the file (don't free).
|
||||||
*
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::logic_error: Existing file open.
|
||||||
* \throws std::logic_error Existing file open.
|
* throws std::runtime_error: Illegal name.
|
||||||
* \throws std::runtime_error Illegal name.
|
|
||||||
*/
|
*/
|
||||||
std::ostream& create_file(const std::string& name) throw(std::bad_alloc, std::logic_error, std::runtime_error);
|
std::ostream& create_file(const std::string& name) throw(std::bad_alloc, std::logic_error, std::runtime_error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Close open member
|
|
||||||
*
|
|
||||||
* Closes open member and destroys stream corresponding to it.
|
* Closes open member and destroys stream corresponding to it.
|
||||||
*
|
*
|
||||||
* \throws std::bad_alloc Not enough memory.
|
* throws std::bad_alloc: Not enough memory.
|
||||||
* \throws std::logic_error No file open.
|
* throws std::logic_error: No file open.
|
||||||
* \throws std::runtime_error Error from operating system.
|
* throws std::runtime_error: Error from operating system.
|
||||||
*/
|
*/
|
||||||
void close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error);
|
void close_file() throw(std::bad_alloc, std::logic_error, std::runtime_error);
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Add table
Reference in a new issue