twisterp2pnetworkbittorrentblockchainmicrobloggingipv6social-networkdhtdecentralizedtwister-servertwister-ipv6twister-coretwisterarmyp2p-network
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
674 lines
18 KiB
674 lines
18 KiB
/* |
|
|
|
Copyright (c) 2010-2012, Arvid Norberg |
|
All rights reserved. |
|
|
|
Redistribution and use in source and binary forms, with or without |
|
modification, are permitted provided that the following conditions |
|
are met: |
|
|
|
* Redistributions of source code must retain the above copyright |
|
notice, this list of conditions and the following disclaimer. |
|
* Redistributions in binary form must reproduce the above copyright |
|
notice, this list of conditions and the following disclaimer in |
|
the documentation and/or other materials provided with the distribution. |
|
* Neither the name of the author nor the names of its |
|
contributors may be used to endorse or promote products derived |
|
from this software without specific prior written permission. |
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|
POSSIBILITY OF SUCH DAMAGE. |
|
|
|
*/ |
|
|
|
#include "libtorrent/rss.hpp" |
|
#include "libtorrent/xml_parse.hpp" |
|
#include "libtorrent/http_parser.hpp" |
|
#include "libtorrent/http_connection.hpp" |
|
#include "libtorrent/aux_/session_impl.hpp" |
|
#include "libtorrent/session.hpp" |
|
#include "libtorrent/settings.hpp" |
|
#include "libtorrent/alert_types.hpp" // for rss_alert |
|
|
|
#include <boost/bind.hpp> |
|
#include <set> |
|
#include <map> |
|
#include <algorithm> |
|
|
|
namespace libtorrent { |
|
|
|
feed_item::feed_item(): size(-1) {} |
|
feed_item::~feed_item() {} |
|
|
|
struct feed_state |
|
{ |
|
feed_state(feed& r) |
|
: in_item(false) |
|
, num_items(0) |
|
, type(none) |
|
, ret(r) |
|
{} |
|
|
|
bool in_item; |
|
int num_items; |
|
std::string current_tag; |
|
enum feed_type |
|
{ |
|
none, atom, rss2 |
|
} type; |
|
feed_item current_item; |
|
feed& ret; |
|
|
|
bool is_item(char const* tag) const |
|
{ |
|
switch (type) |
|
{ |
|
case atom: return string_equal_no_case(tag, "entry"); |
|
case rss2: return string_equal_no_case(tag, "item"); |
|
default: return false; |
|
} |
|
} |
|
|
|
bool is_title(char const* tag) const |
|
{ |
|
switch (type) |
|
{ |
|
case atom: |
|
case rss2: return string_equal_no_case(tag, "title"); |
|
default: return false; |
|
} |
|
} |
|
|
|
bool is_url(char const* tag) const |
|
{ |
|
switch (type) |
|
{ |
|
case atom: |
|
case rss2: return string_equal_no_case(tag, "link"); |
|
default: return false; |
|
} |
|
} |
|
|
|
bool is_desc(char const* tag) const |
|
{ |
|
switch (type) |
|
{ |
|
case atom: return string_equal_no_case(tag, "summary"); |
|
case rss2: return string_equal_no_case(tag, "description") |
|
|| string_equal_no_case(tag, "media:text"); |
|
default: return false; |
|
} |
|
} |
|
|
|
bool is_uuid(char const* tag) const |
|
{ |
|
switch (type) |
|
{ |
|
case atom: return string_equal_no_case(tag, "id"); |
|
case rss2: return string_equal_no_case(tag, "guid"); |
|
default: return false; |
|
} |
|
} |
|
|
|
bool is_comment(char const* tag) const |
|
{ |
|
switch (type) |
|
{ |
|
case atom: return false; |
|
case rss2: return string_equal_no_case(tag, "comments"); |
|
default: return false; |
|
} |
|
} |
|
|
|
bool is_category(char const* tag) const |
|
{ |
|
switch (type) |
|
{ |
|
case atom: return false; |
|
case rss2: return string_equal_no_case(tag, "category"); |
|
default: return false; |
|
} |
|
} |
|
|
|
bool is_size(char const* tag) const |
|
{ |
|
return string_equal_no_case(tag, "size") |
|
|| string_equal_no_case(tag, "contentlength"); |
|
} |
|
|
|
bool is_hash(char const* tag) const |
|
{ |
|
return string_equal_no_case(tag, "hash") |
|
|| string_equal_no_case(tag, "media:hash"); |
|
} |
|
|
|
bool is_ttl(char const* tag) const |
|
{ |
|
return string_equal_no_case(tag, "ttl"); |
|
} |
|
}; |
|
|
|
void parse_feed(feed_state& f, int token, char const* name, char const* val) |
|
{ |
|
switch (token) |
|
{ |
|
case xml_parse_error: |
|
f.ret.m_error = errors::parse_failed; |
|
return; |
|
case xml_start_tag: |
|
case xml_empty_tag: |
|
{ |
|
f.current_tag = name; |
|
if (f.type == feed_state::none) |
|
{ |
|
if (string_equal_no_case(f.current_tag.c_str(), "feed")) |
|
f.type = feed_state::atom; |
|
else if (string_equal_no_case(f.current_tag.c_str(), "rss")) |
|
f.type = feed_state::rss2; |
|
} |
|
if (f.is_item(name)) f.in_item = true; |
|
return; |
|
} |
|
case xml_attribute: |
|
{ |
|
if (!f.in_item) return; |
|
if (f.is_url(f.current_tag.c_str()) |
|
&& f.type == feed_state::atom) |
|
{ |
|
// atom feeds have items like this: |
|
// <link href="http://..." length="12345"/> |
|
if (string_equal_no_case(name, "href")) |
|
f.current_item.url = val; |
|
else if (string_equal_no_case(name, "length")) |
|
f.current_item.size = strtoll(val, 0, 10); |
|
} |
|
else if (f.type == feed_state::rss2 |
|
&& string_equal_no_case(f.current_tag.c_str(), "enclosure")) |
|
{ |
|
// rss feeds have items like this: |
|
// <enclosure url="http://..." length="12345"/> |
|
if (string_equal_no_case(name, "url")) |
|
f.current_item.url = val; |
|
else if (string_equal_no_case(name, "length")) |
|
f.current_item.size = strtoll(val, 0, 10); |
|
} |
|
else if (f.type == feed_state::rss2 |
|
&& string_equal_no_case(f.current_tag.c_str(), "media:content")) |
|
{ |
|
// rss feeds sometimes have items like this: |
|
// <media:content url="http://..." filesize="12345"/> |
|
if (string_equal_no_case(name, "url")) |
|
f.current_item.url = val; |
|
else if (string_equal_no_case(name, "filesize")) |
|
f.current_item.size = strtoll(val, 0, 10); |
|
} |
|
return; |
|
} |
|
case xml_end_tag: |
|
{ |
|
if (f.in_item && f.is_item(name)) |
|
{ |
|
f.in_item = false; |
|
if (!f.current_item.title.empty() |
|
&& !f.current_item.url.empty()) |
|
{ |
|
f.ret.add_item(f.current_item); |
|
++f.num_items; |
|
} |
|
f.current_item = feed_item(); |
|
} |
|
f.current_tag = ""; |
|
return; |
|
} |
|
case xml_string: |
|
{ |
|
if (!f.in_item) |
|
{ |
|
if (f.is_title(f.current_tag.c_str())) |
|
f.ret.m_title = name; |
|
else if (f.is_desc(f.current_tag.c_str())) |
|
f.ret.m_description = name; |
|
else if (f.is_ttl(f.current_tag.c_str())) |
|
{ |
|
int tmp = atoi(name); |
|
if (tmp > 0) f.ret.m_ttl = tmp; |
|
} |
|
return; |
|
} |
|
if (f.is_title(f.current_tag.c_str())) |
|
f.current_item.title = name; |
|
else if (f.is_desc(f.current_tag.c_str())) |
|
f.current_item.description = name; |
|
else if (f.is_uuid(f.current_tag.c_str())) |
|
f.current_item.uuid = name; |
|
else if (f.is_url(f.current_tag.c_str()) && f.type != feed_state::atom) |
|
f.current_item.url = name; |
|
else if (f.is_comment(f.current_tag.c_str())) |
|
f.current_item.comment = name; |
|
else if (f.is_category(f.current_tag.c_str())) |
|
f.current_item.category = name; |
|
else if (f.is_size(f.current_tag.c_str())) |
|
f.current_item.size = strtoll(name, 0, 10); |
|
else if (f.is_hash(f.current_tag.c_str()) && strlen(name) == 40) |
|
{ |
|
if (!from_hex(name, 40, (char*)&f.current_item.info_hash[0])) |
|
{ |
|
// hex parsing failed |
|
f.current_item.info_hash.clear(); |
|
} |
|
} |
|
return; |
|
} |
|
case xml_declaration_tag: return; |
|
case xml_comment: return; |
|
} |
|
} |
|
|
|
torrent_handle add_feed_item(session& s, feed_item const& fi |
|
, add_torrent_params const& tp, error_code& ec) |
|
{ |
|
add_torrent_params p = tp; |
|
p.url = fi.url; |
|
p.uuid = fi.uuid; |
|
// #error figure out how to get the feed url in here |
|
// p.source_feed_url = ???; |
|
p.ti.reset(); |
|
p.info_hash.clear(); |
|
p.name = fi.title.c_str(); |
|
return s.add_torrent(p, ec); |
|
} |
|
|
|
#ifndef BOOST_NO_EXCEPTIONS |
|
torrent_handle add_feed_item(session& s, feed_item const& fi |
|
, add_torrent_params const& tp) |
|
{ |
|
error_code ec; |
|
torrent_handle ret = add_feed_item(s, fi, tp, ec); |
|
if (ec) throw libtorrent_exception(ec); |
|
return ret; |
|
} |
|
#endif |
|
|
|
boost::shared_ptr<feed> new_feed(aux::session_impl& ses, feed_settings const& sett) |
|
{ |
|
return boost::shared_ptr<feed>(new feed(ses, sett)); |
|
} |
|
|
|
feed::feed(aux::session_impl& ses, feed_settings const& sett) |
|
: m_last_attempt(0) |
|
, m_last_update(0) |
|
, m_ttl(-1) |
|
, m_failures(0) |
|
, m_updating(false) |
|
, m_settings(sett) |
|
, m_ses(ses) |
|
{ |
|
} |
|
|
|
void feed::set_settings(feed_settings const& s) |
|
{ |
|
m_settings = s; |
|
} |
|
|
|
void feed::get_settings(feed_settings* s) const |
|
{ |
|
*s = m_settings; |
|
} |
|
|
|
feed_handle feed::my_handle() |
|
{ |
|
return feed_handle(boost::weak_ptr<feed>(shared_from_this())); |
|
} |
|
|
|
void feed::on_feed(error_code const& ec |
|
, http_parser const& parser, char const* data, int size) |
|
{ |
|
// enabling this assert makes the unit test a lot more difficult |
|
// TORRENT_ASSERT(m_updating); |
|
m_updating = false; |
|
|
|
if (ec && ec != asio::error::eof) |
|
{ |
|
++m_failures; |
|
m_error = ec; |
|
if (m_ses.m_alerts.should_post<rss_alert>()) |
|
{ |
|
m_ses.m_alerts.post_alert(rss_alert(my_handle(), m_settings.url |
|
, rss_alert::state_error, m_error)); |
|
} |
|
return; |
|
} |
|
|
|
if (parser.status_code() != 200) |
|
{ |
|
++m_failures; |
|
m_error = error_code(parser.status_code(), get_http_category()); |
|
if (m_ses.m_alerts.should_post<rss_alert>()) |
|
{ |
|
m_ses.m_alerts.post_alert(rss_alert(my_handle(), m_settings.url |
|
, rss_alert::state_error, m_error)); |
|
} |
|
return; |
|
} |
|
|
|
m_failures = 0; |
|
|
|
char* buf = const_cast<char*>(data); |
|
|
|
feed_state s(*this); |
|
xml_parse(buf, buf + size, boost::bind(&parse_feed, boost::ref(s), _1, _2, _3)); |
|
|
|
time_t now = time(NULL); |
|
|
|
// keep history of the typical feed size times 5 |
|
int max_history = (std::max)(s.num_items * 5, 100); |
|
|
|
// this is not very efficient, but that's probably OK for now |
|
while (int(m_added.size()) > max_history) |
|
{ |
|
// loop over all elements and find the one with the lowest timestamp |
|
// i.e. it was added the longest ago, then remove it |
|
std::map<std::string, time_t>::iterator i = std::min_element( |
|
m_added.begin(), m_added.end() |
|
, boost::bind(&std::pair<const std::string, time_t>::second, _1) |
|
< boost::bind(&std::pair<const std::string, time_t>::second, _2)); |
|
m_added.erase(i); |
|
} |
|
|
|
m_last_update = now; |
|
|
|
// report that we successfully updated the feed |
|
if (m_ses.m_alerts.should_post<rss_alert>()) |
|
{ |
|
m_ses.m_alerts.post_alert(rss_alert(my_handle(), m_settings.url |
|
, rss_alert::state_updated, error_code())); |
|
} |
|
|
|
// update m_ses.m_next_rss_update timestamps |
|
// now that we have updated our timestamp |
|
m_ses.update_rss_feeds(); |
|
} |
|
|
|
#define TORRENT_SETTING(t, x) {#x, offsetof(feed_settings,x), t}, |
|
bencode_map_entry feed_settings_map[] = |
|
{ |
|
TORRENT_SETTING(std_string, url) |
|
TORRENT_SETTING(boolean, auto_download) |
|
TORRENT_SETTING(boolean, auto_map_handles) |
|
TORRENT_SETTING(integer, default_ttl) |
|
}; |
|
#undef TORRENT_SETTING |
|
|
|
#define TORRENT_SETTING(t, x) {#x, offsetof(feed_item,x), t}, |
|
bencode_map_entry feed_item_map[] = |
|
{ |
|
TORRENT_SETTING(std_string, url) |
|
TORRENT_SETTING(std_string, uuid) |
|
TORRENT_SETTING(std_string, title) |
|
TORRENT_SETTING(std_string, description) |
|
TORRENT_SETTING(std_string, comment) |
|
TORRENT_SETTING(std_string, category) |
|
TORRENT_SETTING(size_integer, size) |
|
}; |
|
#undef TORRENT_SETTING |
|
|
|
#define TORRENT_SETTING(t, x) {#x, offsetof(feed,x), t}, |
|
bencode_map_entry feed_map[] = |
|
{ |
|
TORRENT_SETTING(std_string, m_title) |
|
TORRENT_SETTING(std_string, m_description) |
|
TORRENT_SETTING(time_integer, m_last_attempt) |
|
TORRENT_SETTING(time_integer, m_last_update) |
|
}; |
|
#undef TORRENT_SETTING |
|
|
|
#define TORRENT_SETTING(t, x) {#x, offsetof(add_torrent_params,x), t}, |
|
bencode_map_entry add_torrent_map[] = |
|
{ |
|
TORRENT_SETTING(std_string, save_path) |
|
TORRENT_SETTING(size_integer, flags) |
|
}; |
|
#undef TORRENT_SETTING |
|
|
|
void feed::load_state(lazy_entry const& rd) |
|
{ |
|
load_struct(rd, this, feed_map, sizeof(feed_map)/sizeof(feed_map[0])); |
|
lazy_entry const* e = rd.dict_find_list("items"); |
|
if (e) |
|
{ |
|
m_items.reserve(e->list_size()); |
|
for (int i = 0; i < e->list_size(); ++i) |
|
{ |
|
if (e->list_at(i)->type() != lazy_entry::dict_t) continue; |
|
m_items.push_back(feed_item()); |
|
load_struct(*e->list_at(i), &m_items.back(), feed_item_map |
|
, sizeof(feed_item_map)/sizeof(feed_item_map[0])); |
|
|
|
// don't load duplicates |
|
if (m_urls.find(m_items.back().url) != m_urls.end()) |
|
{ |
|
m_items.pop_back(); |
|
continue; |
|
} |
|
m_urls.insert(m_items.back().url); |
|
} |
|
} |
|
load_struct(rd, &m_settings, feed_settings_map |
|
, sizeof(feed_settings_map)/sizeof(feed_settings_map[0])); |
|
e = rd.dict_find_dict("add_params"); |
|
if (e) |
|
{ |
|
load_struct(*e, &m_settings.add_args, add_torrent_map |
|
, sizeof(add_torrent_map)/sizeof(add_torrent_map[0])); |
|
} |
|
|
|
e = rd.dict_find_list("history"); |
|
if (e) |
|
{ |
|
for (int i = 0; i < e->list_size(); ++i) |
|
{ |
|
if (e->list_at(i)->type() != lazy_entry::list_t) continue; |
|
|
|
lazy_entry const* item = e->list_at(i); |
|
|
|
if (item->list_size() != 2 |
|
|| item->list_at(0)->type() != lazy_entry::string_t |
|
|| item->list_at(1)->type() != lazy_entry::int_t) |
|
continue; |
|
|
|
m_added.insert(std::pair<std::string, time_t>( |
|
item->list_at(0)->string_value() |
|
, item->list_at(1)->int_value())); |
|
} |
|
} |
|
} |
|
|
|
void feed::save_state(entry& rd) const |
|
{ |
|
// feed properties |
|
save_struct(rd, this, feed_map, sizeof(feed_map)/sizeof(feed_map[0])); |
|
|
|
// items |
|
entry::list_type& items = rd["items"].list(); |
|
for (std::vector<feed_item>::const_iterator i = m_items.begin() |
|
, end(m_items.end()); i != end; ++i) |
|
{ |
|
items.push_back(entry()); |
|
entry& item = items.back(); |
|
save_struct(item, &*i, feed_item_map, sizeof(feed_item_map)/sizeof(feed_item_map[0])); |
|
} |
|
|
|
// settings |
|
feed_settings sett_def; |
|
save_struct(rd, &m_settings, feed_settings_map |
|
, sizeof(feed_settings_map)/sizeof(feed_settings_map[0]), &sett_def); |
|
entry& add = rd["add_params"]; |
|
add_torrent_params add_def; |
|
save_struct(add, &m_settings.add_args, add_torrent_map |
|
, sizeof(add_torrent_map)/sizeof(add_torrent_map[0]), &add_def); |
|
|
|
entry::list_type& history = rd["history"].list(); |
|
for (std::map<std::string, time_t>::const_iterator i = m_added.begin() |
|
, end(m_added.end()); i != end; ++i) |
|
{ |
|
history.push_back(entry()); |
|
entry::list_type& item = history.back().list(); |
|
item.push_back(entry(i->first)); |
|
item.push_back(entry(i->second)); |
|
} |
|
} |
|
|
|
void feed::add_item(feed_item const& item) |
|
{ |
|
// don't add duplicates |
|
if (m_urls.find(item.url) != m_urls.end()) |
|
return; |
|
|
|
m_urls.insert(item.url); |
|
m_items.push_back(item); |
|
|
|
feed_item& i = m_items.back(); |
|
|
|
if (m_settings.auto_map_handles) |
|
i.handle = torrent_handle(m_ses.find_torrent(i.uuid.empty() ? i.url : i.uuid)); |
|
|
|
if (m_ses.m_alerts.should_post<rss_item_alert>()) |
|
m_ses.m_alerts.post_alert(rss_item_alert(my_handle(), i)); |
|
|
|
if (m_settings.auto_download) |
|
{ |
|
if (!m_settings.auto_map_handles) |
|
i.handle = torrent_handle(m_ses.find_torrent(i.uuid.empty() ? i.url : i.uuid)); |
|
|
|
// if we're already downloading this torrent |
|
// move along to the next one |
|
if (i.handle.is_valid()) return; |
|
|
|
// has this already been added? |
|
if (m_added.find(i.url) != m_added.end()) return; |
|
|
|
// this means we should add this torrent to the session |
|
add_torrent_params p = m_settings.add_args; |
|
p.url = i.url; |
|
p.uuid = i.uuid; |
|
p.source_feed_url = m_settings.url; |
|
p.ti.reset(); |
|
p.info_hash.clear(); |
|
p.name = i.title.c_str(); |
|
|
|
error_code e; |
|
m_ses.add_torrent(p, e); |
|
time_t now = time(NULL); |
|
m_added.insert(make_pair(i.url, now)); |
|
} |
|
} |
|
|
|
// returns the number of seconds until trying again |
|
int feed::update_feed() |
|
{ |
|
if (m_updating) return 60; |
|
|
|
m_last_attempt = time(0); |
|
m_last_update = 0; |
|
|
|
if (m_ses.m_alerts.should_post<rss_alert>()) |
|
{ |
|
m_ses.m_alerts.post_alert(rss_alert(my_handle(), m_settings.url |
|
, rss_alert::state_updating, error_code())); |
|
} |
|
|
|
boost::shared_ptr<http_connection> feed( |
|
new http_connection(m_ses.m_io_service, m_ses.m_half_open |
|
, boost::bind(&feed::on_feed, shared_from_this() |
|
, _1, _2, _3, _4))); |
|
|
|
m_updating = true; |
|
feed->get(m_settings.url, seconds(30), 0, 0, 5, m_ses.m_settings.user_agent); |
|
|
|
return 60 + m_failures * m_failures * 60; |
|
} |
|
|
|
void feed::get_feed_status(feed_status* ret) const |
|
{ |
|
ret->items = m_items; |
|
ret->last_update = m_last_update; |
|
ret->updating = m_updating; |
|
ret->url = m_settings.url; |
|
ret->title = m_title; |
|
ret->description = m_description; |
|
ret->error = m_error; |
|
ret->ttl = m_ttl == -1 ? m_settings.default_ttl : m_ttl; |
|
ret->next_update = next_update(time(0)); |
|
} |
|
|
|
int feed::next_update(time_t now) const |
|
{ |
|
if (m_last_update == 0) return m_last_attempt + 60 * 5 - now; |
|
int ttl = m_ttl == -1 ? m_settings.default_ttl : m_ttl; |
|
TORRENT_ASSERT((m_last_update + ttl * 60) - now < INT_MAX); |
|
return int((m_last_update + ttl * 60) - now); |
|
} |
|
|
|
// defined in session.cpp |
|
void fun_wrap(bool* done, condition_variable* e, mutex* m, boost::function<void(void)> f); |
|
|
|
#define TORRENT_ASYNC_CALL(x) \ |
|
boost::shared_ptr<feed> f = m_feed_ptr.lock(); \ |
|
if (!f) return; \ |
|
aux::session_impl& ses = f->session(); \ |
|
ses.m_io_service.post(boost::bind(&feed:: x, f)) |
|
|
|
#define TORRENT_ASYNC_CALL1(x, a1) \ |
|
boost::shared_ptr<feed> f = m_feed_ptr.lock(); \ |
|
if (!f) return; \ |
|
aux::session_impl& ses = f->session(); \ |
|
ses.m_io_service.post(boost::bind(&feed:: x, f, a1)) |
|
|
|
#define TORRENT_SYNC_CALL1(x, a1) \ |
|
boost::shared_ptr<feed> f = m_feed_ptr.lock(); \ |
|
if (f) { \ |
|
bool done = false; \ |
|
aux::session_impl& ses = f->session(); \ |
|
mutex::scoped_lock l(ses.mut); \ |
|
ses.m_io_service.post(boost::bind(&fun_wrap, &done, &ses.cond, &ses.mut, boost::function<void(void)>(boost::bind(&feed:: x, f, a1)))); \ |
|
f.reset(); \ |
|
do { ses.cond.wait(l); } while(!done); } |
|
|
|
feed_handle::feed_handle(boost::weak_ptr<feed> const& p) |
|
: m_feed_ptr(p) {} |
|
|
|
void feed_handle::update_feed() |
|
{ |
|
TORRENT_ASYNC_CALL(update_feed); |
|
} |
|
|
|
feed_status feed_handle::get_feed_status() const |
|
{ |
|
feed_status ret; |
|
TORRENT_SYNC_CALL1(get_feed_status, &ret); |
|
return ret; |
|
} |
|
|
|
void feed_handle::set_settings(feed_settings const& s) |
|
{ |
|
TORRENT_SYNC_CALL1(set_settings, s); |
|
} |
|
|
|
feed_settings feed_handle::settings() const |
|
{ |
|
feed_settings ret; |
|
TORRENT_SYNC_CALL1(get_settings, &ret); |
|
return ret; |
|
} |
|
|
|
}; |
|
|
|
|