/[gnomoradio]/gnomoradio/rainbow/hub-client.cc
ViewVC logotype

Contents of /gnomoradio/rainbow/hub-client.cc

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.57 - (show annotations) (download)
Sun Oct 17 04:09:35 2004 UTC (19 years, 7 months ago) by garrison
Branch: MAIN
CVS Tags: HEAD
Changes since 1.56: +32 -32 lines
port to gtkmm 2.4

1 // Gnomoradio - rainbow/hub-client.cc
2 // Copyright (C) 2003 Jim Garrison
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 #include "hub-client.h"
19 #include "rainbow/util.h"
20
21 extern "C" {
22 #include "rainbow/sha1.h"
23 }
24
25 #include <sys/stat.h>
26
27 #include <unistd.h>
28 #include <cstdio>
29 #include <string>
30
31 #include <cstdlib>
32 #include <sstream>
33
34 #define CRLF "\r\n"
35
36 namespace Rainbow
37 {
38 class Checksum : public sigc::trackable
39 {
40 public:
41 static void verify (const sigc::slot<void,bool,Resource*> &mem_fun,
42 Resource *resource);
43
44 private:
45 Checksum (const sigc::slot<void,bool,Resource*> &mem_fun,
46 Resource *r);
47 void verify_thread ();
48 void verify_do ();
49 void dispatch ();
50
51 sigc::signal<void,bool,Resource*> signal;
52 Glib::Dispatcher dispatcher;
53 bool success;
54 Resource *resource;
55 Glib::ustring filename;
56 std::vector<guint8> checksum;
57 };
58
59 class HubConnector : public sigc::trackable
60 {
61 public:
62 static void connect (const sigc::slot<void,int> &mem_fun,
63 const Glib::ustring &hub_server);
64
65 private:
66 HubConnector (const sigc::slot<void,int> &mem_fun,
67 const Glib::ustring &hub_server);
68 void thread ();
69 void dispatch ();
70
71 sigc::signal<void,int> signal;
72 Glib::Dispatcher dispatcher;
73 int sock;
74 sockaddr_in hub;
75 Glib::ustring server;
76 };
77 }
78
79 using namespace std;
80 using namespace sigc;
81 using namespace Glib;
82 using namespace Rainbow;
83
84 Rainbow::Checksum::Checksum (const sigc::slot<void,bool,Resource*> &mem_fun,
85 Resource *r)
86 : resource(r),
87 filename(r->get_filename()),
88 checksum(r->checksum),
89 success(false)
90 {
91 signal.connect(mem_fun);
92 dispatcher.connect(sigc::mem_fun(*this, &Checksum::dispatch));
93
94 if (!Glib::thread_supported())
95 Glib::thread_init();
96 Glib::Thread::create(sigc::mem_fun(*this, &Checksum::verify_thread), false);
97 }
98
99 void Rainbow::Checksum::verify (const sigc::slot<void,bool,Resource*> &mem_fun,
100 Resource *resource)
101 {
102 new Checksum(mem_fun, resource);
103 }
104
105 void Rainbow::Checksum::verify_thread ()
106 {
107 verify_do();
108 dispatcher();
109 }
110
111 void Rainbow::Checksum::verify_do ()
112 {
113 std::string fn;
114 try {
115 fn = filename_from_utf8(filename);
116 } catch (...) {
117 return;
118 }
119
120 FILE *file = fopen(fn.c_str(), "rb");
121 if (file) {
122 const int buf_size = 4096;
123 unsigned char buf[buf_size];
124
125 SHA1Context cxt;
126 if (SHA1Reset(&cxt))
127 return;
128
129 size_t r;
130 for (;;) {
131 r = fread(buf, 1, buf_size, file);
132 if (r == 0)
133 break;
134 if (SHA1Input(&cxt, buf, r))
135 return;
136 }
137
138 fclose(file);
139
140 guint8 sum[SHA1HashSize];
141 if (SHA1Result(&cxt, sum))
142 return;
143
144 for (int i = 0; i < SHA1HashSize; ++i)
145 if (sum[i] != checksum[i])
146 return;
147 success = true;
148 }
149 }
150
151 void Rainbow::Checksum::dispatch ()
152 {
153 signal(success, resource);
154 delete this;
155 }
156
157 void Rainbow::HubConnector::connect (const sigc::slot<void,int> &mem_fun,
158 const Glib::ustring &hub_server)
159 {
160 new HubConnector(mem_fun, hub_server);
161 }
162
163 Rainbow::HubConnector::HubConnector (const sigc::slot<void,int> &mem_fun,
164 const Glib::ustring &hub_server)
165 : server(hub_server),
166 sock(0)
167 {
168 signal.connect(mem_fun);
169 dispatcher.connect(sigc::mem_fun(*this, &HubConnector::dispatch));
170
171 struct hostent *host = gethostbyname(hub_server.c_str());
172 if (!host) {
173 logmsg << "HubClient: Could not create host entry for " << hub_server << endl;
174 } else {
175 sock = socket(PF_INET, SOCK_STREAM, 0);
176 if (sock < 1)
177 logmsg << "HubClient: Could not create socket" << endl;
178 }
179
180 if (sock > 0) {
181 hub.sin_addr = *(struct in_addr*) host->h_addr;
182 hub.sin_family = AF_INET;
183 hub.sin_port = htons(18373);
184
185 if (!Glib::thread_supported())
186 Glib::thread_init();
187 Glib::Thread::create(sigc::mem_fun(*this, &HubConnector::thread), false);
188 } else {
189 signal(sock);
190 delete this;
191 }
192 }
193
194 void Rainbow::HubConnector::thread ()
195 {
196 if (::connect(sock, (struct sockaddr*) &hub, sizeof(hub))) {
197 close(sock);
198 sock = 0;
199 logmsg << "HubClient: Could not connect to host" << endl;
200 } else {
201 // connected, now send handshake line
202 string send = "RAINBOW/1.0 " "4617" CRLF; // FIXME
203 if (!send_data_on_socket(sock, send)) {
204 close(sock);
205 sock = 0;
206 logmsg << "HubClient: Could not initiate handshake (broken socket)" << endl;
207 }
208 }
209
210 dispatcher();
211 }
212
213 void Rainbow::HubConnector::dispatch ()
214 {
215 signal(sock);
216 delete this;
217 }
218
219 static inline guint8 hex_to_int (char c)
220 {
221 if (c >= '0' && c <= '9')
222 return guint8(c - '0');
223 if (c >= 'A' && c <= 'F')
224 return guint8(c - 'A') + 10;
225 if (c >= 'a' && c <= 'f')
226 return guint8(c - 'a') + 10;
227 return 0;
228 }
229
230 void Rainbow::HubClient::parse_resource (xmlpp::Element *xml, ref_ptr<RdfResource> rdf, Resource *r)
231 {
232 r->obtained_info = true;
233
234 if (xml) {
235 xmlpp::Node::NodeList children = xml->get_children();
236 for (xmlpp::Node::NodeList::iterator i = children.begin(); i != children.end(); ++i) {
237 xmlpp::Element *el = dynamic_cast<xmlpp::Element*>(*i);
238 if (!el)
239 continue;
240 xmlpp::TextNode *content = el->get_child_text();
241 xmlpp::Attribute *resource_link = el->get_attribute("resource");
242
243 if (el->get_name() == "available") {
244 if (resource_link)
245 r->available_locations.push_back(resource_link->get_value());
246 } else if (el->get_name() == "license") {
247 if (resource_link) {
248 Glib::ustring absolute = RdfResource::absolute_uri(resource_link->get_value(),
249 rdf->get_base_uri());
250 License::get_and_do(absolute,
251 bind(mem_fun(*this, &HubClient::license_obtained_callback), r),
252 rdf->get_secondary_info(absolute));
253 }
254 } else if (el->get_name() == "checksum") {
255 if (content && content->get_content().length()) {
256 r->checksum.resize(SHA1HashSize);
257 try {
258 string chk = content->get_content();
259 int n = chk.length() / 2;
260 if (n > SHA1HashSize) // we only have 20 bytes to work with
261 n = SHA1HashSize;
262 for (int i = 0; i < n; ++i) {
263 r->checksum[i] = hex_to_int(chk[i*2]) * 16;
264 r->checksum[i] += hex_to_int(chk[i*2+1]);
265 }
266 } catch (...) {
267 }
268 }
269 } else if (el->get_name() == "representationOf") {
270
271 }
272 }
273 }
274
275 if (r->obtained_info) {
276 if (r->prepare_once_info_is_obtained)
277 prepare_resource(r);
278 r->signal_found_info(bool(xml));
279 }
280 }
281
282 void Rainbow::HubClient::license_obtained_callback (Rainbow::ref_ptr<Rainbow::License> license, Resource *r)
283 {
284 r->sharable = license->sharable();
285 }
286
287 Rainbow::HubClient::HubClient (const Glib::ustring &hub)
288 : server(this),
289 hub_host(hub),
290 hubsock(0),
291 cache_size(50),
292 total_allocated_size(0)
293 {
294 alarm.signal_alarm().connect(mem_fun(*this, &HubClient::connect));
295
296 load_database();
297 save_alarm.signal_alarm().connect(mem_fun(*this, &HubClient::on_save_alarm));
298 save_alarm.set_relative(save_alarm_interval);
299
300 // FIXME: we could wait until connected to the hub to start http services
301 if (!server.start(http_server_port)) {
302 logmsg << "HubClient: Could not start http server" << endl;
303 }
304 connect();
305 }
306
307 Rainbow::HubClient::~HubClient ()
308 {
309 disconnect();
310 save_database();
311 }
312
313 void Rainbow::HubClient::on_save_alarm ()
314 {
315 save_database();
316 save_alarm.set_relative(save_alarm_interval);
317 }
318
319 void Rainbow::HubClient::load_database ()
320 {
321 Mutex::Lock lock(db_mutex);
322 xmlpp::DomParser tree;
323 try {
324 tree.parse_file(string(getenv("HOME")) + "/.rainbow-db.xml");
325 } catch (...) {
326 return;
327 }
328
329 xmlpp::Node *root = tree.get_document()->get_root_node();
330 xmlpp::Node::NodeList headings = root->get_children();
331
332 for (xmlpp::Node::NodeList::iterator heading = headings.begin();
333 heading != headings.end(); ++heading) {
334 if ((*heading)->get_name() == "resources") {
335 xmlpp::Node::NodeList resources = (*heading)->get_children();
336 for (xmlpp::Node::NodeList::iterator i = resources.begin();
337 i != resources.end(); ++i) {
338 xmlpp::Element *resource = dynamic_cast<xmlpp::Element*>(*i);
339 if (!resource)
340 continue;
341
342 xmlpp::Attribute *url = resource->get_attribute("url");
343 xmlpp::Attribute *filename = resource->get_attribute("filename");
344 if (!url || !filename)
345 continue;
346
347 Resource *r = new Resource(url->get_value(), filename->get_value());
348
349 xmlpp::Attribute *sz = resource->get_attribute("size");
350 if (sz)
351 set_allocated_size(r, atoi(sz->get_value().c_str()));
352 else
353 check_allocated_size(r);
354
355 xmlpp::Attribute *sharable = resource->get_attribute("sharable");
356 if (sharable && sharable->get_value() == "true")
357 r->sharable = true;
358
359 db.insert(make_pair(url->get_value(),
360 ref_ptr<Resource>(r)));
361 }
362 }
363 }
364 }
365
366 void Rainbow::HubClient::save_database ()
367 {
368 Mutex::Lock lock(db_mutex);
369 xmlpp::Document tree;
370 tree.create_root_node("rainbow");
371 xmlpp::Node *root = tree.get_root_node();
372
373 xmlpp::Node *resources = root->add_child("resources");
374 map<Glib::ustring,ref_ptr<Resource> >::iterator p;
375 for (p = db.begin(); p != db.end(); ++p) {
376 if (!p->second->prepared)
377 continue;
378 xmlpp::Element *node = resources->add_child("resource");
379 node->set_attribute("url", p->second->get_url());
380 node->set_attribute("filename", p->second->get_filename());
381 node->set_attribute("sharable", p->second->sharable ? "true" : "false");
382 if (p->second->allocated_size) {
383 ostringstream as;
384 as << p->second->allocated_size;
385 node->set_attribute("size", as.str());
386 }
387 }
388
389 tree.write_to_file_formatted(string(getenv("HOME")) + "/.rainbow-db.xml");
390 }
391
392 void Rainbow::HubClient::connect ()
393 {
394 if (hub_host.length() == 0)
395 return;
396
397 if (hubsock > 0) {
398 // connection is already/still established
399 alarm.set_relative(reconnect_interval);
400 } else {
401 HubConnector::connect(mem_fun(*this, &HubClient::on_connect), hub_host);
402 }
403 }
404
405 void Rainbow::HubClient::disconnect ()
406 {
407 if (hubsock > 0) {
408 close(hubsock);
409 }
410 hubsock = 0;
411 }
412
413 void Rainbow::HubClient::on_connect (int socket)
414 {
415 hubsock = socket;
416
417 // reset alarm
418 alarm.set_relative(reconnect_interval);
419
420 // send database to server
421 if (socket > 0) {
422 string send;
423 map<Glib::ustring,ref_ptr<Resource> >::iterator i;
424 for (i = db.begin(); i != db.end(); ++i)
425 if (i->second->sharable)
426 send.append('+' + i->first + CRLF);
427
428 if (!send_data_on_socket(hubsock, send))
429 disconnect();
430 }
431 }
432
433 ref_ptr<Resource> Rainbow::HubClient::find (const Glib::ustring &url)
434 {
435 Mutex::Lock lock(db_mutex);
436 map<Glib::ustring,ref_ptr<Resource> >::iterator p;
437 p = db.find(url);
438 if (p != db.end())
439 return p->second;
440 else
441 return ref_ptr<Resource>();
442 }
443
444 ref_ptr<Resource> Rainbow::HubClient::create (const Glib::ustring &url)
445 {
446 ref_ptr<Resource> r = find(url);
447 if (!&*r) {
448 Mutex::Lock lock(db_mutex);
449 r = ref_ptr<Resource>(new Resource(url));
450 db.insert(make_pair(url, r));
451
452 if (r->has_rdf())
453 RdfResource::get_and_do(url, bind(mem_fun(*this, &HubClient::parse_resource), &*r));
454 else {
455 r->available_locations.push_back(url);
456 r->obtained_info = true;
457 }
458 }
459 return r;
460 }
461
462 bool Rainbow::HubClient::get_filename_threadsafe (const Glib::ustring &url,
463 Glib::ustring &filename,
464 bool only_if_sharable)
465 {
466 ref_ptr<Resource> r = find(url);
467 if (!&*r)
468 return false;
469 if (only_if_sharable && !r->sharable)
470 return false;
471
472 Mutex::Lock lock(db_mutex);
473 filename = r->get_filename();
474 return true;
475 }
476
477 size_t Rainbow::HubClient::get_size_threadsafe (const Glib::ustring &url)
478 {
479 ref_ptr<Resource> r = find(url);
480 if (!&*r)
481 return 0;
482
483 Mutex::Lock lock(db_mutex);
484 size_t s = r->allocated_size;
485 return s;
486 }
487
488 static inline bool check_path (const char *path)
489 {
490 struct stat s;
491
492 if (!stat(path, &s)) {
493 if (!S_ISDIR(s.st_mode)) {
494 //g_warning("%s exists but is not a directory", path);
495 return false;
496 }
497 } else {
498 if (mkdir(path, 0755)) {
499 //g_warning("%s cannot be created", path);
500 return false;
501 }
502 }
503
504 return true;
505 }
506
507 Glib::ustring Rainbow::HubClient::random_filename ()
508 {
509 string basedir = getenv("HOME") + string("/.rainbow-cache");
510 check_path(basedir.c_str()); // error out if false?
511
512 string filename;
513 struct stat s;
514 do {
515 char rnd[] = " ";
516 for (int i = 0; i < 8; ++i)
517 rnd[i] = rand() % 26 + 'a';
518 filename = basedir + string("/") + rnd;
519 } while (!stat(filename.c_str(), &s));
520
521 return filename_to_utf8(filename);
522 }
523
524 void Rainbow::HubClient::prepare_resource (Resource *r)
525 {
526 if (r->prepared || r->downloading)
527 return;
528 if (!r->obtained_info) {
529 r->prepare_once_info_is_obtained = true;
530 return;
531 }
532
533 r->downloading = true;
534
535 if (r->has_rdf())
536 query_hub(r); // FIXME: make this asynchronous
537 start_download(r);
538 }
539
540 void Rainbow::HubClient::query_hub (Resource *r)
541 {
542 string query('?' + r->get_url() + CRLF);
543 string buffer;
544 int lines_remaining = -1;
545 if (hubsock <= 0 || !send_data_on_socket(hubsock, query)) {
546 disconnect();
547 } else {
548 while (lines_remaining) {
549 const size_t buf_size = 2048;
550 char buf[buf_size];
551 ssize_t rb = read(hubsock, buf, buf_size);
552 if (rb <= 0)
553 break;
554 buffer.append(buf, rb);
555 string::size_type p;
556 while (lines_remaining
557 && (p = buffer.find(CRLF)) != string::npos) {
558 string line = buffer.substr(0, p);
559 buffer = buffer.substr(p + 2);
560 if (lines_remaining == -1) {
561 // first line
562 lines_remaining = atoi(line.c_str());
563 if (lines_remaining < 0)
564 return;
565 } else {
566 // add to array
567 try {
568 Glib::ustring l(line);
569 if (l.validate())
570 r->mirrors.push_back(l);
571 } catch (...) {
572 }
573 --lines_remaining;
574 }
575 }
576 }
577 }
578 }
579
580 void Rainbow::HubClient::start_download (Resource *r)
581 {
582 if (!r->filename.size() && (r->mirrors.size() || r->available_locations.size()))
583 r->filename = random_filename();
584
585 if (r->mirrors.size()) {
586 Glib::ustring mirror = r->mirrors[r->mirrors.size() - 1];
587 r->mirrors.pop_back();
588 unsigned short port = 80;
589 Glib::ustring::size_type colon = mirror.find(':');
590 if (colon != Glib::ustring::npos) {
591 Glib::ustring prt = mirror.substr(colon + 1);
592 mirror = mirror.substr(0, colon);
593 port = atoi(prt.c_str());
594 }
595 // FIXME: use different DL
596 r->dl.reset(new HttpClient(mirror, port, false));
597 if (r->get_url().substr(0, 6) != "http:/") {
598 start_download(r); // try, try again
599 return;
600 }
601 r->dl->get(r->get_url().substr(6), r->get_filename());
602 r->dl->signal_request_done.connect(bind(mem_fun(*this, &HubClient::file_download_done_callback), r));
603 r->dl->signal_download_percent.connect(bind(mem_fun(*this, &HubClient::file_download_percent_callback), r));
604 } else if (r->available_locations.size() > r->available_locations_cursor) {
605 Glib::ustring remote_host, remote_file;
606 unsigned short remote_port;
607 if (!HttpClient::parse_url(r->available_locations[r->available_locations_cursor++],
608 remote_host, remote_port, remote_file)) {
609 start_download(r); // try, try again
610 return;
611 }
612 r->dl.reset(new HttpClient(remote_host, remote_port, false));
613 r->dl->get(remote_file, r->get_filename());
614 r->dl->signal_request_done.connect(bind(mem_fun(*this, &HubClient::file_download_done_callback), r));
615 r->dl->signal_download_percent.connect(bind(mem_fun(*this, &HubClient::file_download_percent_callback), r));
616 } else {
617 // not available anywhere now
618 logmsg << "HubClient: Resource not available currently: " << r->get_url() << endl;
619 r->downloading = false;
620 r->signal_download_done(false);
621 }
622 }
623
624 void Rainbow::HubClient::set_hub (const Glib::ustring &hub)
625 {
626 if (hub == hub_host)
627 return;
628
629 hub_host = hub;
630 alarm.reset();
631 disconnect();
632 connect();
633 }
634
635 void Rainbow::HubClient::file_download_percent_callback (unsigned int percent, Resource *r)
636 {
637 r->signal_downloading(percent);
638 }
639
640 void Rainbow::HubClient::download_success (Resource *r)
641 {
642 r->prepared = true;
643 r->downloading = false;
644 r->signal_download_done(true);
645 check_allocated_size(r);
646 }
647
648 void Rainbow::HubClient::file_download_done_callback (bool success, Resource *r)
649 {
650 r->prepared = success;
651 r->dl.reset();
652
653 if (success) {
654 // verify checksum
655 if (r->checksum.size() != 0)
656 Checksum::verify(mem_fun(*this, &HubClient::verify_checksum_callback), r);
657 else
658 download_success(r);
659 } else
660 start_download(r);
661 }
662
663 void Rainbow::HubClient::verify_checksum_callback (bool success, Resource *r)
664 {
665 if (success) {
666 download_success(r);
667 // tell hub we have this file
668 if (hubsock > 0) {
669 string send(' ' + r->get_url() + CRLF);
670 if (r->sharable)
671 send[0] = '+';
672 else
673 send[0] = '-'; // fixme: is the necessary?
674
675 if (!send_data_on_socket(hubsock, send))
676 disconnect();
677 }
678 } else
679 start_download(r);
680 }
681
682 void Rainbow::HubClient::check_allocated_size (Resource *r)
683 {
684 struct stat s;
685 try {
686 if (!stat(filename_from_utf8(r->get_filename()).c_str(), &s))
687 set_allocated_size(r, s.st_size);
688 } catch (...) {
689 }
690 }
691
692 void Rainbow::HubClient::set_allocated_size (Resource *r, size_t s)
693 {
694 total_allocated_size -= r->allocated_size / 1024;
695 r->allocated_size = s;
696 total_allocated_size += r->allocated_size / 1024;
697
698 if (s != 0)
699 review_cache_size();
700 }
701
702 void Rainbow::HubClient::review_cache_size ()
703 {
704 const size_t allowed_allocated_size = cache_size * 1024;
705 if (total_allocated_size > allowed_allocated_size)
706 signal_must_uncache(total_allocated_size - allowed_allocated_size);
707 }
708
709 void Rainbow::HubClient::set_cache_size (unsigned int megabytes)
710 {
711 if (megabytes < 30)
712 megabytes = 30; // there has to be a bottom limit
713
714 cache_size = megabytes;
715 review_cache_size();
716 }
717
718 void Rainbow::HubClient::uncache_resource (Resource *r)
719 {
720 if (!r->prepared)
721 return;
722
723 set_allocated_size(r, 0);
724 r->prepared = false;
725 r->obtained_info = false;
726 r->download_percent = 0;
727 r->available_locations_cursor = 0;
728 r->available_locations.clear();
729 r->mirrors.clear();
730 r->signal_file_deleted();
731
732 unlink(r->filename.c_str());
733
734 // fixme: this should not be necessary now, but it needs to be done at some point in case you decide to download again
735 if (r->has_rdf())
736 RdfResource::get_and_do(r->get_url(), bind(mem_fun(*this, &HubClient::parse_resource), r));
737 }
738
739 // fixme: maybe size should be stored, with a boolean whether it's allocated or used

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26