summaryrefslogtreecommitdiffstats
path: root/src/modules/UserDBBackendMysql/UserDBBackendMysql.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/UserDBBackendMysql/UserDBBackendMysql.cpp')
-rw-r--r--src/modules/UserDBBackendMysql/UserDBBackendMysql.cpp652
1 files changed, 652 insertions, 0 deletions
diff --git a/src/modules/UserDBBackendMysql/UserDBBackendMysql.cpp b/src/modules/UserDBBackendMysql/UserDBBackendMysql.cpp
new file mode 100644
index 0000000..7ca3db2
--- /dev/null
+++ b/src/modules/UserDBBackendMysql/UserDBBackendMysql.cpp
@@ -0,0 +1,652 @@
+/*
+ * UserDBBackendMysql.cpp
+ *
+ * Copyright (C) 2008 Matthias Schiffer <matthias@gamezock.de>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "UserDBBackendMysql.h"
+#include <Core/ConfigEntry.h>
+#include <Core/ConfigManager.h>
+#include <Core/ThreadManager.h>
+
+#include <sstream>
+
+#include <boost/bind.hpp>
+#include <boost/regex.hpp>
+#include <boost/thread/locks.hpp>
+
+#include <mysql/mysqld_error.h>
+
+namespace Mad {
+namespace Modules {
+namespace UserDBBackendMysql {
+
+const std::string UserDBBackendMysql::name("UserDBBackendMysql");
+
+bool UserDBBackendMysql::handleConfigEntry(const Core::ConfigEntry &entry, bool /*handled*/) {
+ if(!entry[0].getKey().matches("UserManager"))
+ return false;
+
+ if(entry[1].empty())
+ return true;
+
+ if(!entry[1].getKey().matches("Mysql"))
+ return false;
+
+ if(entry[2].getKey().matches("Host")) {
+ if(entry[3].empty())
+ host = entry[2][0];
+ }
+ else if(entry[2].getKey().matches("Username")) {
+ if(entry[3].empty())
+ username = entry[2][0];
+ }
+ else if(entry[2].getKey().matches("Password")) {
+ if(entry[3].empty())
+ passwd = entry[2][0];
+ }
+ else if(entry[2].getKey().matches("Database")) {
+ if(entry[3].empty())
+ db = entry[2][0];
+ }
+ else if(entry[2].getKey().matches("Port")) {
+ if(entry[3].empty()) {
+ char *endptr;
+ long val;
+
+ val = strtol(entry[2][0].c_str(), &endptr, 10);
+
+ if(endptr != 0 || val < 0 || val > 65535)
+ application->log(Core::LoggerBase::WARNING, "UserDBBackendMysql: Invalid port");
+ else
+ port = val;
+ }
+ }
+ else if(entry[2].getKey().matches("UnixSocket")) {
+ if(entry[3].empty())
+ unixSocket = entry[2][0];
+ }
+ else if(entry[2].getKey().matches("Queries")) {
+ if(entry[3].getKey().matches("ListUsers")) {
+ if(entry[4].empty())
+ queryListUsers = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("ListGroups")) {
+ if(entry[4].empty())
+ queryListGroups = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("ListUserGroups")) {
+ if(entry[4].empty())
+ queryListUserGroups = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("ListGroupUsers")) {
+ if(entry[4].empty())
+ queryListGroupUsers = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("UserById")) {
+ if(entry[4].empty())
+ queryUserById = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("UserByName")) {
+ if(entry[4].empty())
+ queryUserByName = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("GroupById")) {
+ if(entry[4].empty())
+ queryGroupById = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("GroupByName")) {
+ if(entry[4].empty())
+ queryGroupByName = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("UserGroupTable")) {
+ if(entry[4].empty())
+ queryUserGroupTable = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("AddUser")) {
+ if(entry[4].empty())
+ queryAddUser = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("UpdateUser")) {
+ if(entry[4].empty())
+ queryUpdateUser = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("DeleteUser")) {
+ if(entry[4].empty())
+ queryDeleteUser = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("AddGroup")) {
+ if(entry[4].empty())
+ queryAddGroup = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("UpdateGroup")) {
+ if(entry[4].empty())
+ queryUpdateGroup = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("DeleteGroup")) {
+ if(entry[4].empty())
+ queryDeleteGroup = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("AddUserToGroup")) {
+ if(entry[4].empty())
+ queryAddUserToGroup = entry[3][0];
+ }
+ else if(entry[3].getKey().matches("DeleteUserFromGroup")) {
+ if(entry[4].empty())
+ queryDeleteUserFromGroup = entry[3][0];
+ }
+ else if(!entry[3].empty())
+ return false;
+ }
+ else if(!entry[2].empty())
+ return false;
+
+ return true;
+}
+
+void UserDBBackendMysql::configFinished() {
+ if(db.empty()) {
+ application->log(Core::LoggerBase::ERROR, "UserDBBackendMysql: No database name given");
+ return;
+ }
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+ mysql = mysql_init(0);
+ mysql_real_connect(mysql, host.c_str(), username.c_str(), passwd.c_str(), db.c_str(), port, unixSocket.empty() ? 0 : unixSocket.c_str(), 0);
+}
+
+
+UserDBBackendMysql::Result UserDBBackendMysql::query(const std::string &query, const ArgumentMap &args) throw(Core::Exception) {
+ if(!mysql || mysql_ping(mysql))
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ if(args.empty()) {
+ mysql_real_query(mysql, query.c_str(), query.length());
+ }
+ else {
+ std::string queryStr = query;
+
+ for(ArgumentMap::const_iterator arg = args.begin(); arg != args.end(); ++arg) {
+ std::string argStr;
+
+ try {
+ argStr = boost::get<std::string>(arg->second);
+ }
+ catch(...) {
+ std::ostringstream stream;
+ stream << arg->second;
+ argStr = stream.str();
+ }
+
+ boost::scoped_array<char> escaped(new char[argStr.length()*2+1]);
+ mysql_real_escape_string(mysql, escaped.get(), argStr.c_str(), argStr.length());
+
+ queryStr = boost::regex_replace(queryStr, boost::regex("\\{" + arg->first + "\\}"), "\"" + std::string(escaped.get()) + "\"", boost::match_default);
+ }
+
+ mysql_real_query(mysql, queryStr.c_str(), queryStr.length());
+ }
+
+ return Result(mysql);
+}
+
+
+boost::shared_ptr<const std::map<unsigned long, Common::UserInfo> > UserDBBackendMysql::getUserList(boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const std::map<unsigned long, Common::UserInfo> >();
+ }
+
+ Result result = query(queryListUsers);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 4)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ boost::shared_ptr<std::map<unsigned long, Common::UserInfo> > users(new std::map<unsigned long, Common::UserInfo>());
+
+ while(MYSQL_ROW row = result.getNextRow()) {
+ Common::UserInfo user(strtoul(row[0], 0, 10), row[2]);
+
+ user.setGid(strtoul(row[1], 0, 10));
+ user.setFullName(row[3]);
+
+ users->insert(std::make_pair(user.getUid(), user));
+ }
+
+ return users;
+}
+
+boost::shared_ptr<const Common::UserInfo> UserDBBackendMysql::getUserInfo(unsigned long uid, boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const Common::UserInfo>();
+ }
+
+ ArgumentMap args;
+ args.insert(std::make_pair("UID", uid));
+
+ Result result = query(queryUserById, args);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 4)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ MYSQL_ROW row = result.getNextRow();
+
+ if(row) {
+ boost::shared_ptr<Common::UserInfo> user(new Common::UserInfo(strtoul(row[0], 0, 10), row[2]));
+
+ user->setGid(strtoul(row[1], 0, 10));
+ user->setFullName(row[3]);
+
+ return user;
+ }
+
+ throw Core::Exception(Core::Exception::NOT_FOUND);
+}
+
+boost::shared_ptr<const Common::UserInfo> UserDBBackendMysql::getUserInfoByName(const std::string &name, boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const Common::UserInfo>();
+ }
+
+ ArgumentMap args;
+ args.insert(std::make_pair("USER", name));
+
+ Result result = query(queryUserByName, args);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 4)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ MYSQL_ROW row = result.getNextRow();
+
+ if(row) {
+ boost::shared_ptr<Common::UserInfo> user(new Common::UserInfo(strtoul(row[0], 0, 10), row[2]));
+
+ user->setGid(strtoul(row[1], 0, 10));
+ user->setFullName(row[3]);
+
+ return user;
+ }
+
+ throw Core::Exception(Core::Exception::NOT_FOUND);
+}
+
+boost::shared_ptr<const std::set<unsigned long> > UserDBBackendMysql::getUserGroupList(unsigned long uid, boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const std::set<unsigned long> >();
+ }
+
+ ArgumentMap args;
+ args.insert(std::make_pair("UID", uid));
+
+ Result result = query(queryListUserGroups, args);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 1)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ boost::shared_ptr<std::set<unsigned long> > groups(new std::set<unsigned long>);
+
+ while(MYSQL_ROW row = result.getNextRow())
+ groups->insert(strtoul(row[0], 0, 10));
+
+ return groups;
+}
+
+
+boost::shared_ptr<const std::map<unsigned long, Common::GroupInfo> > UserDBBackendMysql::getGroupList(boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const std::map<unsigned long, Common::GroupInfo> >();
+ }
+
+ Result result = query(queryListGroups);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 2)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ boost::shared_ptr<std::map<unsigned long, Common::GroupInfo> > groups(new std::map<unsigned long, Common::GroupInfo>());
+
+ while(MYSQL_ROW row = result.getNextRow()) {
+ Common::GroupInfo group(strtoul(row[0], 0, 10), row[1]);
+
+ groups->insert(std::make_pair(group.getGid(), group));
+ }
+
+ return groups;
+}
+
+boost::shared_ptr<const Common::GroupInfo> UserDBBackendMysql::getGroupInfo(unsigned long gid, boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const Common::GroupInfo>();
+ }
+
+ ArgumentMap args;
+ args.insert(std::make_pair("GID", gid));
+
+ Result result = query(queryGroupById, args);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 2)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ MYSQL_ROW row = result.getNextRow();
+
+ if(row)
+ return boost::shared_ptr<Common::GroupInfo>(new Common::GroupInfo(strtoul(row[0], 0, 10), row[1]));
+
+ throw Core::Exception(Core::Exception::NOT_FOUND);
+}
+
+boost::shared_ptr<const Common::GroupInfo> UserDBBackendMysql::getGroupInfoByName(const std::string &name, boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const Common::GroupInfo>();
+ }
+
+ ArgumentMap args;
+ args.insert(std::make_pair("GROUP", name));
+
+ Result result = query(queryGroupByName, args);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 2)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ MYSQL_ROW row = result.getNextRow();
+
+ if(row)
+ return boost::shared_ptr<Common::GroupInfo>(new Common::GroupInfo(strtoul(row[0], 0, 10), row[1]));
+
+ throw Core::Exception(Core::Exception::NOT_FOUND);
+}
+
+boost::shared_ptr<const std::set<unsigned long> > UserDBBackendMysql::getGroupUserList(unsigned long gid, boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const std::set<unsigned long> >();
+ }
+
+ ArgumentMap args;
+ args.insert(std::make_pair("GID", gid));
+
+ Result result = query(queryListGroupUsers, args);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 1)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ boost::shared_ptr<std::set<unsigned long> > users(new std::set<unsigned long>);
+
+ while(MYSQL_ROW row = result.getNextRow())
+ users->insert(strtoul(row[0], 0, 10));
+
+ return users;
+}
+
+boost::shared_ptr<const std::multimap<unsigned long, unsigned long> > UserDBBackendMysql::getFullUserGroupList(boost::posix_time::ptime *timestamp) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::unique_lock<boost::mutex> lock(mutex);
+
+ if(timestamp) {
+ if(timestamp->is_not_a_date_time() || *timestamp < lastUpdate)
+ *timestamp = lastUpdate;
+ else
+ return boost::shared_ptr<const std::multimap<unsigned long, unsigned long> >();
+ }
+
+ Result result = query(queryUserGroupTable);
+
+ lock.unlock();
+
+ if(!result || result.getErrno() || result.getFieldNumber() < 2)
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ boost::shared_ptr<std::multimap<unsigned long, unsigned long> > usergroups(new std::multimap<unsigned long, unsigned long>);
+
+ while(MYSQL_ROW row = result.getNextRow())
+ usergroups->insert(std::make_pair(strtoul(row[0], 0, 10), strtoul(row[1], 0, 10)));
+
+ return usergroups;
+}
+
+void UserDBBackendMysql::addUser(const Common::UserInfo &userInfo) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("UID", userInfo.getUid()));
+ args.insert(std::make_pair("GID", userInfo.getGid()));
+ args.insert(std::make_pair("USER", userInfo.getUsername()));
+ args.insert(std::make_pair("FULL_NAME", userInfo.getFullName()));
+
+ Result result = query(queryAddUser, args);
+
+ if(result.getErrno()) {
+ if(result.getErrno() == ER_DUP_ENTRY)
+ throw Core::Exception(Core::Exception::DUPLICATE_ENTRY);
+ else
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+ }
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+void UserDBBackendMysql::updateUser(unsigned long uid, const Common::UserInfo &userInfo) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("ORIG_UID", uid));
+ args.insert(std::make_pair("UID", userInfo.getUid()));
+ args.insert(std::make_pair("GID", userInfo.getGid()));
+ args.insert(std::make_pair("USER", userInfo.getUsername()));
+ args.insert(std::make_pair("FULL_NAME", userInfo.getFullName()));
+
+ Result result = query(queryUpdateUser, args);
+
+ if(result.getErrno()) {
+ if(result.getErrno() == ER_DUP_ENTRY)
+ throw Core::Exception(Core::Exception::DUPLICATE_ENTRY);
+ else
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+ }
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+void UserDBBackendMysql::deleteUser(unsigned long uid) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("UID", uid));
+
+ Result result = query(queryDeleteUser, args);
+
+ if(result.getErrno())
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+void UserDBBackendMysql::addGroup(const Common::GroupInfo &groupInfo) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("GID", groupInfo.getGid()));
+ args.insert(std::make_pair("GROUP", groupInfo.getName()));
+
+ Result result = query(queryAddGroup, args);
+
+ if(result.getErrno()) {
+ if(result.getErrno() == ER_DUP_ENTRY)
+ throw Core::Exception(Core::Exception::DUPLICATE_ENTRY);
+ else
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+ }
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+void UserDBBackendMysql::updateGroup(unsigned long gid, const Common::GroupInfo &groupInfo) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("ORIG_GID", gid));
+ args.insert(std::make_pair("GID", groupInfo.getGid()));
+ args.insert(std::make_pair("GROUP", groupInfo.getName()));
+
+ Result result = query(queryUpdateGroup, args);
+
+ if(result.getErrno()) {
+ if(result.getErrno() == ER_DUP_ENTRY)
+ throw Core::Exception(Core::Exception::DUPLICATE_ENTRY);
+ else
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+ }
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+void UserDBBackendMysql::deleteGroup(unsigned long gid) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("GID", gid));
+
+ Result result = query(queryDeleteGroup, args);
+
+ if(result.getErrno())
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+void UserDBBackendMysql::addUserToGroup(unsigned long uid, unsigned long gid) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("UID", uid));
+ args.insert(std::make_pair("GID", gid));
+
+ Result result = query(queryAddUserToGroup, args);
+
+ if(result.getErrno()) {
+ if(result.getErrno() == ER_DUP_ENTRY)
+ return;
+ else
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+ }
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+void UserDBBackendMysql::deleteUserFromGroup(unsigned long uid, unsigned long gid) throw(Core::Exception) {
+ application->getThreadManager()->detach();
+
+ boost::lock_guard<boost::mutex> lock(mutex);
+
+ ArgumentMap args;
+ args.insert(std::make_pair("UID", uid));
+ args.insert(std::make_pair("GID", gid));
+
+ Result result = query(queryDeleteUserFromGroup, args);
+
+ if(result.getErrno())
+ throw Core::Exception(Core::Exception::NOT_AVAILABLE);
+
+ lastUpdate = boost::posix_time::microsec_clock::universal_time();
+}
+
+}
+}
+}