1 #include <algorithm>
2 #include <boost/algorithm/string/replace.hpp>
3 #include <boost/bind.hpp>
4 #include <deque>
5 #include <iostream>
6
7 #if !defined(_WINDOWS)
8 #include <sys/time.h>
9 #endif
10
11 #include "asserts.hpp"
12 #include "base64.hpp"
13 #include "filesystem.hpp"
14 #include "foreach.hpp"
15 #include "formatter.hpp"
16 #include "json_parser.hpp"
17 #include "md5.hpp"
18 #include "module_web_server.hpp"
19 #include "string_utils.hpp"
20 #include "utils.hpp"
21 #include "unit_test.hpp"
22 #include "variant.hpp"
23
24 using boost::asio::ip::tcp;
25
module_web_server(const std::string & data_path,boost::asio::io_service & io_service,int port)26 module_web_server::module_web_server(const std::string& data_path, boost::asio::io_service& io_service, int port)
27 : http::web_server(io_service, port), timer_(io_service),
28 nheartbeat_(0), data_path_(data_path)
29 {
30 if(data_path_.empty() || data_path_[data_path_.size()-1] != '/') {
31 data_path_ += "/";
32 }
33
34 if(sys::file_exists(data_file_path())) {
35 data_ = json::parse_from_file(data_file_path());
36 } else {
37 std::map<variant, variant> m;
38 data_ = variant(&m);
39 }
40
41 heartbeat();
42 }
43
heartbeat()44 void module_web_server::heartbeat()
45 {
46 timer_.expires_from_now(boost::posix_time::seconds(1));
47 timer_.async_wait(boost::bind(&module_web_server::heartbeat, this));
48 }
49
handle_post(socket_ptr socket,variant doc,const http::environment & env)50 void module_web_server::handle_post(socket_ptr socket, variant doc, const http::environment& env)
51 {
52 std::map<variant,variant> response;
53 try {
54 const std::string msg_type = doc["type"].as_string();
55 if(msg_type == "upload_module") {
56 variant module_node = doc["module"];
57 const std::string module_id = module_node["id"].as_string();
58 ASSERT_LOG(std::count_if(module_id.begin(), module_id.end(), isalnum) + std::count(module_id.begin(), module_id.end(), '_') == module_id.size(), "ILLEGAL MODULE ID");
59
60 std::vector<variant> prev_versions;
61
62 variant current_data = data_[variant(module_id)];
63 if(current_data.is_null() == false) {
64 const variant new_version = module_node[variant("version")];
65 const variant old_version = current_data[variant("version")];
66 ASSERT_LOG(new_version > old_version, "VERSION " << new_version.write_json() << " IS NOT NEWER THAN EXISTING VERSION " << old_version.write_json());
67 prev_versions = current_data[variant("previous_versions")].as_list();
68 current_data.remove_attr_mutation(variant("previous_versions"));
69 prev_versions.push_back(current_data);
70 }
71
72 const std::string module_path = data_path_ + module_id + ".cfg";
73 const std::string module_path_tmp = module_path + ".tmp";
74 const std::string contents = module_node.write_json();
75
76 sys::write_file(module_path_tmp, contents);
77 const int rename_result = rename(module_path_tmp.c_str(), module_path.c_str());
78 ASSERT_LOG(rename_result == 0, "FAILED TO RENAME FILE: " << errno);
79
80 response[variant("status")] = variant("ok");
81
82 {
83 std::map<variant, variant> summary;
84 summary[variant("previous_versions")] = variant(&prev_versions);
85 summary[variant("version")] = module_node[variant("version")];
86 summary[variant("name")] = module_node[variant("name")];
87 summary[variant("description")] = module_node[variant("description")];
88 summary[variant("author")] = module_node[variant("author")];
89 summary[variant("dependencies")] = module_node[variant("dependencies")];
90 summary[variant("num_downloads")] = variant(0);
91 summary[variant("num_ratings")] = variant(0);
92 summary[variant("sum_ratings")] = variant(0);
93
94 std::vector<variant> reviews_list;
95 summary[variant("reviews")] = variant(&reviews_list);
96
97 if(module_node.has_key("icon")) {
98 std::string icon_str = base64::b64decode(module_node["icon"].as_string());
99
100 const std::string hash = md5::sum(icon_str);
101 sys::write_file(data_path_ + "/.glob/" + hash, icon_str);
102
103 summary[variant("icon")] = variant(hash);
104 }
105
106 data_.add_attr_mutation(variant(module_id), variant(&summary));
107 write_data();
108 }
109
110 } else if(msg_type == "query_globs") {
111 response[variant("status")] = variant("ok");
112 foreach(const std::string& k, doc["keys"].as_list_string()) {
113 const std::string data = sys::read_file(data_path_ + "/.glob/" + k);
114 response[variant(k)] = variant(base64::b64encode(data));
115 }
116 } else if(msg_type == "rate") {
117 const std::string module_id = doc["module_id"].as_string();
118 variant summary = data_[module_id];
119 ASSERT_LOG(summary.is_map(), "UNKNOWN MODULE ID: " << module_id);
120
121 const int rating = doc["rating"].as_int();
122 ASSERT_LOG(rating >= 1 && rating <= 5, "ILLEGAL RATING");
123 summary.add_attr_mutation(variant("num_ratings"), variant(summary["num_ratings"].as_int() + 1));
124 summary.add_attr_mutation(variant("sum_ratings"), variant(summary["sum_ratings"].as_int() + rating));
125
126 if(doc["review"].is_null() == false) {
127 std::vector<variant> v = summary["reviews"].as_list();
128 v.push_back(doc);
129 summary.add_attr_mutation(variant("reviews"), variant(&v));
130 }
131
132 response[variant("status")] = variant("ok");
133 } else {
134 ASSERT_LOG(false, "Unknown message type");
135 }
136 } catch(validation_failure_exception& e) {
137 response[variant("status")] = variant("error");
138 response[variant("message")] = variant(e.msg);
139 }
140
141 send_msg(socket, "text/json", variant(&response).write_json(), "");
142 }
143
handle_get(socket_ptr socket,const std::string & url,const std::map<std::string,std::string> & args)144 void module_web_server::handle_get(socket_ptr socket, const std::string& url, const std::map<std::string, std::string>& args)
145 {
146 std::map<variant,variant> response;
147 try {
148 std::cerr << "URL: (" << url << ")\n";
149 response[variant("status")] = variant("error");
150 if(url == "/download_module" && args.count("module_id")) {
151 const std::string module_id = args.find("module_id")->second;
152 const std::string module_path = data_path_ + module_id + ".cfg";
153 if(sys::file_exists(module_path)) {
154 std::string response = "{\nstatus: \"ok\",\nmodule: ";
155 {
156 const std::string contents = sys::read_file(module_path);
157 response += contents;
158 }
159
160 response += "\n}";
161 send_msg(socket, "text/json", response, "");
162
163 variant summary = data_[module_id];
164 if(summary.is_map()) {
165 summary.add_attr_mutation(variant("num_downloads"), variant(summary["num_downloads"].as_int() + 1));
166 }
167 return;
168
169 } else {
170 response[variant("message")] = variant("No such module");
171 }
172 } else if(url == "/get_summary") {
173 response[variant("status")] = variant("ok");
174 response[variant("summary")] = data_;
175 } else {
176 response[variant("message")] = variant("Unknown path");
177 }
178 } catch(validation_failure_exception& e) {
179 response[variant("status")] = variant("error");
180 response[variant("message")] = variant(e.msg);
181 }
182
183 send_msg(socket, "text/json", variant(&response).write_json(), "");
184 }
185
data_file_path() const186 std::string module_web_server::data_file_path() const
187 {
188 return data_path_ + "/module-data.json";
189 }
190
write_data()191 void module_web_server::write_data()
192 {
193 const std::string tmp_path = data_file_path() + ".tmp";
194 sys::write_file(tmp_path, data_.write_json());
195 const int rename_result = rename(tmp_path.c_str(), data_file_path().c_str());
196 ASSERT_LOG(rename_result == 0, "FAILED TO RENAME FILE: " << errno);
197 }
198
COMMAND_LINE_UTILITY(module_server)199 COMMAND_LINE_UTILITY(module_server)
200 {
201 std::string path = ".";
202 int port = 23456;
203
204 std::deque<std::string> arguments(args.begin(), args.end());
205 while(!arguments.empty()) {
206 const std::string arg = arguments.front();
207 arguments.pop_front();
208 if(arg == "--path") {
209 ASSERT_LOG(arguments.empty() == false, "NEED ARGUMENT AFTER " << arg);
210 path = arguments.front();
211 arguments.pop_front();
212 } else if(arg == "-p" || arg == "--port") {
213 ASSERT_LOG(arguments.empty() == false, "NEED ARGUMENT AFTER " << arg);
214 port = atoi(arguments.front().c_str());
215 arguments.pop_front();
216 } else {
217 ASSERT_LOG(false, "UNRECOGNIZED ARGUMENT: " << arg);
218 }
219 }
220
221 const assert_recover_scope recovery;
222 boost::asio::io_service io_service;
223 module_web_server server(path, io_service, port);
224 io_service.run();
225 }
226