summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2015-06-03 22:05:58 +0200
committerJoel Klinghed <the_jk@yahoo.com>2015-06-03 22:05:58 +0200
commitc9c45371e8eb4f500593a089a6087a26649e0456 (patch)
tree36da0512b1576ce2330207daef9fae300bd3ba40
parent41c45d4a4d6f8bea9b45d76ddef5d8bac71cfc5a (diff)
Improved
-rw-r--r--src/event.cc141
-rw-r--r--src/event.hh21
-rw-r--r--src/event_main.cc233
3 files changed, 303 insertions, 92 deletions
diff --git a/src/event.cc b/src/event.cc
index 8606819..02343c9 100644
--- a/src/event.cc
+++ b/src/event.cc
@@ -15,6 +15,10 @@ public:
~EventImpl() override {
}
+ int64_t id() const override {
+ return id_;
+ }
+
const std::string& name() const override {
return name_;
}
@@ -45,44 +49,41 @@ public:
start_ = start;
}
- void going(std::set<std::string>* going,
- std::set<std::string>* not_going) const override {
- if (going) {
- going->clear();
- going->insert(going_.begin(), going_.end());
- }
- if (not_going) {
- not_going->clear();
- not_going->insert(not_going_.begin(), not_going_.end());
- }
+ void going(std::vector<Going>* going) const override {
+ going->assign(going_.begin(), going_.end());
}
bool is_going(const std::string& name) const override {
- return going_.count(name) > 0;
+ for (const auto& going : going_) {
+ if (going.name == name) {
+ return going.is_going;
+ }
+ }
+ return false;
}
- void update_going(const std::string& name, bool going) override {
- auto it = going_.find(name);
- if (it != going_.end()) {
- if (going) return;
- going_.erase(it);
- not_going_.insert(name);
- going_uptodate_ = false;
- } else {
- it = not_going_.find(name);
- if (it != not_going_.end()) {
- if (!going) return;
- going_.insert(name);
- not_going_.erase(it);
- going_uptodate_ = false;
- } else {
- if (going) {
- going_.insert(name);
- } else {
- not_going_.insert(name);
+ void update_going(const std::string& name, bool is_going,
+ const std::string& note) override {
+ for (auto it = going_.begin(); it != going_.end(); ++it) {
+ if (it->name == name) {
+ if (it->is_going == is_going && it->note == note) {
+ return;
+ }
+ going_.erase(it);
+ break;
+ }
+ }
+ std::vector<Going>::iterator it;
+ if (is_going) {
+ for (it = going_.begin(); it != going_.end(); ++it) {
+ if (!it->is_going) {
+ break;
}
}
+ } else {
+ it = going_.end();
}
+ going_.emplace(it, name, is_going, note, time(NULL));
going_uptodate_ = false;
}
@@ -110,11 +111,10 @@ public:
bool remove() override {
if (new_) return true;
DB::Transaction transaction(db_);
- if (!db_->remove(kEventTable,
- DB::Condition("id", DB::Condition::EQUAL, id_)))
- return false;
- if (!db_->remove(kEventGoingTable,
- DB::Condition("event", DB::Condition::EQUAL, id_)))
+ if (db_->remove(kEventTable,
+ DB::Condition("id", DB::Condition::EQUAL, id_)) < 0 ||
+ db_->remove(kEventGoingTable,
+ DB::Condition("event", DB::Condition::EQUAL, id_)) < 0)
return false;
if (transaction.commit()) {
new_ = true;
@@ -156,52 +156,55 @@ private:
}
}
- bool store_going(const std::set<std::string>& names, bool going) const {
- for (const auto& name : names) {
+ bool store_going() {
+ if (going_uptodate_) return true;
+ DB::Transaction transaction(db_);
+ if (db_->remove(kEventGoingTable,
+ DB::Condition("event", DB::Condition::EQUAL, id_)) < 0)
+ return false;
+ for (const auto& going : going_) {
auto editor = db_->insert(kEventGoingTable);
editor->set("event", id_);
- editor->set("name", name);
- editor->set("going", going);
+ editor->set("name", going.name);
+ editor->set("is_going", going.is_going);
+ editor->set("note", going.note);
+ editor->set("added", static_cast<int64_t>(going.added));
if (!editor->commit()) {
return false;
}
}
- return true;
- }
-
- bool store_going() {
- if (going_uptodate_) return true;
- DB::Transaction transaction(db_);
- if (!db_->remove(kEventGoingTable,
- DB::Condition("event", DB::Condition::EQUAL, id_)))
- return false;
- if (!store_going(going_, true) || !store_going(not_going_, false))
- return false;
if (transaction.commit()) {
going_uptodate_ = true;
return true;
}
return false;
}
+
bool load_going() {
going_.clear();
- not_going_.clear();
going_uptodate_ = true;
+ std::vector<DB::OrderBy> order_by;
+ order_by.push_back(DB::OrderBy("is_going", false));
+ order_by.push_back(DB::OrderBy("added"));
+ order_by.push_back(DB::OrderBy("name"));
auto snapshot = db_->select(kEventGoingTable,
DB::Condition("event",
DB::Condition::EQUAL,
- id_));
+ id_),
+ order_by);
if (!snapshot) return true;
do {
- bool is_going;
std::string name;
- if (!snapshot->get(0, &name) || !snapshot->get(1, &is_going))
+ bool is_going;
+ std::string note;
+ int64_t added;
+ if (!snapshot->get(0, &name) || !snapshot->get(1, &is_going) ||
+ !snapshot->get(3, &added))
return false;
- if (is_going) {
- going_.insert(name);
- } else {
- not_going_.insert(name);
- }
+ if (!snapshot->get(2, &note))
+ note = "";
+ going_.emplace_back(name, is_going, note,
+ static_cast<time_t>(added));
} while (snapshot->next());
return !snapshot->bad();
}
@@ -214,10 +217,16 @@ private:
time_t start_;
bool new_;
bool going_uptodate_;
- std::set<std::string> going_;
- std::set<std::string> not_going_;
+ std::vector<Going> going_;
};
+std::shared_ptr<DB::Snapshot> open(std::shared_ptr<DB> db) {
+ std::vector<DB::OrderBy> order_by;
+ order_by.push_back(DB::OrderBy("start"));
+ order_by.push_back(DB::OrderBy("name"));
+ return db->select(kEventTable, DB::Condition(), order_by);
+}
+
} // namespace
// static
@@ -231,14 +240,15 @@ bool Event::setup(DB* db) {
decl.clear();
decl.push_back(std::make_pair("event", DB::NotNull(DB::Type::INT64)));
decl.push_back(std::make_pair("name", DB::NotNull(DB::Type::STRING)));
- decl.push_back(std::make_pair("going", DB::NotNull(DB::Type::BOOL)));
+ decl.push_back(std::make_pair("is_going", DB::NotNull(DB::Type::BOOL)));
+ decl.push_back(std::make_pair("note", DB::Type::STRING));
+ decl.push_back(std::make_pair("added", DB::NotNull(DB::Type::INT64)));
return db->insert_table(kEventGoingTable, decl);
}
// static
std::unique_ptr<Event> Event::next(std::shared_ptr<DB> db) {
- auto snapshot =
- db->select(kEventTable, DB::Condition(), DB::OrderBy("start"));
+ auto snapshot = open(db);
if (snapshot) {
do {
auto ev = new EventImpl(db);
@@ -253,8 +263,7 @@ std::unique_ptr<Event> Event::next(std::shared_ptr<DB> db) {
// static
std::vector<std::unique_ptr<Event>> Event::all(std::shared_ptr<DB> db) {
- auto snapshot =
- db->select(kEventTable, DB::Condition(), DB::OrderBy("start"));
+ auto snapshot = open(db);
std::vector<std::unique_ptr<Event>> ret;
if (snapshot) {
do {
diff --git a/src/event.hh b/src/event.hh
index 425a70f..8919429 100644
--- a/src/event.hh
+++ b/src/event.hh
@@ -2,7 +2,6 @@
#define EVENT_HH
#include <memory>
-#include <set>
#include <string>
#include <vector>
@@ -12,8 +11,22 @@ class DB;
class Event {
public:
+ struct Going {
+ std::string name;
+ bool is_going;
+ std::string note;
+ time_t added;
+
+ Going(const std::string& name, bool is_going, const std::string& note,
+ time_t added)
+ : name(name), is_going(is_going), note(note), added(added) {
+ }
+ };
+
virtual ~Event() {}
+ virtual int64_t id() const = 0;
+
virtual const std::string& name() const = 0;
virtual void set_name(const std::string& name) = 0;
@@ -23,11 +36,11 @@ public:
virtual time_t start() const = 0;
virtual void set_start(time_t start) = 0;
- virtual void going(std::set<std::string>* going,
- std::set<std::string>* not_going) const = 0;
+ virtual void going(std::vector<Going>* going) const = 0;
virtual bool is_going(const std::string& name) const = 0;
- virtual void update_going(const std::string& name, bool going) = 0;
+ virtual void update_going(const std::string& name, bool is_going,
+ const std::string& note = std::string()) = 0;
virtual bool store() = 0;
diff --git a/src/event_main.cc b/src/event_main.cc
index 99b7d1a..afde933 100644
--- a/src/event_main.cc
+++ b/src/event_main.cc
@@ -42,9 +42,13 @@ std::shared_ptr<DB> open(const std::string& channel) {
*it = '.';
}
}
- auto db = SQLite3::open(DB_PATH + tmp + ".db");
- if (!db) {
+ auto db = SQLite3::open(DB_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);
}
@@ -55,11 +59,150 @@ bool parse(const std::string& text, std::vector<std::string>* args) {
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& str) {
+
+}
+
+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 /going to join the event" << std::endl;
+ signal_channel(ss.str());
+}
+
+bool parse_time(const std::string& value, time_t* date) {
+ struct tm _t, _tmp;
+ time_t now = time(NULL);
+ localtime_r(&now, &_t);
+ _tmp = _t;
+ auto ptr = strptime(value.c_str(), "%H:%M", &_tmp);
+ if (ptr && !*ptr) goto done;
+ _tmp = _t;
+ ptr = strptime(value.c_str(), "%A %H:%M", &_tmp);
+ if (ptr && !*ptr) {
+ // Given the weekday, figure out distance to "now"
+ int days = _tmp.tm_wday - _t.tm_wday;
+ if (days <= 0) {
+ days += 7;
+ }
+ time_t tmp = mktime(&_tmp) + days * ONE_DAY_IN_SEC;
+ localtime_r(&tmp, &_tmp);
+ goto done;
+ }
+ _tmp = _t;
+ ptr = strptime(value.c_str(), "%d/%m %H:%M", &_tmp);
+ if (ptr && !*ptr) goto done;
+ _tmp = _t;
+ ptr = strptime(value.c_str(), "%A %d/%m %H:%M", &_tmp);
+ if (ptr && !*ptr) goto done;
+ _tmp = _t;
+ ptr = strptime(value.c_str(), "%Y-%m-%d %H:%M", &_tmp);
+ if (ptr && !*ptr) goto done;
+ return false;
+ done:
+ *date = mktime(&_tmp);
+ return true;
+}
+
bool create(const std::string& channel,
std::map<std::string, std::string>& data) {
std::vector<std::string> args;
- if (!parse(data["text"], &args) || args.empty()) return true;
-
+ if (!parse(data["text"], &args)) return true;
+ if (args.size() < 2) {
+ Http::response(200, "Usage: /create NAME START [TEXT]");
+ return true;
+ }
+ std::string name, text;
+ time_t start;
+ name = args.front();
+ args.erase(args.begin());
+ text = args.front();
+ args.erase(args.begin());
+ while (true) {
+ if (parse_time(text, &start)) break;
+ if (args.empty()) {
+ Http::response(200, "Couldn't figure out when to start the event, try [DAY|DATE] HH:MM");
+ return true;
+ }
+ text.push_back(' ');
+ text.append(args.front());
+ args.erase(args.begin());
+ }
+ auto db = open(channel);
+ if (!db) return true;
+ auto event = Event::create(db, name, start);
+ if (!event) {
+ Http::response(200, "Unable to create event");
+ return true;
+ }
+ if (!args.empty()) {
+ std::string text(args.front());
+ args.erase(args.begin());
+ for (const auto& arg : args) {
+ text.push_back(' ');
+ text.append(arg);
+ }
+ event->set_text(text);
+ }
+ if (!event->store()) {
+ Http::response(200, "Unable to store event");
+ return true;
+ }
+ auto next_event = Event::next(db);
+ if (next_event->id() == event->id()) {
+ signal_event(next_event);
+ }
+ Http::response(200, "Event created");
+ return true;
+}
+
+template<typename Iterator>
+bool append_indexes(Iterator begin, Iterator end,
+ std::vector<unsigned long>* out) {
+ for (auto it = begin; it != end; ++it) {
+ try {
+ size_t end;
+ auto tmp = std::stoul(*it, &end);
+ if (end != it->size()) {
+ Http::response(200, "Bad index: " + *it);
+ return false;
+ }
+ out->push_back(tmp);
+ } catch (std::invalid_argument& e) {
+ Http::response(200, "Bad index: " + *it);
+ return false;
+ }
+ }
return true;
}
@@ -71,19 +214,8 @@ bool cancel(const std::string& channel,
if (args.empty()) {
indexes.push_back(0);
} else {
- for (const auto& arg : args) {
- try {
- size_t end;
- auto tmp = std::stoul(arg, &end);
- if (end != arg.size()) {
- Http::response(200, "Bad index: " + arg);
- return true;
- }
- indexes.push_back(tmp);
- } catch (std::invalid_argument& e) {
- Http::response(200, "Bad index: " + arg);
- return true;
- }
+ if (!append_indexes(args.begin(), args.end(), &indexes)) {
+ return true;
}
std::sort(indexes.begin(), indexes.end(),
std::greater<unsigned long>());
@@ -95,6 +227,8 @@ bool cancel(const std::string& channel,
if (indexes.front() >= events.size()) {
if (events.empty()) {
Http::response(200, "There are no events");
+ } else if (events.size() == 1) {
+ Http::response(200, "There is only one event");
} else {
std::ostringstream ss;
ss << "There are only " << events.size() << " events";
@@ -102,9 +236,14 @@ bool cancel(const std::string& channel,
}
return true;
}
- bool signal_channel = false;
+ std::string signal;
for (const auto& index : indexes) {
- if (index == 0) signal_channel = true;
+ if (index == 0) {
+ std::ostringstream ss;
+ ss << "Event canceled: " << events[index]->name() << " @ "
+ << format_date(events[index]->start());
+ signal = ss.str();
+ }
events[index]->remove();
}
if (indexes.size() > 1) {
@@ -112,8 +251,8 @@ bool cancel(const std::string& channel,
} else {
Http::response(200, "Event removed");
}
- if (signal_channel) {
-
+ if (!signal.empty()) {
+ signal_channel(signal);
}
return true;
}
@@ -130,7 +269,57 @@ bool show(const std::string& channel,
std::map<std::string, std::string>& data) {
std::vector<std::string> args;
if (!parse(data["text"], &args)) return true;
-
+ std::vector<unsigned long> indexes;
+ if (args.empty()) {
+ indexes.push_back(0);
+ } else {
+ if (!append_indexes(args.begin(), args.end(), &indexes)) {
+ return true;
+ }
+ }
+ auto db = open(channel);
+ if (!db) return true;
+ auto events = Event::all(db);
+ std::ostringstream ss;
+ for (const auto& index : indexes) {
+ if (indexes.size() > 1) {
+ ss << '(' << index << ") ";
+ }
+ if (index >= events.size()) {
+ if (events.empty()) {
+ ss << "There are no events" << std::endl;
+ } else {
+ ss << "No such event: " << index << std::endl;
+ }
+ } else {
+ ss << events[index]->name() << " @ "
+ << format_date(events[index]->start()) << std::endl;
+ const auto& text = events[index]->text();
+ if (!text.empty()) {
+ ss << text << std::endl;
+ }
+ std::vector<Event::Going> going;
+ events[index]->going(&going);
+ auto it = going.begin();
+ for (; it != going.end(); ++it) {
+ if (!it->is_going) break;
+ ss << it->name;
+ if (!it->note.empty()) {
+ ss << ": " << it->note;
+ }
+ ss << std::endl;
+ }
+ if (it != going.end()) ss << std::endl;
+ for (; it != going.end(); ++it) {
+ ss << it->name << ": not going";
+ if (!it->note.empty()) {
+ ss << ' ' << it->note;
+ }
+ ss << std::endl;
+ }
+ }
+ }
+ Http::response(200, ss.str());
return true;
}