/* * $Id$ * * ede-bug-report, a tool to report bugs on EDE bugzilla instance * Part of Equinox Desktop Environment (EDE). * Copyright (c) 2009 EDE Authors. * * This program is licensed under terms of the * GNU General Public License version 2 or newer. * See COPYING for details. */ #include #include #include #include /* used from local xmlrpc-c source */ #include #include #include #include #include #include #include #include "PulseProgress.h" #include "BugzillaSender.h" #include "Bugzilla.h" #define EDE_BUGZILLA_XMLRPC_URL "http://bugs.equinox-project.org/xmlrpc.cgi" /* must match to existing user */ #define EDE_BUGZILLA_USER "ede-bugs@lists.sourceforge.net" #define EDE_BUGZILLA_PASS "edebugs2709" #define PROGRESS_STEP_REFRESH 0.06 /* message length without '\n' */ #define REPORT_STR_LEN 1024 EDELIB_NS_USING(alert) EDELIB_NS_USING(message) EDELIB_NS_USING(String) /* for easier passing it to the thread */ struct BugContent { const char *bug_title; const char *bug_content; }; static Fl_Double_Window *win; static PulseProgress *progress; static BugzillaData *bdata; static bool data_submitted; /* we use only one additional thread */ static int report_pipe[2]; static pthread_mutex_t runner_mutex; /* to prevent sending data; shared between threads */ static bool cancel_sending; static void write_string(int fd, const char *str) { int len = strlen(str); if(len > REPORT_STR_LEN) len = REPORT_STR_LEN; ::write(fd, str, len); ::write(fd, "\n", 1); } /* these are messages prepended with 'FAILED' so we knows how error was emited */ static void write_string_fail(int fd, const char *str) { int len = strlen(str); if(len > REPORT_STR_LEN) { /* our max message size sans 'FAILED' header */ len = REPORT_STR_LEN - 6; } ::write(fd, "FAILED", 6); ::write(fd, str, len); ::write(fd, "\n", 1); } static int read_string(int fd, String& ret) { char c; char buf[REPORT_STR_LEN]; int i, nc; memset(buf, 0, sizeof(buf)); i = 0; nc = ::read(fd, &c, 1); while(nc > 0 && c != '\n' && i < REPORT_STR_LEN) { buf[i] = c; i = i + nc; nc = ::read(fd, &c, 1); } buf[i] = '\0'; ret = buf; return i; } static void cancel_cb(Fl_Widget*, void*) { /* * XXX: we must not use win->hide() here, mostly due sucky xmlrpc-c design. When window * is going to be closed, but we still have pending RPC connections, we will get assertion from * xmlrpc-c. To prevent that, we will change cancel_state and hope thread will catch it before * bugzilla_submit_bug() is called. If it was called, we can't do anything about it, because data * is already send to Bugzilla so we can only log out from it. * * Alternative (and crude) solution would be to comment assertion check xmlrpc_curl_transport.c (977) * and leave cleaning stuff to the kernel. */ pthread_mutex_lock(&runner_mutex); cancel_sending = true; pthread_mutex_unlock(&runner_mutex); } static void progress_timeout(void*) { progress->step(); Fl::repeat_timeout(PROGRESS_STEP_REFRESH, progress_timeout); } static void clear_timeouts(void) { Fl::remove_timeout(progress_timeout); } static void report_pipe_cb(int fd, void *) { String s; if(read_string(report_pipe[0], s) <= 0) return; /* check if the message started with 'FAILED' and see it as error */ if(strncmp(s.c_str(), "FAILED", 6) == 0) { win->hide(); const char *str = s.c_str(); str += 6; /* do not show our header */ alert(str); } progress->copy_label(s.c_str()); /* marked as completed successfully */ if(s == "DONE") { win->hide(); message(_("The report was sent successfully. Thank you for your contribution")); /* the only case when data was submitted correctly */ data_submitted = true; return; } /* marked as canceled */ if(s == "CANCELED") win->hide(); } static void* thread_worker(void *d) { int ret; BugContent *data = (BugContent*)d; bool should_cancel; ret = bugzilla_login(bdata, EDE_BUGZILLA_USER, EDE_BUGZILLA_PASS); if(ret == -1) { write_string_fail(report_pipe[1], _("Unable to properly login. Probably the host is temporarily down or your are not connected to the internet")); goto done; } /* wait some time if user decided to press 'Cancel' */ sleep(1); pthread_mutex_lock(&runner_mutex); should_cancel = cancel_sending; pthread_mutex_unlock(&runner_mutex); if(should_cancel) { write_string(report_pipe[1], "CANCELED"); goto done; } write_string(report_pipe[1], _("Submitting the report...")); ret = bugzilla_submit_bug(bdata, "ede", "general", data->bug_title, "unspecified", data->bug_content, "All", "All", "P5", "normal"); if(ret == -1) { write_string_fail(report_pipe[1], _("Unable to properly submit the data. Please try again")); goto done; } write_string(report_pipe[1], _("Logging out...")); bugzilla_logout(bdata); write_string(report_pipe[1], "DONE"); done: delete data; pthread_exit(NULL); /* shutup compiler */ return NULL; } static void perform_send(const char *title, const char *content) { pthread_t thread; /* * Must be allocated since this function could quit before thread was created * of before thread could acceess the arguments. Freeing is done in that thread. */ BugContent *c = new BugContent; c->bug_title = title; c->bug_content = content; /* Create joinable thread. Some implementations prefer this explicitly was set. */ pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); int rc = pthread_create(&thread, &attr, thread_worker, (void*)c); if(rc) { alert(_("Unable to create additional thread")); /* close everything */ win->hide(); } pthread_attr_destroy(&attr); } bool bugzilla_send_with_progress(const char *title, const char *content) { data_submitted = false; cancel_sending = false; bdata = bugzilla_new(EDE_BUGZILLA_XMLRPC_URL); if(!bdata) { alert(_("Unable to initialize bugzilla interface!")); return false; } if(pipe(report_pipe) != 0) { alert(_("Unable to initialize communication pipe")); return false; } /* prepare mutex */ pthread_mutex_init(&runner_mutex, NULL); /* register our callback on pipe */ Fl::add_fd(report_pipe[0], FL_READ, report_pipe_cb); win = new Fl_Double_Window(275, 90, _("Sending report data")); win->begin(); progress = new PulseProgress(10, 20, 255, 25, _("Sending report...")); progress->selection_color((Fl_Color)137); Fl_Button *cancel = new Fl_Button(175, 55, 90, 25, _("&Cancel")); cancel->callback(cancel_cb); win->end(); win->set_modal(); win->show(); progress->label(_("Preparing data...")); Fl::add_timeout(PROGRESS_STEP_REFRESH, progress_timeout); perform_send(title, content); while(win->shown()) Fl::wait(); /* clear pipe callback */ Fl::remove_fd(report_pipe[0]); clear_timeouts(); pthread_mutex_destroy(&runner_mutex); close(report_pipe[0]); close(report_pipe[1]); bugzilla_free(bdata); /* true if we completed all stages */ return data_submitted; }