1 // rTorrent - BitTorrent client
2 // Copyright (C) 2005-2011, Jari Sundell
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 //
18 // In addition, as a special exception, the copyright holders give
19 // permission to link the code of portions of this program with the
20 // OpenSSL library under certain conditions as described in each
21 // individual source file, and distribute linked combinations
22 // including the two.
23 //
24 // You must obey the GNU General Public License in all respects for
25 // all of the code used other than OpenSSL.  If you modify file(s)
26 // with this exception, you may extend this exception to your version
27 // of the file(s), but you are not obligated to do so.  If you do not
28 // wish to do so, delete this exception statement from your version.
29 // If you delete this exception statement from all source files in the
30 // program, then also delete it here.
31 //
32 // Contact:  Jari Sundell <jaris@ifi.uio.no>
33 //
34 //           Skomakerveien 33
35 //           3185 Skoppum, NORWAY
36 
37 #include "config.h"
38 
39 #include <torrent/download/resource_manager.h>
40 #include <torrent/download/choke_group.h>
41 #include <torrent/download/choke_queue.h>
42 #include <torrent/utils/option_strings.h>
43 
44 #include "ui/root.h"
45 #include "rpc/parse.h"
46 #include "rpc/parse_commands.h"
47 
48 #include "globals.h"
49 #include "control.h"
50 #include "command_helpers.h"
51 
52 // For cg_d_group.
53 #include "core/download.h"
54 
55 // A hack to allow testing of the new choke_group API without the
56 // working parts present.
57 #define USE_CHOKE_GROUP 0
58 
59 #if USE_CHOKE_GROUP
60 
61 int64_t
cg_get_index(const torrent::Object & raw_args)62 cg_get_index(const torrent::Object& raw_args) {
63   const torrent::Object& arg = (raw_args.is_list() && !raw_args.as_list().empty()) ? raw_args.as_list().front() : raw_args;
64 
65   int64_t index = 0;
66 
67   if (arg.is_string()) {
68     if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index))
69       return torrent::resource_manager()->group_index_of(arg.as_string());
70 
71   } else {
72     index = arg.as_value();
73   }
74 
75   if (index < 0)
76     index = (int64_t)torrent::resource_manager()->group_size() + index;
77 
78   return std::min<uint64_t>(index, torrent::resource_manager()->group_size());
79 }
80 
81 torrent::choke_group*
cg_get_group(const torrent::Object & raw_args)82 cg_get_group(const torrent::Object& raw_args) {
83   return torrent::resource_manager()->group_at(cg_get_index(raw_args));
84 }
85 
86 int64_t
cg_d_group(core::Download * download)87 cg_d_group(core::Download* download) {
88   return torrent::resource_manager()->entry_at(download->main()).group();
89 }
90 
91 const std::string&
cg_d_group_name(core::Download * download)92 cg_d_group_name(core::Download* download) {
93   return torrent::resource_manager()->group_at(torrent::resource_manager()->entry_at(download->main()).group())->name();
94 }
95 
96 void
cg_d_group_set(core::Download * download,const torrent::Object & arg)97 cg_d_group_set(core::Download* download, const torrent::Object& arg) {
98   torrent::resource_manager()->set_group(torrent::resource_manager()->find_throw(download->main()), cg_get_index(arg));
99 }
100 
101 torrent::Object
apply_cg_list()102 apply_cg_list() {
103   torrent::Object::list_type result;
104 
105   for (torrent::ResourceManager::group_iterator
106          itr = torrent::resource_manager()->group_begin(),
107          last = torrent::resource_manager()->group_end(); itr != last; itr++)
108     result.push_back((*itr)->name());
109 
110   return torrent::Object::from_list(result);
111 }
112 
113 torrent::Object
apply_cg_insert(const std::string & arg)114 apply_cg_insert(const std::string& arg) {
115   int64_t dummy;
116 
117   if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy))
118     throw torrent::input_error("Cannot use a value string as choke group name.");
119 
120   torrent::resource_manager()->push_group(arg);
121 
122   return torrent::Object();
123 }
124 
125 //
126 // The hacked version:
127 //
128 #else
129 
130 std::vector<torrent::choke_group*> cg_list_hack;
131 
132 int64_t
cg_get_index(const torrent::Object & raw_args)133 cg_get_index(const torrent::Object& raw_args) {
134   const torrent::Object& arg = (raw_args.is_list() && !raw_args.as_list().empty()) ? raw_args.as_list().front() : raw_args;
135 
136   int64_t index = 0;
137 
138   if (arg.is_string()) {
139     if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index)) {
140       std::vector<torrent::choke_group*>::iterator itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(),
141                                                                       rak::equal(arg.as_string(), std::mem_fun(&torrent::choke_group::name)));
142 
143       if (itr == cg_list_hack.end())
144         throw torrent::input_error("Choke group not found.");
145 
146       return std::distance(cg_list_hack.begin(), itr);
147     }
148 
149   } else {
150     index = arg.as_value();
151   }
152 
153   if (index < 0)
154     index = (int64_t)cg_list_hack.size() + index;
155 
156   if ((size_t)index >= cg_list_hack.size())
157     throw torrent::input_error("Choke group not found.");
158 
159   return index;
160 }
161 
162 torrent::choke_group*
cg_get_group(const torrent::Object & raw_args)163 cg_get_group(const torrent::Object& raw_args) {
164   int64_t index = cg_get_index(raw_args);
165 
166   if ((size_t)index >= cg_list_hack.size())
167     throw torrent::input_error("Choke group not found.");
168 
169   return cg_list_hack.at(index);
170 }
171 
cg_d_group(core::Download * download)172 int64_t cg_d_group(core::Download* download) { return download->group(); }
cg_d_group_set(core::Download * download,const torrent::Object & arg)173 void    cg_d_group_set(core::Download* download, const torrent::Object& arg) { download->set_group(cg_get_index(arg)); }
174 
175 torrent::Object
apply_cg_list()176 apply_cg_list() {
177   torrent::Object::list_type result;
178 
179   for (std::vector<torrent::choke_group*>::iterator itr = cg_list_hack.begin(), last = cg_list_hack.end(); itr != last; itr++)
180     result.push_back((*itr)->name());
181 
182   return torrent::Object::from_list(result);
183 }
184 
185 torrent::Object
apply_cg_insert(const std::string & arg)186 apply_cg_insert(const std::string& arg) {
187   int64_t dummy;
188 
189   if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy))
190     throw torrent::input_error("Cannot use a value string as choke group name.");
191 
192   if (arg.empty() ||
193       std::find_if(cg_list_hack.begin(), cg_list_hack.end(),
194                    rak::equal(arg, std::mem_fun(&torrent::choke_group::name))) != cg_list_hack.end())
195     throw torrent::input_error("Duplicate name for choke group.");
196 
197   cg_list_hack.push_back(new torrent::choke_group());
198   cg_list_hack.back()->set_name(arg);
199 
200   cg_list_hack.back()->up_queue()->set_heuristics(torrent::choke_queue::HEURISTICS_UPLOAD_LEECH);
201   cg_list_hack.back()->down_queue()->set_heuristics(torrent::choke_queue::HEURISTICS_DOWNLOAD_LEECH);
202 
203   return torrent::Object();
204 }
205 
206 torrent::Object
apply_cg_index_of(const std::string & arg)207 apply_cg_index_of(const std::string& arg) {
208   std::vector<torrent::choke_group*>::iterator itr =
209     std::find_if(cg_list_hack.begin(), cg_list_hack.end(), rak::equal(arg, std::mem_fun(&torrent::choke_group::name)));
210 
211   if (itr == cg_list_hack.end())
212     throw torrent::input_error("Choke group not found.");
213 
214   return std::distance(cg_list_hack.begin(), itr);
215 }
216 
217 //
218 // End of choke group hack.
219 //
220 #endif
221 
222 
223 torrent::Object
apply_cg_max_set(const torrent::Object::list_type & args,bool is_up)224 apply_cg_max_set(const torrent::Object::list_type& args, bool is_up) {
225   if (args.size() != 2)
226     throw torrent::input_error("Incorrect number of arguments.");
227 
228   int64_t second_arg = 0;
229   rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg);
230 
231   if (is_up)
232     cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg);
233   else
234     cg_get_group(args.front())->down_queue()->set_max_unchoked(second_arg);
235 
236   return torrent::Object();
237 }
238 
239 torrent::Object
apply_cg_heuristics_set(const torrent::Object::list_type & args,bool is_up)240 apply_cg_heuristics_set(const torrent::Object::list_type& args, bool is_up) {
241   if (args.size() != 2)
242     throw torrent::input_error("Incorrect number of arguments.");
243 
244   int t = torrent::option_find_string(is_up ? torrent::OPTION_CHOKE_HEURISTICS_UPLOAD : torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD,
245                                       args.back().as_string().c_str());
246 
247   if (is_up)
248     cg_get_group(args.front())->up_queue()->set_heuristics((torrent::choke_queue::heuristics_enum)t);
249   else
250     cg_get_group(args.front())->down_queue()->set_heuristics((torrent::choke_queue::heuristics_enum)t);
251 
252   return torrent::Object();
253 }
254 
255 torrent::Object
apply_cg_tracker_mode_set(const torrent::Object::list_type & args)256 apply_cg_tracker_mode_set(const torrent::Object::list_type& args) {
257   if (args.size() != 2)
258     throw torrent::input_error("Incorrect number of arguments.");
259 
260   int t = torrent::option_find_string(torrent::OPTION_TRACKER_MODE, args.back().as_string().c_str());
261 
262   cg_get_group(args.front())->set_tracker_mode((torrent::choke_group::tracker_mode_enum)t);
263 
264   return torrent::Object();
265 }
266 
267 #define CG_GROUP_AT()          std::bind(&cg_get_group, std::placeholders::_2)
268 #define CHOKE_GROUP(direction) std::bind(direction, CG_GROUP_AT())
269 
270 /*
271 
272 <cg_index> -> '0'..'(choke_group.size)'
273            -> '-1'..'-(choke_group.size)'
274            -> '<group_name>'
275 
276 (choke_group.list) -> List of group names.
277 (choke_group.size) -> Number of groups.
278 
279 (choke_group.insert,"group_name")
280 
281 Adds a new group with default settings, use index '-1' to accessing it
282 immediately afterwards.
283 
284 (choke_group.index_of,"group_name") -> <group_index>
285 
286 Throws if the group name was not found.
287 
288 (choke_group.general.size,<cg_index>) -> <size>
289 
290 Number of torrents in this group.
291 
292 (choke_group.tracker.mode,<cg_index>) -> "tracker_mode"
293 (choke_group.tracker.mode.set,<cg_index>,"tracker_mode")
294 
295 Decide on how aggressive a tracker should be, see
296 'strings.tracker_mode' for list of available options
297 
298 (choke_group.up.rate,<cg_index>) -> <bytes/second>
299 (choke_group.down.rate,<cg_index>) -> <bytes/second>
300 
301 Upload / download rate for the aggregate of all torrents in this
302 particular group.
303 
304 (choke_group.up.max,<cg_index>) -> <max_upload_slots>
305 (choke_group.up.max.unlimited,<cg_index>) -> <max_upload_slots>
306 (choke_group.up.max.set,<cg_index>, <max_upload_slots>)
307 (choke_group.down.max,<cg_index>) -> <max_download_slots>
308 (choke_group.down.max.unlimited,<cg_index>) -> <max_download_slots>
309 (choke_group.down.max.set,<cg_index>, <max_download_slots)
310 
311 Number of unchoked upload / download peers regulated on a group basis.
312 
313 (choke_group.up.total,<cg_index>) -> <number of queued and unchoked interested peers>
314 (choke_group.up.queued,<cg_index>) -> <number of queued interested peers>
315 (choke_group.up.unchoked,<cg_index>) -> <number of unchoked uploads>
316 (choke_group.down.total,<cg_index>) -> <number of queued and unchoked interested peers>
317 (choke_group.down.queued,<cg_index>) -> <number of queued interested peers>
318 (choke_group.down.unchoked,<cg_index>) -> <number of unchoked uploads>
319 
320 (choke_group.up.heuristics,<cg_index>) -> "heuristics"
321 (choke_group.up.heuristics.set,<cg_index>,"heuristics")
322 (choke_group.down.heuristics,<cg_index>) -> "heuristics"
323 (choke_group.down.heuristics.set,<cg_index>,"heuristics")
324 
325 Heuristics are used for deciding what peers to choke and unchoke, see
326 'strings.choke_heuristics{,_download,_upload}' for a list of available
327 options.
328 
329 (d.group) -> <choke_group_index>
330 (d.group.name) -> "choke_group_name"
331 (d.group.set,<cg_index>)
332 
333  */
334 
335 
336 void
initialize_command_groups()337 initialize_command_groups() {
338   CMD2_ANY         ("choke_group.list",                std::bind(&apply_cg_list));
339   CMD2_ANY_STRING  ("choke_group.insert",              std::bind(&apply_cg_insert, std::placeholders::_2));
340 
341 #if USE_CHOKE_GROUP
342   CMD2_ANY         ("choke_group.size",                std::bind(&torrent::ResourceManager::group_size, torrent::resource_manager()));
343   CMD2_ANY_STRING  ("choke_group.index_of",            std::bind(&torrent::ResourceManager::group_index_of, torrent::resource_manager(), std::placeholders::_2));
344 #else
345   apply_cg_insert("default");
346 
347   CMD2_ANY         ("choke_group.size",                std::bind(&std::vector<torrent::choke_group*>::size, cg_list_hack));
348   CMD2_ANY_STRING  ("choke_group.index_of",            std::bind(&apply_cg_index_of, std::placeholders::_2));
349 #endif
350 
351   // Commands specific for a group. Supports as the first argument the
352   // name, the index or a negative index.
353   CMD2_ANY         ("choke_group.general.size",        std::bind(&torrent::choke_group::size, CG_GROUP_AT()));
354 
355   CMD2_ANY         ("choke_group.tracker.mode",        std::bind(&torrent::option_as_string, torrent::OPTION_TRACKER_MODE,
356                                                                  std::bind(&torrent::choke_group::tracker_mode, CG_GROUP_AT())));
357   CMD2_ANY_LIST    ("choke_group.tracker.mode.set",    std::bind(&apply_cg_tracker_mode_set, std::placeholders::_2));
358 
359   CMD2_ANY         ("choke_group.up.rate",             std::bind(&torrent::choke_group::up_rate, CG_GROUP_AT()));
360   CMD2_ANY         ("choke_group.down.rate",           std::bind(&torrent::choke_group::down_rate, CG_GROUP_AT()));
361 
362   CMD2_ANY         ("choke_group.up.max.unlimited",    std::bind(&torrent::choke_queue::is_unlimited, CHOKE_GROUP(&torrent::choke_group::up_queue)));
363   CMD2_ANY         ("choke_group.up.max",              std::bind(&torrent::choke_queue::max_unchoked_signed, CHOKE_GROUP(&torrent::choke_group::up_queue)));
364   CMD2_ANY_LIST    ("choke_group.up.max.set",          std::bind(&apply_cg_max_set, std::placeholders::_2, true));
365 
366   CMD2_ANY         ("choke_group.up.total",            std::bind(&torrent::choke_queue::size_total, CHOKE_GROUP(&torrent::choke_group::up_queue)));
367   CMD2_ANY         ("choke_group.up.queued",           std::bind(&torrent::choke_queue::size_queued, CHOKE_GROUP(&torrent::choke_group::up_queue)));
368   CMD2_ANY         ("choke_group.up.unchoked",         std::bind(&torrent::choke_queue::size_unchoked, CHOKE_GROUP(&torrent::choke_group::up_queue)));
369   CMD2_ANY         ("choke_group.up.heuristics",       std::bind(&torrent::option_as_string, torrent::OPTION_CHOKE_HEURISTICS,
370                                                                  std::bind(&torrent::choke_queue::heuristics, CHOKE_GROUP(&torrent::choke_group::up_queue))));
371   CMD2_ANY_LIST    ("choke_group.up.heuristics.set",   std::bind(&apply_cg_heuristics_set, std::placeholders::_2, true));
372 
373   CMD2_ANY         ("choke_group.down.max.unlimited",  std::bind(&torrent::choke_queue::is_unlimited, CHOKE_GROUP(&torrent::choke_group::down_queue)));
374   CMD2_ANY         ("choke_group.down.max",            std::bind(&torrent::choke_queue::max_unchoked_signed, CHOKE_GROUP(&torrent::choke_group::down_queue)));
375   CMD2_ANY_LIST    ("choke_group.down.max.set",        std::bind(&apply_cg_max_set, std::placeholders::_2, false));
376 
377   CMD2_ANY         ("choke_group.down.total",          std::bind(&torrent::choke_queue::size_total, CHOKE_GROUP(&torrent::choke_group::down_queue)));
378   CMD2_ANY         ("choke_group.down.queued",         std::bind(&torrent::choke_queue::size_queued, CHOKE_GROUP(&torrent::choke_group::down_queue)));
379   CMD2_ANY         ("choke_group.down.unchoked",       std::bind(&torrent::choke_queue::size_unchoked, CHOKE_GROUP(&torrent::choke_group::down_queue)));
380   CMD2_ANY         ("choke_group.down.heuristics",     std::bind(&torrent::option_as_string, torrent::OPTION_CHOKE_HEURISTICS,
381                                                                  std::bind(&torrent::choke_queue::heuristics, CHOKE_GROUP(&torrent::choke_group::down_queue))));
382   CMD2_ANY_LIST    ("choke_group.down.heuristics.set", std::bind(&apply_cg_heuristics_set, std::placeholders::_2, false));
383 }
384