///////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008-2012 Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>
//
// See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#define CPPCMS_SOURCE
#include <cppcms/views_pool.h>
#include <cppcms/json.h>
#include <cppcms/config.h>
#include <cppcms/cppcms_error.h>
#include <booster/locale/format.h>
#include <booster/shared_ptr.h>
#include <booster/thread.h>
#include <booster/system_error.h>
#include <booster/log.h>
#ifdef CPPCMS_WIN_NATIVE
#include <booster/nowide/convert.h>
#include <windows.h>
#include <process.h>
#else
#include <dlfcn.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
namespace cppcms {
namespace views {
// class generator
struct generator::data {};
generator::generator()
{
}
generator::~generator()
{
}
void generator::name(std::string const &n)
{
name_ = n;
}
std::string generator::name() const
{
return name_;
}
void generator::add_factory(std::string const &n,generator::view_factory_type *factory)
{
views_[n]=factory;
}
std::auto_ptr<base_view> generator::create( std::string const &view_name,
std::ostream &output,
base_content *content) const
{
std::auto_ptr<base_view> result;
views_type::const_iterator p = views_.find(view_name);
if(p==views_.end())
return result;
result = p->second(output,content);
return result;
}
std::vector<std::string> generator::enumerate() const
{
std::vector<std::string> all;
all.reserve(views_.size());
for(views_type::const_iterator p=views_.begin(),e=views_.end();p!=e;++p) {
all.push_back(p->first);
}
return all;
}
// class pool
struct pool::data {
booster::recursive_shared_mutex lock;
typedef std::map<std::string, generator const *> skin_generators_type;
typedef std::map<std::string,skin_generators_type> generators_type;
generators_type generators;
};
void pool::add(generator const &g)
{
generator const *ptr = &g;
std::string name = ptr->name();
booster::unique_lock<booster::recursive_shared_mutex> guard(d->lock);
data::generators_type::iterator s = d->generators.find(name);
if (s == d->generators.end()) {
d->generators[name] = data::skin_generators_type();
s = d->generators.find(name);
}
std::vector<std::string> views = ptr->enumerate();
for(std::vector<std::string>::iterator v=views.begin(), e=views.end();v!=e;++v) {
data::skin_generators_type& skin = s->second;
skin[*v] = ptr;
}
}
void pool::remove(generator const &g)
{
generator const *ptr = &g;
std::string name = ptr->name();
booster::unique_lock<booster::recursive_shared_mutex> guard(d->lock);
data::generators_type::iterator s = d->generators.find(name);
if (s == d->generators.end())
return;
data::skin_generators_type& skin = s->second;
std::vector<std::string> views = ptr->enumerate();
for(std::vector<std::string>::iterator v=views.begin(), e=views.end();v!=e;++v) {
if (skin[*v] == ptr)
skin.erase(*v);
}
if (skin.empty())
d->generators.erase(name);
}
base_view *pool::create_view(std::string const &skin,std::string const &template_name,std::ostream &out,base_content &content)
{
data::generators_type::iterator s=d->generators.find(skin);
if(s==d->generators.end())
throw cppcms_error("cppcms::views::pool: no such skin:" + skin);
data::skin_generators_type const& reg_skin = s->second;
data::skin_generators_type::const_iterator t=reg_skin.find(template_name);
if(t==reg_skin.end())
throw cppcms_error("cppcms::view::pool: no suck view:" + template_name + " is registered for skin: " + skin);
std::auto_ptr<base_view> v;
v = t->second->create(template_name,out,&content);
if(!v.get())
throw cppcms_error("cppcms::views::pool: no such view " + template_name + " in the skin " + skin);
return v.release();
}
void pool::lock()
{
d->lock.shared_lock();
}
void pool::unlock()
{
d->lock.unlock();
}
struct view_lock::_data {};
view_lock::view_lock(std::string const &skin,std::string const &template_name,std::ostream &out,base_content &content)
{
pool &p = pool::instance();
p.lock();
try {
view_.reset(p.create_view(skin,template_name,out,content));
}
catch(...) {
p.unlock();
throw;
}
}
view_lock::~view_lock()
{
view_.reset();
pool::instance().unlock();
}
base_view &view_lock::view()
{
return *view_;
}
void pool::render(std::string const &skin,std::string const &template_name,std::ostream &out,base_content &content)
{
booster::shared_lock<booster::recursive_shared_mutex> guard(d->lock);
{
booster::hold_ptr<base_view> v(create_view(skin,template_name,out,content));
v->render();
}
}
std::vector<std::string> pool::enumerate()
{
booster::shared_lock<booster::recursive_shared_mutex> guard(d->lock);
std::vector<std::string> all;
all.reserve(d->generators.size());
for(data::generators_type::iterator p=d->generators.begin(),e=d->generators.end();p!=e;++p) {
all.push_back(p->first);
}
return all;
}
pool::pool() : d(new data) {}
pool::~pool() {}
pool &pool::instance()
{
static pool instance_;
return instance_;
}
namespace {
struct initializer { initializer() { pool::instance(); } } initializer_instance;
}
namespace impl {
#ifdef CPPCMS_WIN_NATIVE
class shared_object : public booster::noncopyable {
public:
static std::string name(std::string file,std::string path);
shared_object(std::string u8file_name,bool reloadable)
{
std::wstring file_name = booster::nowide::convert(u8file_name);
if(reloadable) {
int pid=_getpid();
file_name_ = (booster::locale::wformat(L"{1}.tmp-{2}.dll") % file_name % pid).str(std::locale::classic());
if(!CopyFileW(file_name.c_str(),file_name_.c_str(),1)) {
booster::system::error_code e(GetLastError(),booster::system::windows_category);
throw booster::system::system_error(e,"Failed to copy file "+u8file_name+" to "
+booster::nowide::convert(file_name_));
}
remove_=true;
}
else {
file_name_ = file_name;
remove_=false;
}
handler_ = LoadLibraryW(file_name_.c_str());
if(!handler_) {
if(remove_)
DeleteFileW(file_name_.c_str());
booster::system::error_code e(GetLastError(),booster::system::windows_category);
throw booster::system::system_error(e,"Failed to load library "+u8file_name);
}
}
~shared_object()
{
FreeLibrary(handler_);
if(remove_) {
DeleteFileW(file_name_.c_str());
}
}
FARPROC symbol(std::string const &name) const
{
return GetProcAddress(handler_,name.c_str());
}
private:
HMODULE handler_;
std::wstring file_name_;
bool remove_;
};
#else
class shared_object : public booster::noncopyable {
public:
static std::string name(std::string file,std::string path);
shared_object(std::string file_name,bool /*unused*/)
{
handler_ = dlopen(file_name.c_str(),RTLD_LAZY | RTLD_GLOBAL);
if(!handler_) {
booster::system::error_code e(errno,booster::system::system_category);
throw booster::system::system_error(e,"Failed to load library "+file_name);
}
}
~shared_object()
{
dlclose(handler_);
}
void *symbol(std::string const &name) const
{
return dlsym(handler_,name.c_str());
}
private:
void *handler_;
};
#endif
time_t get_mtime(std::string const &file_name)
{
#ifdef CPPCMS_WIN_NATIVE
struct _stat st;
if(_wstat(booster::nowide::convert(file_name).c_str(),&st) < 0)
return 0;
#else
struct stat st;
if(stat(file_name.c_str(),&st) < 0)
return 0;
#endif
return st.st_mtime;
}
struct skin {
std::string file_name;
booster::shared_ptr<impl::shared_object> so;
time_t mtime;
};
std::string default_library_pattern()
{
#ifdef CPPCMS_LIBRARY_PREFIX
return CPPCMS_LIBRARY_PREFIX "{1}" CPPCMS_LIBRARY_SUFFIX;
#else
return "{1}" CPPCMS_LIBRARY_SUFFIX;
#endif
}
} // impl
struct manager::data {
bool auto_reload;
std::string default_skin;
std::vector<impl::skin> skins;
booster::recursive_shared_mutex lock;
data() : auto_reload(false)
{
}
};
manager::manager(json::value const &settings) :
d(new data())
{
// configure all
d->auto_reload = settings.get("views.auto_reload",false);
std::string pattern = settings.get("views.shared_object_pattern",impl::default_library_pattern());
std::vector<std::string> paths=settings.get("views.paths",std::vector<std::string>());
std::vector<std::string> skins=settings.get("views.skins",std::vector<std::string>());
if(!skins.empty() && paths.empty()) {
throw cppcms_error("When views.skins provided at least one search path should be given in views.paths");
}
d->default_skin = settings.get<std::string>("views.default_skin","");
if(d->default_skin.empty()) {
if(!skins.empty())
d->default_skin = skins.front();
else {
std::vector<std::string> static_skins = pool::instance().enumerate();
if(static_skins.size()==1)
d->default_skin = static_skins[0];
}
}
// configuration done now load all
for(unsigned i=0;i<skins.size();i++) {
std::string name=skins[i];
unsigned j;
for(j=0;j<paths.size();j++) {
impl::skin new_skin;
new_skin.file_name = paths[j] + "/" + (booster::locale::format(pattern) % name).str();
new_skin.mtime = impl::get_mtime(new_skin.file_name);
if(new_skin.mtime!=0) {
new_skin.so.reset(new impl::shared_object(new_skin.file_name,d->auto_reload));
d->skins.push_back(new_skin);
break;
}
}
if(j == paths.size()) {
throw cppcms_error("Failed to load skin:" + name+ ", no shared object/dll found");
}
}
}
manager::~manager()
{
}
std::string manager::default_skin()
{
return d->default_skin;
}
void manager::render(std::string const &skin_name,std::string const &template_name,std::ostream &out,base_content &content)
{
if(skin_name.empty() && d->default_skin.empty()) {
throw cppcms_error("No default skin was detected, please define one in views.default_skin");
}
if(d->auto_reload) {
{ // Check if update
bool reload_required = false;
booster::shared_lock<booster::recursive_shared_mutex> guard(d->lock);
for(size_t i=0;i<d->skins.size();i++) {
time_t mtime = impl::get_mtime(d->skins[i].file_name);
if(mtime != d->skins[i].mtime) {
reload_required = true;
break;
}
}
if(!reload_required) {
pool::instance().render(skin_name,template_name,out,content);
return;
}
}
// reload all if needed
booster::unique_lock<booster::recursive_shared_mutex> lock(d->lock);
for(size_t i=0;i<d->skins.size();i++) {
impl::skin ¤t = d->skins[i];
time_t mtime = impl::get_mtime(current.file_name);
if(mtime == current.mtime) {
continue;
}
BOOSTER_DEBUG("cppcms") << "Reloading shared object/dll " << current.file_name;
current.so.reset();
current.mtime = mtime;
current.so.reset(new impl::shared_object(current.file_name,true));
}
pool::instance().render(skin_name,template_name,out,content);
return;
}
pool::instance().render(skin_name,template_name,out,content);
}
} // views
} // cppcms