diff options
| -rw-r--r-- | src/.gitignore | 2 | ||||
| -rw-r--r-- | src/Makefile.am | 10 | ||||
| -rw-r--r-- | src/event_main.cc | 182 | ||||
| -rw-r--r-- | src/event_utils.cc | 199 | ||||
| -rw-r--r-- | src/event_utils.hh | 58 |
5 files changed, 309 insertions, 142 deletions
diff --git a/src/.gitignore b/src/.gitignore index 13d1e3c..bda1427 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -4,9 +4,11 @@ /stamp-h1 /libcgi.la /libdb.la +/libevent.la /libjson.la /libutil.la /libsender_client.la /event +/page /sender /test-sender diff --git a/src/Makefile.am b/src/Makefile.am index 1f8b819..8a6ee24 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,10 +6,10 @@ AM_CPPFLAGS = @DEFINES@ -DLOCALSTATEDIR='"@localstatedir@/stuff"' \ bin_PROGRAMS = event sender noinst_PROGRAMS = test-sender noinst_LTLIBRARIES = libdb.la libcgi.la libutil.la libsender_client.la \ - libjson.la + libjson.la libevent.la -event_SOURCES = event.cc event.hh event_main.cc common.hh cgi.hh db.hh -event_LDADD = libdb.la libcgi.la libsender_client.la +event_SOURCES = event_main.cc common.hh cgi.hh +event_LDADD = libcgi.la libevent.la sender_SOURCES = common.hh sender.cc sender_CPPFLAGS = $(AM_CPPFLAGS) @CURL_CFLAGS@ @@ -18,6 +18,10 @@ sender_LDADD = libjson.la libutil.la @CURL_LIBS@ test_sender_SOURCES = common.hh test_sender.cc sender_client.hh test_sender_LDADD = libsender_client.la +libevent_la_SOURCES = event.cc event.hh event_utils.cc event_utils.hh \ + common.hh db.hh sender_client.hh +libevent_la_LIBADD = libdb.la libsender_client.la + libjson_la_SOURCES = json.hh json.cc common.hh libcgi_la_SOURCES = cgi.hh common.hh cgi.cc \ diff --git a/src/event_main.cc b/src/event_main.cc index 093ed76..c7c867e 100644 --- a/src/event_main.cc +++ b/src/event_main.cc @@ -1,7 +1,6 @@ #include "common.hh" #include <algorithm> -#include <functional> #include <iostream> #include <memory> #include <set> @@ -14,10 +13,9 @@ #include "config.hh" #include "db.hh" #include "event.hh" -#include "fsutils.hh" +#include "event_utils.hh" #include "http.hh" #include "sender_client.hh" -#include "sqlite3_db.hh" /* token=gIkuvaNzQIHg97ATvDxqgjtO @@ -38,82 +36,12 @@ namespace { std::unique_ptr<Config> g_cfg; std::unique_ptr<SenderClient> g_sender; -std::shared_ptr<DB> open(const std::string& channel) { - std::string tmp = channel; - for (auto it = tmp.begin(); it != tmp.end(); ++it) { - if (!((*it >= 'a' && *it <= 'z') || - (*it >= 'A' && *it <= 'Z') || - (*it >= '0' && *it <= '9') || - *it == '-' || *it == '_' || *it == '.')) { - *it = '.'; - } - } - std::string path = g_cfg->get("db_path", LOCALSTATEDIR); - if (path.empty()) path = "."; - if (!mkdir_p(path)) { - Http::response(200, "Unable to create database directory"); - return nullptr; - } - auto db = SQLite3::open(path + "/" + tmp + ".db"); - if (!db || db->bad()) { - Http::response(200, "Unable to open database"); - db.reset(); - } else if (!Event::setup(db.get())) { - Http::response(200, "Unable to setup database"); - db.reset(); - } - return std::move(db); -} - bool parse(const std::string& text, std::vector<std::string>* args) { if (Args::parse(text, args)) return true; Http::response(200, "Invalid parameter format"); return false; } -const double ONE_DAY_IN_SEC = 24.0 * 60.0 * 60.0; -const double ONE_WEEK_IN_SEC = ONE_DAY_IN_SEC * 7.0; -// It's OK that we ignore leap years here -const double ONE_YEAR_IN_SEC = 365 * ONE_DAY_IN_SEC; - -std::string format_date(time_t date) { - time_t now = time(NULL); - struct tm _t; - struct tm* t = localtime_r(&date, &_t); - double diff = difftime(date, now); - char tmp[100]; - if (diff <= ONE_DAY_IN_SEC) { - // Same day, just show time - strftime(tmp, sizeof(tmp), "%H:%M", t); - } else if (diff <= ONE_WEEK_IN_SEC) { - // Inside a week, show day and time - strftime(tmp, sizeof(tmp), "%A %H:%M", t); - } else if (diff <= ONE_YEAR_IN_SEC / 2.0) { - // Inside a year, show date, day and time - strftime(tmp, sizeof(tmp), "%A %d/%m %H:%M", t); - } else { - strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M", t); - } - return tmp; -} - -void signal_channel(const std::string& channel, const std::string& str) { - if (!g_sender) return; - g_sender->send(channel, str); -} - -void signal_event(const std::string& channel, - const std::unique_ptr<Event>& event) { - std::ostringstream ss; - ss << event->name() << " @ " << format_date(event->start()) << std::endl; - if (!event->text().empty()) { - ss << event->text() << std::endl; - } - ss << std::endl; - ss << "Use /event going to join the event" << std::endl; - signal_channel(channel, ss.str()); -} - bool parse_time(const std::string& value, time_t* date) { struct tm _t, _tmp; time_t now = time(NULL); @@ -129,7 +57,7 @@ bool parse_time(const std::string& value, time_t* date) { if (days <= 0) { days += 7; } - time_t tmp = mktime(&_tmp) + days * ONE_DAY_IN_SEC; + time_t tmp = mktime(&_tmp) + days * EventUtils::ONE_DAY_IN_SEC; localtime_r(&tmp, &_tmp); goto done; } @@ -148,7 +76,7 @@ bool parse_time(const std::string& value, time_t* date) { return true; } -bool create(const std::string& channel, +bool create(EventUtils* utils, std::map<std::string, std::string>& data, std::vector<std::string>& args) { if (args.size() < 2) { @@ -171,9 +99,8 @@ bool create(const std::string& channel, text.append(args.front()); args.erase(args.begin()); } - auto db = open(channel); - if (!db) return true; - auto event = Event::create(db, name, start); + auto event = utils->create(name, start); + if (!utils->good()) return true; if (!event) { Http::response(200, "Unable to create event"); return true; @@ -192,10 +119,7 @@ bool create(const std::string& channel, return true; } Http::response(200, "Event created"); - auto next_event = Event::next(db); - if (next_event->id() == event->id()) { - signal_event(channel, next_event); - } + utils->created(event.get()); return true; } @@ -215,7 +139,7 @@ bool append_indexes(Iterator begin, Iterator end, return true; } -bool cancel(const std::string& channel, +bool cancel(EventUtils* utils, std::map<std::string, std::string>& data, std::vector<std::string>& args) { std::vector<unsigned long> indexes; @@ -229,9 +153,8 @@ bool cancel(const std::string& channel, std::greater<unsigned long>()); std::unique(indexes.begin(), indexes.end()); } - auto db = open(channel); - if (!db) return true; - auto events = Event::all(db); + auto events = utils->all(); + if (!utils->good()) return true; if (indexes.front() >= events.size()) { if (events.empty()) { Http::response(200, "There are no events"); @@ -244,28 +167,18 @@ bool cancel(const std::string& channel, } return true; } - std::string signal; for (const auto& index : indexes) { - if (index == 0) { - std::ostringstream ss; - ss << "Event canceled: " << events[index]->name() << " @ " - << format_date(events[index]->start()); - signal = ss.str(); - } - events[index]->remove(); + utils->cancel(events[index].get(), index); } if (indexes.size() > 1) { Http::response(200, "Events removed"); } else { Http::response(200, "Event removed"); } - if (!signal.empty()) { - signal_channel(channel, signal); - } return true; } -bool update(const std::string& channel, +bool update(EventUtils* utils, std::map<std::string, std::string>& data, std::vector<std::string>& args) { std::set<std::string> update; @@ -276,32 +189,34 @@ bool update(const std::string& channel, Http::response(200, "Usage: update [INDEX] [name NAME] [start START] [text TEXT]"); return true; } - auto db = open(channel); - if (!db) return true; std::unique_ptr<Event> event; + int64_t first_event; auto it = args.begin(); if (update.count(*it) == 0) { std::vector<unsigned long> indexes; if (!append_indexes(args.begin(), ++it, &indexes)) { return true; } - auto events = Event::all(db); + auto events = utils->all(); + if (!utils->good()) return true; if (indexes.front() >= events.size()) { std::ostringstream ss; ss << "No such event: " << indexes.front() << std::endl; Http::response(200, ss.str()); return true; } + first_event = events.front()->id(); event.swap(events[indexes.front()]); ++it; } else { - event = Event::next(db); + event = utils->next(); + if (!utils->good()) return true; if (!event) { Http::response(200, "No event to update"); return true; } + first_event = event->id(); } - auto first_event = Event::next(db)->id(); while (it != args.end()) { if (*it == "name") { if (++it == args.end()) { @@ -344,18 +259,14 @@ bool update(const std::string& channel, } if (event->store()) { Http::response(200, "Event updated"); - auto next_event = Event::next(db); - if (next_event->id() != first_event || - next_event->id() == event->id()) { - signal_event(channel, next_event); - } + utils->updated(event.get(), first_event); } else { Http::response(200, "Update failed"); } return true; } -bool show(const std::string& channel, +bool show(EventUtils* utils, std::map<std::string, std::string>& data, std::vector<std::string>& args) { std::vector<unsigned long> indexes; @@ -366,9 +277,8 @@ bool show(const std::string& channel, return true; } } - auto db = open(channel); - if (!db) return true; - auto events = Event::all(db); + auto events = utils->all(); + if (!utils->good()) return true; std::ostringstream ss; for (const auto& index : indexes) { if (indexes.size() > 1) { @@ -382,7 +292,7 @@ bool show(const std::string& channel, } } else { ss << events[index]->name() << " @ " - << format_date(events[index]->start()) << std::endl; + << EventUtils::format_date(events[index]->start()) << std::endl; const auto& text = events[index]->text(); if (!text.empty()) { ss << text << std::endl; @@ -412,7 +322,7 @@ bool show(const std::string& channel, return true; } -bool going(const std::string& channel, +bool going(EventUtils* utils, std::map<std::string, std::string>& data, std::vector<std::string>& args, bool going) { @@ -424,8 +334,6 @@ bool going(const std::string& channel, std::unique_ptr<Event> event; std::vector<unsigned long> indexes; std::string note, user = user_name; - auto db = open(channel); - if (!db) return true; if (!args.empty() && args.front() == "user") { if (args.size() == 1) { Http::response(200, "Expected username after 'user'"); @@ -435,7 +343,8 @@ bool going(const std::string& channel, args.erase(args.begin(), args.begin() + 2); } if (args.empty()) { - event = Event::next(db); + event = utils->next(); + if (!utils->good()) return true; } else { if (args.size() == 1) { char* end = nullptr; @@ -446,7 +355,8 @@ bool going(const std::string& channel, } if (indexes.empty()) { - event = Event::next(db); + event = utils->next(); + if (!utils->good()) return true; note = args.front(); } } else { @@ -462,7 +372,8 @@ bool going(const std::string& channel, } } if (!event) { - auto events = Event::all(db); + auto events = utils->all(); + if (!utils->good()) return true; if (events.empty()) { Http::response(200, "There are no events to attend"); return true; @@ -478,20 +389,7 @@ bool going(const std::string& channel, event->update_going(user, going, note); if (event->store()) { Http::response(200, "Your wish have been recorded, if not granted"); - auto next_event = Event::next(db); - if (next_event->id() == event->id()) { - std::string extra; - if (user != user_name) { - extra = " says " + user_name; - } - if (going) { - signal_channel(channel, user + " will be attending " + - event->name() + extra); - } else { - signal_channel(channel, user + " will not be attending " + - event->name() + extra); - } - } + utils->going(event.get(), going, user, user_name); } else { Http::response(200, "Event store failed"); } @@ -541,6 +439,10 @@ bool help(std::vector<std::string>& args) { return true; } +void error_response(const std::string& message) { + Http::response(200, message); +} + bool handle_request(CGI* cgi) { switch (cgi->request_type()) { case CGI::GET: @@ -575,23 +477,25 @@ bool handle_request(CGI* cgi) { } auto command = args.front(); args.erase(args.begin()); + auto utils = EventUtils::create(channel, error_response, g_cfg.get(), + g_sender.get()); if (command == "create") { - return create(channel, data, args); + return create(utils.get(), data, args); } if (command == "cancel") { - return cancel(channel, data, args); + return cancel(utils.get(), data, args); } if (command == "update") { - return update(channel, data, args); + return update(utils.get(), data, args); } if (command == "show") { - return show(channel, data, args); + return show(utils.get(), data, args); } if (command == "going") { - return going(channel, data, args, true); + return going(utils.get(), data, args, true); } if (command == "!going") { - return going(channel, data, args, false); + return going(utils.get(), data, args, false); } if (command == "help") { return help(args); diff --git a/src/event_utils.cc b/src/event_utils.cc new file mode 100644 index 0000000..403d10f --- /dev/null +++ b/src/event_utils.cc @@ -0,0 +1,199 @@ +#include "common.hh" + +#include <sstream> + +#include "config.hh" +#include "event.hh" +#include "event_utils.hh" +#include "fsutils.hh" +#include "sender_client.hh" +#include "sqlite3_db.hh" + +namespace stuff { + +namespace { + +class EventUtilsImpl : public EventUtils { +public: + EventUtilsImpl(const std::string& channel, + std::function<void(const std::string&)> error_cb, + Config* config, SenderClient* sender) + : channel_(channel), error_cb_(error_cb), cfg_(config), + sender_(sender) { + } + + std::unique_ptr<Event> create( + const std::string& name, time_t start) override { + if (!db_ && !open()) return nullptr; + return Event::create(db_, name, start); + } + + void created(Event* event) override { + if (!event) return; + auto next_event = Event::next(db_); + if (next_event->id() == event->id()) { + signal_event(next_event); + } + } + + std::vector<std::unique_ptr<Event>> all() override { + if (!db_ && !open()) return std::vector<std::unique_ptr<Event>>(); + return Event::all(db_); + } + + std::unique_ptr<Event> next() override { + if (!db_ && !open()) return nullptr; + return Event::next(db_); + } + + bool good() const override { + return db_.get() != nullptr; + } + + void cancel(Event* event, size_t index) override { + if (!event) return; + event->remove(); + if (index == 0) { + std::ostringstream ss; + ss << "Event canceled: " << event->name() << " @ " + << format_date(event->start()); + signal_channel(ss.str()); + } + } + + void updated(Event* event, int64_t was_first) override { + auto next_event = Event::next(db_); + if (next_event->id() != was_first || + next_event->id() == event->id()) { + signal_event(next_event); + } + } + + void going(Event* event, bool going, const std::string& user, + const std::string& owner) override { + auto next_event = Event::next(db_); + if (next_event->id() == event->id()) { + std::string extra; + if (user != owner) { + extra = " says " + owner; + } + if (going) { + signal_channel(user + " will be attending " + + event->name() + extra); + } else { + signal_channel(user + " will not be attending " + + event->name() + extra); + } + } + } + + const std::string& channel() const override { + return channel_; + } + +private: + void signal_event(const std::unique_ptr<Event>& event) { + std::ostringstream ss; + ss << event->name() << " @ " << format_date(event->start()) << std::endl; + if (!event->text().empty()) { + ss << event->text() << std::endl; + } + ss << std::endl; + ss << "Use /event going to join the event" << std::endl; + signal_channel(ss.str()); + } + + void signal_channel(const std::string& str) { + if (!sender_) return; + sender_->send(channel_, str); + } + + void error(const std::string& message) { + if (error_cb_) { + error_cb_(message); + } + } + + bool open() { + std::string tmp = channel_; + for (auto it = tmp.begin(); it != tmp.end(); ++it) { + if (!((*it >= 'a' && *it <= 'z') || + (*it >= 'A' && *it <= 'Z') || + (*it >= '0' && *it <= '9') || + *it == '-' || *it == '_' || *it == '.')) { + *it = '.'; + } + } + std::string path; + if (cfg_) path = cfg_->get("db_path", LOCALSTATEDIR); + if (path.empty()) path = "."; + if (!mkdir_p(path)) { + error("Unable to create database directory"); + return false; + } + auto db = SQLite3::open(path + "/" + tmp + ".db"); + if (!db || db->bad()) { + error("Unable to open database"); + return false; + } else if (!Event::setup(db.get())) { + error("Unable to setup database"); + return false; + } + db_ = std::move(db); + return true; + } + + std::string channel_; + std::function<void(const std::string&)> error_cb_; + std::shared_ptr<DB> db_; + Config* cfg_; + SenderClient* sender_; +}; + +} // namespace + +EventUtils::EventUtils() { +} + +EventUtils::~EventUtils() { +} + +std::unique_ptr<EventUtils> EventUtils::create( + const std::string& channel, + std::function<void(const std::string&)> error_cb, + Config* config, + SenderClient* sender) { + std::unique_ptr<EventUtils> utils( + new EventUtilsImpl(channel, error_cb, config, sender)); + return utils; +} + +const double EventUtils::ONE_DAY_IN_SEC = 24.0 * 60.0 * 60.0; +const double EventUtils::ONE_WEEK_IN_SEC = ONE_DAY_IN_SEC * 7.0; +// It's OK that we ignore leap years here +const double EventUtils::ONE_YEAR_IN_SEC = 365 * ONE_DAY_IN_SEC; + +std::string EventUtils::format_date(time_t date) { + time_t now = time(NULL); + struct tm _t; + struct tm* t = localtime_r(&date, &_t); + double diff = difftime(date, now); + char tmp[100]; + if (diff <= ONE_DAY_IN_SEC) { + // Same day, just show time + strftime(tmp, sizeof(tmp), "%H:%M", t); + } else if (diff <= ONE_WEEK_IN_SEC) { + // Inside a week, show day and time + strftime(tmp, sizeof(tmp), "%A %H:%M", t); + } else if (diff <= ONE_YEAR_IN_SEC / 2.0) { + // Inside a year, show date, day and time + strftime(tmp, sizeof(tmp), "%A %d/%m %H:%M", t); + } else { + strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M", t); + } + return tmp; +} + +} // namespace stuff + + diff --git a/src/event_utils.hh b/src/event_utils.hh new file mode 100644 index 0000000..8151d64 --- /dev/null +++ b/src/event_utils.hh @@ -0,0 +1,58 @@ +#ifndef EVENT_UTILS_HH +#define EVENT_UTILS_HH + +#include <functional> +#include <memory> +#include <string> + +namespace stuff { + +class Config; +class Event; +class SenderClient; + +class EventUtils { +public: + virtual ~EventUtils(); + + virtual std::unique_ptr<Event> create( + const std::string& name, time_t start) = 0; + virtual void created(Event* event) = 0; + + virtual std::vector<std::unique_ptr<Event>> all() = 0; + virtual std::unique_ptr<Event> next() = 0; + + virtual bool good() const = 0; + + virtual void cancel(Event* event, size_t index) = 0; + + virtual void updated(Event* event, int64_t was_first) = 0; + + virtual void going(Event* event, bool going, const std::string& user, + const std::string& owner) = 0; + + virtual const std::string& channel() const = 0; + + static std::unique_ptr<EventUtils> create( + const std::string& channel, + std::function<void(const std::string&)> error_cb, + Config* config, + SenderClient* sender); + + static const double ONE_DAY_IN_SEC; + static const double ONE_WEEK_IN_SEC; + static const double ONE_YEAR_IN_SEC; + + static std::string format_date(time_t date); + +protected: + EventUtils(); + +private: + EventUtils(const EventUtils&) = delete; + EventUtils& operator=(const EventUtils&) = delete; +}; + +} // namespace stuff + +#endif /* EVENT_UTILS_HH */ |
