/* * It's hard to believe, but this code is actually less bloated than * what we had in the beginning. Still, it needs to be rewritten to * have better fault tolerance and a more flexible signal queue. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "dc_settings.h" #include "status_monitor.h" #include "transfer_widget.h" #include "transfer_view.h" #include "debugdlg.h" // Least interval between requesting gdl-list updates. // right now it has to be 1000, otherwise transfer rate calc doesn't work =) #define GDL_UPDATE_INTERVAL (1000) // Least interval between emitting queued signals. #define SIGNAL_EMIT_INTERVAL (4000) // how long to remember a deleted gdl's filename #define DISCARD_THRESHOLD (6000) // how long to wait before resuming a stopped download _for_the_first_time_ #define AUTORESUME_THRESHOLD (30000) // how long to wait _between_ subsequent autoresuming attempts on a file #define AUTORESUME_DELAY (90000) // get timestamp in milliseconds long timestamp() { struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); long s = tv.tv_sec, u = tv.tv_usec; s = ((s-((s/1000000)*1000000))*1000) + (u - (u/1000)*1000); #ifdef _DEBUG //printf( "Timestamp: %lds + %ldmsec -> %ld\n", (long)tv.tv_sec, (long)tv.tv_usec, (long)s ); #endif return (long)s; } transfer_widget::transfer_widget(QWidget *parent, const char* name) : QWidget(parent, name) { intlist_setting wl_dl(settings.get_setting("widthlist_xfer_lv_dl")); intlist_setting wl_q(settings.get_setting("widthlist_xfer_lv_q")); intlist_setting wl_ul(settings.get_setting("widthlist_xfer_lv_ul")); intlist_setting wl_h(settings.get_setting("widthlist_xfer_lv_h")); QGridLayout* layout = new QGridLayout( this, 1, 1 ); tabs = new QTabWidget( this, "Transfer tabs" ); // List views lv[T_DL] = new QListView(this,"Downloads list"); lv[T_DL]->setAllColumnsShowFocus(true); lv[T_DL]->setRootIsDecorated(true); lv[T_DL]->addColumn("File"); lv[T_DL]->addColumn("Size"); lv[T_DL]->addColumn("User"); lv[T_DL]->addColumn("Status"); lv[T_DL]->addColumn("Remaining"); lv[T_DL]->addColumn("Transfer Rate"); lv[T_DL]->addColumn("Time Left"); lv[T_Q] = new QListView(this,"Queue list"); lv[T_Q]->setAllColumnsShowFocus(true); lv[T_Q]->setRootIsDecorated(true); lv[T_Q]->addColumn("File"); lv[T_Q]->addColumn("Size"); lv[T_Q]->addColumn("User"); lv[T_Q]->addColumn("Status"); lv[T_Q]->addColumn("Target Directory"); lv[T_UL] = new QListView(this,"Uploads list"); lv[T_UL]->setAllColumnsShowFocus(true); lv[T_UL]->setRootIsDecorated(false); lv[T_UL]->addColumn("File"); lv[T_UL]->addColumn("Size"); lv[T_UL]->addColumn("User"); lv[T_UL]->addColumn("Status"); lv[T_UL]->addColumn("Remaining"); lv[T_UL]->addColumn("Transfer Rate"); lv[T_UL]->addColumn("Time Left"); lv[T_H] = new QListView(this,"History list"); lv[T_H]->setAllColumnsShowFocus(true); lv[T_H]->setRootIsDecorated(true); lv[T_H]->addColumn("File"); lv[T_H]->addColumn("Count"); lv[T_H]->addColumn("Size"); lv[T_H]->addColumn("User"); // History's upload & download root items. history_dl = new QListViewItem(lv[T_H], "Downloaded", "0", "0 Byte", ""); history_dl->setExpandable(true); history_ul = new QListViewItem(lv[T_H], "Uploaded", "0", "0 Byte", ""); history_ul->setExpandable(true); // restore column widths from previous session // function defined in util.cc set_lv_widths(lv[T_DL], wl_dl); set_lv_widths(lv[T_Q], wl_q); set_lv_widths(lv[T_UL], wl_ul); set_lv_widths(lv[T_H], wl_h); // Tabs. Make sure the order is same as in enum T_TABS, or menus will not work. tabs->addTab( lv[T_DL], "&Downloads"); tabs->addTab( lv[T_Q], "File &Queue"); tabs->addTab( lv[T_UL], "&Uploads"); tabs->addTab( lv[T_H], "&History"); tabs->setCurrentPage(T_Q); layout->addWidget(tabs,0,0); // Popup menus & actions //text, menuText, QKeySequence accel, parent, name menu[T_DL] = new QPopupMenu(this, "DownloadPopupMenu"); menu[T_DL]->insertItem("&Pause download", PM_CloseGDL); menu[T_DL]->insertSeparator(); menu[T_DL]->insertItem("&Close connection", PM_CloseDL); menu[T_DL]->insertItem("Get user's &file list", PM_GetFileList); menu[T_DL]->insertSeparator(); menu[T_DL]->insertItem("Pause a&ll downloads", PM_CloseAllGDL); menu[T_Q] = new QPopupMenu(this, "QueuePopupMenu"); menu[T_Q]->insertItem("&Re-activate download", PM_ActivateGDL); menu[T_Q]->insertItem("Set target &directory", PM_SetTgtDir); menu[T_Q]->insertItem("&Kill download", PM_DeleteGDL); menu[T_Q]->insertSeparator(); menu[T_Q]->insertItem("Remove &source", PM_CloseDL); menu[T_Q]->insertItem("Get user's &file list", PM_GetFileList); menu[T_Q]->insertSeparator(); menu[T_Q]->insertItem("Re-&activate all downloads", PM_ActivateAllGDL); menu[T_Q]->insertItem("Ki&ll all downloads", PM_DeleteAllGDL); menu[T_UL] = new QPopupMenu(this, "UploadPopupMenu"); menu[T_UL]->insertItem("&Close connection", PM_CloseUL); menu[T_UL]->insertItem("Get user's &file list", PM_GetFileList); menu[T_H] = new QPopupMenu(this, "HistoryPopupMenu"); menu[T_H]->insertItem("&Clear history", PM_ClearHistory); menu[T_H]->insertSeparator(); menu[T_H]->insertItem("Get user's &file list", PM_GetFileList); // Connect signals/slots for menus for (int t=0; tstart(SIGNAL_EMIT_INTERVAL, false); // Set up the timestamp for GDLs and download sources. gdlinfo_timestamp = timestamp(); is_updating = false; // we are not getting update at creation time } // save column widths of list views void transfer_widget::save_layout() { // function defined in util.cc intlist_setting wl_dl; intlist_setting wl_ul; intlist_setting wl_q; intlist_setting wl_h; get_lv_widths(lv[T_DL], &wl_dl); get_lv_widths(lv[T_UL], &wl_ul); get_lv_widths(lv[T_Q], &wl_q); get_lv_widths(lv[T_H], &wl_h); settings.set_setting("common","widthlist_xfer_lv_dl", wl_dl.toString()); settings.set_setting("common","widthlist_xfer_lv_ul", wl_ul.toString()); settings.set_setting("common","widthlist_xfer_lv_q", wl_q.toString()); settings.set_setting("common","widthlist_xfer_lv_h", wl_h.toString()); settings.save_to_file(); } // remove any signals that related to GDLs with given ID from the signal queue void transfer_widget::remove_queued_signals(unsigned int id) { list::iterator qi = dl_signal_queue.begin(); while (qi != dl_signal_queue.end()) { list::iterator i = qi++; if ((*i).id == id) dl_signal_queue.erase(i); } } // remove any signals that related to GDLs with given local filename from the signal queue void transfer_widget::remove_queued_signals(const QString &name) { list::iterator qi = dl_signal_queue.begin(); while (qi != dl_signal_queue.end()) { list::iterator i = qi++; if ((*i).local_file == name) dl_signal_queue.erase(i); } } void transfer_widget::find_unattached_gdls() { static int count = 0; if (count<999) count++; if (count!=10) // execute this function only once: the 10th time it's called return; debugWin->print("Looking for unattached GDLs...\n"); unsigned int dnum = 0; if (gdl_item::gdl_id_counter<1) gdl_item::gdl_id_counter = 1; QDir dir(QString(settings.get_setting("dl_dir")+"/GDL/")); dir.refresh(); while (dnumcurrentPageIndex(); if (!lastClicked[page]) return; // Set up variables. QString username; gdl_item *gdl = NULL; transfer_item *udl = NULL; if (page != T_H) { gdl = ((transfer_lvi*)lastClicked[page])->GDL(); udl = ((transfer_lvi*)lastClicked[page])->UDL(); if (!gdl && !udl) return; if (udl) username = udl->user_name; else username = ""; // gdl } else username = lastClicked[page]->text(3); // history list item // // Emit appropriate signal. // switch(i) { case PM_CloseGDL: debugWin->print("PM_CloseGDL\n"); if (gdl) { autoresumed_gdls.insert(gdl->filename(), -1); // -1 == don't resume emit detach_gdl(gdl->id()); trigger_gdl_update(); } break; case PM_CloseAllGDL: { debugWin->print("PM_CloseAllGDL\n"); GDLsMap::iterator i = active_gdls.begin(); while(i != active_gdls.end()) { if ((*i)!=NULL) { autoresumed_gdls.insert((*i)->filename(), -1); //-1 don't resume emit detach_gdl((*i)->id()); } i++; } trigger_gdl_update(); } break; case PM_CloseDL: debugWin->print("PM_CloseDL\n"); emit close_dl(udl->id, username, udl->file_name); if (page==T_DL) udl->status = X_CLOSING; else { GDLsMap::iterator j; // Mark corresponding download in DL-tab as closing, if exists. What a mess. j = active_gdls.find(udl->id); if (j!=active_gdls.end()) { list::iterator k = (*j)->sources_first(); bool done=false; while (k != (*j)->sources_last() && !done) { if ((*k) == *udl) { (*k).status = X_CLOSING; done = true; } else k++; } } // Mark queued-item source for removal udl->status = X_REMOVED; // Delete source in queue that was marked for removal, using the // parent gdl_item's method delete_removed(). j = queued_gdls.find(udl->id); if (j!=queued_gdls.end()) (*j)->delete_removed(); } trigger_gdl_update(); break; case PM_ActivateGDL: debugWin->print("PM_ActivateGDL\n"); if (gdl) { // resume immediately resume_download(gdl, true); } trigger_gdl_update(); break; case PM_ActivateAllGDL: { debugWin->print("PM_ActivateAllGDL\n"); GDLsMap::iterator iq = queued_gdls.begin(); while(iq != queued_gdls.end()) { if ((*iq)!=NULL) { if ((*iq)->status_str() != "Downloading" && (*iq)->status_str() != "Trying") { // clear "autoresumed" flag NameMap::iterator it = autoresumed_gdls.find((*iq)->filename()); if (it != autoresumed_gdls.end()) autoresumed_gdls.remove(it); // enqueue commands resume_download((*iq), false); } } iq++; } trigger_gdl_update(); } break; case PM_DeleteGDL: { debugWin->print("PM_DeleteGDL\n"); // close emit close_gdl(gdl->id()); // remove related signals from queued signals if they are there remove_queued_signals(gdl->id()); remove_queued_signals(gdl->filename()); // remove from queue GDLsMap::iterator q = queued_gdls.find(gdl->id()); if (q != queued_gdls.end()) { emit sig_gdl_removed(gdl->id()); // talk to status monitor queued_gdls.remove(q); } deleted_gdls[gdl->filename()] = timestamp(); delete gdl; trigger_gdl_update(); } break; case PM_DeleteAllGDL: { debugWin->print("PM_DeleteAllGDL\n"); GDLsMap::iterator q = queued_gdls.begin(); while (q != queued_gdls.end()) { emit close_gdl((*q)->id()); emit sig_gdl_removed((*q)->id()); // remove related signals from queued signals if they are there remove_queued_signals((*q)->id()); remove_queued_signals((*q)->filename()); // remember that it was deleted deleted_gdls[(*q)->filename()] = timestamp(); // remove from queue queued_gdls.remove(q); delete (*q); q = queued_gdls.begin(); } trigger_gdl_update(); } break; case PM_SetTgtDir: if (gdl!=NULL) { QString dir = selectDlDirDlg(); gdl->set_target_directory(dir); } break; case PM_CloseUL: debugWin->print("PM_CloseUL\n"); udl->status = X_CLOSING; emit close_ul(udl->id); trigger_gdl_update(); break; case PM_ClearHistory: debugWin->print("PM_ClearHistory\n"); // I'm not gonna traverse their siblings. Just kill and resurrect delete history_ul; delete history_dl; // History's upload & download root items. history_dl = new QListViewItem(lv[T_H], "Downloaded", "0", "0 Byte", ""); history_dl->setExpandable(true); history_ul = new QListViewItem(lv[T_H], "Uploaded", "0", "0 Byte", ""); history_ul->setExpandable(true); break; case PM_GetFileList: debugWin->print("PM_GetFileList\nUser: " + username + "\n"); emit getUserFileList(username); break; default: debugWin->print("In transfer_widget::menuClicked: Unhandled popup menu item index!\n"); } } void transfer_widget::showMenu(QListViewItem* i, const QPoint &p, int) { if(i) { int page = tabs->currentPageIndex(); QString uname; if (page!=T_H) uname = ((transfer_lvi *)i)->text(transfer_lvi::CT_UNAME); else uname = i->text(3); lastClicked[page] = (QListViewItem*)i; // Enable/disable appropriate menu choices depending on what kind // of item has been selected, and change menu text according to // contents of the item. switch(page) { case T_Q: if (((transfer_lvi *)i)->GDL()) { menu[page]->setItemEnabled(PM_ActivateGDL, true); menu[page]->setItemEnabled(PM_SetTgtDir, true); menu[page]->setItemEnabled(PM_DeleteGDL, true); menu[page]->changeItem(PM_CloseDL, "&Remove source"); menu[page]->setItemEnabled(PM_CloseDL, false); menu[page]->changeItem(PM_GetFileList, "Get user's &file list"); menu[page]->setItemEnabled(PM_GetFileList, false); } else { menu[page]->setItemEnabled(PM_ActivateGDL, false); menu[page]->setItemEnabled(PM_SetTgtDir, false); menu[page]->setItemEnabled(PM_DeleteGDL, false); menu[page]->changeItem(PM_CloseDL, "&Remove source " + uname); menu[page]->setItemEnabled(PM_CloseDL, true); menu[page]->changeItem(PM_GetFileList, "Get " + uname + "'s &file list"); menu[page]->setItemEnabled(PM_GetFileList, true); } menu[page]->setItemEnabled(PM_DeleteAllGDL, true); break; case T_DL: if (((transfer_lvi *)i)->GDL()) { menu[page]->setItemEnabled(PM_CloseGDL, true); menu[page]->changeItem(PM_CloseDL, "&Close connection"); menu[page]->setItemEnabled(PM_CloseDL, false); menu[page]->changeItem(PM_GetFileList, "Get user's &file list"); menu[page]->setItemEnabled(PM_GetFileList, false); } else { menu[page]->setItemEnabled(PM_CloseGDL, false); menu[page]->changeItem(PM_CloseDL, "&Close connection to " + uname); menu[page]->setItemEnabled(PM_CloseDL, true); menu[page]->changeItem(PM_GetFileList, "Get " + uname + "'s &file list"); menu[page]->setItemEnabled(PM_GetFileList, true); } menu[page]->setItemEnabled(PM_CloseAllGDL, true); break; case T_UL: menu[page]->changeItem(PM_CloseUL, "&Close connection to " + uname); menu[page]->setItemEnabled(PM_CloseUL, true); menu[page]->changeItem(PM_GetFileList, "Get " + uname + "'s &file list"); menu[page]->setItemEnabled(PM_GetFileList, true); break; case T_H: if (i->isExpandable()) { menu[page]->setItemEnabled(PM_ClearHistory, true); menu[page]->changeItem(PM_GetFileList, "Get user's &file list"); menu[page]->setItemEnabled(PM_GetFileList, false); } else { menu[page]->setItemEnabled(PM_ClearHistory, true); menu[page]->changeItem(PM_GetFileList, "Get " + uname + "'s &file list"); menu[page]->setItemEnabled(PM_GetFileList, true); } break; default: return; } menu[page]->popup(p); } } bool transfer_widget::is_user_busy(const QString &uname) const { GDLsMap::const_iterator i = active_gdls.begin(); while (i!=active_gdls.end()) { list::iterator k = (*i)->sources_first(); while (k != (*i)->sources_last()) { if ((*k).user_name == uname && ((*k).status != X_CLOSING || (*k).status != X_CLOSED)) return true; else k++; } i++; } return false; } bool transfer_widget::is_gdl_active(const QString &fname) const { for (GDLsMap::const_iterator it=active_gdls.begin(); it!=active_gdls.end(); ++it) if ( (*it)->filename() == fname ) { if ( (*it)->status_str() == "Trying" || (*it)->status_str() == "Downloading" ) return true; // is active else return false; // found, but isn't really active } return false; // not found in active list at all } void transfer_widget::restart_download(const QString &fname) { gdl_item *gdl=NULL; // See if a GDL for this file is in the queue. GDLsMap::iterator qi = queued_gdls.begin(); while (qi!=queued_gdls.end() && gdl==NULL) if ((*qi)->filename() != fname) qi++; else gdl = (*qi); if (gdl==NULL) return; // Destroy the GDL, if still running. emit close_gdl(gdl->id()); // Re-add each source that we know about. list::iterator i = gdl->sources_first(); while (i != gdl->sources_last()) { if (i==gdl->sources_first()) enqueue_signal(gdl->id(), (*i).user_name, (*i).file_name, gdl->filename(), (*i).file_size); else enqueue_signal(gdl->id(), (*i).user_name, (*i).file_name, (*i).file_size); i++; } } // detach gdl if active, then attach again void transfer_widget::resume_download(gdl_item *gdl, bool immediately) { if (immediately) { emit detach_gdl(gdl->id()); emit attach_gdl(gdl->filename()); autoresumed_gdls[gdl->filename()] = timestamp(); } else { bool doit=true; NameMap::iterator i=autoresumed_gdls.find(gdl->filename()); if (i!=autoresumed_gdls.end()) { if (i.data()==-1 || (timestamp() - i.data() < AUTORESUME_THRESHOLD)) doit = false; } if (doit) { enqueue_signal(gdl->id(), gdl->filename()); // detach enqueue_signal(gdl->filename()); // attach autoresumed_gdls[gdl->filename()] = timestamp(); } else { debugWin->print(QString("Decided to not resume '%1'\n").arg(gdl->filename())); } } } // // TO DO? Check if user is busy and only emit signals if not. // Maintain list of busy/available users. // // ANSWER: we probably don't need this. DCTC queues DLs anyway. void transfer_widget::download_file(const QString &user, const QString &remote_file, const QString &local_file, unsigned long int fsize) { gdl_item *gdl=NULL; // See if the local_file contains a path, and in that case split the string QString local_file_path; QString local_file_name; int spos = local_file.findRev('/') + 1; // normally index starts from 0 if (spos>0) { int slen = local_file.length(); local_file_path = local_file.left(spos); // include trailing slash local_file_name = local_file.right(slen - spos); } else { local_file_path = ""; local_file_name = local_file; } // make sure we have a filename if (local_file_name.isEmpty()) { debugWin->print("download_file() was called with malformed local_file argument!\n"); return; } // See if this GDL is in the queue. GDLsMap::iterator qi = queued_gdls.begin(); while (qi!=queued_gdls.end() && gdl==NULL) if ((*qi)->filename() != local_file_name || (*qi)->filesize()!=fsize) qi++; else gdl = (*qi); // If the GDL is not in the queue, add it. if (gdl==NULL) { // create storage item gdl = new gdl_item(local_file_name, fsize); // add it to the queue queued_gdls.insert(gdl->id(), gdl); // create view item for the GDL ((transfer_lvi*)new transfer_lvi(lv[T_Q]))->set_layout(transfer_lvi::Q_GDL, gdl); // If we had to create a new GDL, we will emit 'start_gdl' signal. enqueue_signal(gdl->id(), user, remote_file, local_file_name, fsize); // And we'll talk to status monitor emit sig_gdl_added(gdl->id(), local_file_name, fsize); } else // If GDL already existed, we will emit 'add_gdl_source' signal. enqueue_signal(gdl->id(), user, remote_file, fsize); // Add user+file as a new source. transfer_item u(gdl->id(), user, remote_file); gdl->add_source(u); // Set target download directory for the GDL gdl->set_target_directory(local_file_path); attach_view_items(gdl, transfer_lvi::Q_GDL_SRC); // prevent immediate auto-resuming autoresumed_gdls[gdl->filename()] = timestamp(); // prevent auto-deletion if (deleted_gdls.find(gdl->filename()) != deleted_gdls.end()) deleted_gdls.remove(deleted_gdls.find(gdl->filename())); trigger_gdl_update(); } void transfer_widget::new_gdl_source(const unsigned int gdl_id, const QString &user, const QString &remote_file, const unsigned long int fsize) { gdl_item *gdl=NULL; // See if there is a matching GDL in the queue. GDLsMap::iterator qi = queued_gdls.find(gdl_id); if (qi==queued_gdls.end()) return; gdl = (*qi); // emit 'add_gdl_source' signal. enqueue_signal(gdl->id(), user, remote_file, fsize); // Add user+file as a new source. transfer_item u(gdl->id(), user, remote_file); gdl->add_source(u); attach_view_items(gdl, transfer_lvi::Q_GDL_SRC); trigger_gdl_update(); } void transfer_widget::attach_view_items(gdl_item *gdl, const int layout_type) { // Run through all sources and see if any need a view-item attached. list::iterator src = gdl->sources_first(); while (src!=gdl->sources_last()) { if ((*src).QLVI() == 0) ((transfer_lvi*)new transfer_lvi(gdl->QLVI()))->set_layout(layout_type, &(*src)); src++; } } QString transfer_widget::update_gdl_in_queue(GDLsMap &gdls, QListView *tgt_lv, const int layout_main_type, const int layout_item_type, const bool erase_outdated, const unsigned int id, const QString &fname, const ULLINT fsize, const ULLINT foffs, const ULLINT frecv, const unsigned int time0, const ULLINT rcv10, const QString &running, const QString &complete) { gdl_item *gdl=NULL; GDLsMap::iterator qi = gdls.find( id ); if (qi != gdls.end()) { gdl = (*qi); } else { qi = gdls.begin(); while (qi!=gdls.end() && gdl==NULL) if ((*qi)->filename() != fname || (*qi)->filesize()!=fsize) qi++; else gdl = (*qi); } // If the GDL is not in the queue, add it. if (gdl==NULL) { // if the gdl has been previously deleted, skip it NameMap::iterator it = deleted_gdls.find(fname); if (it!=deleted_gdls.end()) { if ((timestamp() - (*it)) < DISCARD_THRESHOLD) { // can be a little dangerous, auto-killing gdls... debugWin->print(QString("Auto-killing zombie GDL '%1'\n").arg(fname)); remove_queued_signals(fname); emit close_gdl(id); return QString("Closed"); } else { deleted_gdls.remove(it); } } // create storage item gdl = new gdl_item( id, fname, fsize, foffs, frecv, time0, rcv10 ); // add it to the queue gdls.insert(gdl->id(), gdl); // create view item ((transfer_lvi*)new transfer_lvi(tgt_lv))->set_layout(layout_main_type, gdl); // now a little cheat in order to find out if this is a queued or // active list so we can tell status monitor... if (layout_main_type==transfer_lvi::A_GDL) emit sig_gdl_active(); // inactive gdl has turned active } else { // has the ID changed? need to re-insert the gdl into the map unsigned int old_id = gdl->id(); if (old_id != id) { gdls.remove(qi); gdls.insert(id, gdl); remove_queued_signals(old_id); remove_queued_signals(gdl->filename()); } } // Now update the queued gdl item. gdl->update(gdlinfo_timestamp, id, fsize, foffs, frecv, time0, rcv10, running, complete); // Remove or Mark as queued all sources that haven't been updated. if (erase_outdated) gdl->remove_outdated(gdlinfo_timestamp); else gdl->mark_outdated(gdlinfo_timestamp); // Run through each source and see if any need view-items attached. attach_view_items(gdl, layout_item_type); return gdl->status_str(); } void transfer_widget::update_gdl_info(const QString &info) { // I know that this parsing stuff would look better if it was done // somewhere else, like, say, in transfer_storage.cc. But it's here // now, and now I've got better things to do than shuffling chunks // of code around. unsigned int id = (info.section('|', 0,0)).toUInt(); QString fname = info.section('|', 1,1); ULLINT fsize = (info.section('|', 2,2)).toULong(); ULLINT foffs = (info.section('|', 3,3)).toULong(); ULLINT frecv = (info.section('|', 4,4)).toULong(); unsigned int time0 = (info.section('|', 5,5)).toUInt(); ULLINT rcv10 = (info.section('|', 6,6)).toULong(); QString running="", complete=""; int i=10; while(info.section('|', i,i)!="") { running += info.section('|', i,i) + "|"; i++; } running += ""; i++; while(info.section('|', i,i)!="") { complete += info.section('|', i,i) + "|"; i++; } complete += ""; QString status = update_gdl_in_queue(queued_gdls, lv[T_Q], transfer_lvi::Q_GDL, transfer_lvi::Q_GDL_SRC, false, id, fname, fsize, foffs, frecv, time0, rcv10, running, complete); if (status!="Queued" && status!="Closed" && status!="DCTC-Queued" && status!="Waiting") { update_gdl_in_queue(active_gdls, lv[T_DL], transfer_lvi::A_GDL, transfer_lvi::A_GDL_SRC, true, id, fname, fsize, foffs, frecv, time0, rcv10, running, complete); // update resume time autoresumed_gdls[fname] = timestamp(); } } // // Checks if a downloaded file exists and no longer in use, and moves // the file to target directory (if it has been specified). Returns 0 // if the file doesn't exist or can't be moved to specified target // directory, otherwise returns size of the file. // ULLINT transfer_widget::process_completed_gdl(gdl_item *gdl) { QString tgt_file = settings.get_setting("dl_dir") + gdl->filename(); QFileInfo fi(tgt_file); fi.refresh(); ULLINT fsize = fi.size(); // is the file download complete? Check the size! if (fi.exists() && fi.isWritable() && fsize>=gdl->filesize()) { // see if we need to move the file to a different directory. QString tgt_dir_name = gdl->get_target_directory(); if (tgt_dir_name.isEmpty()) { // no need to move the file - all done return fsize; } else { // need to move the file QString new_abs_file_name = tgt_dir_name + gdl->filename(); // trailing slash is already in tgt_dir QString old_abs_file_name = fi.absFilePath(); QDir dsrc(fi.dir()); QDir dtgt(tgt_dir_name); // create target directory if it doesn't exist anymore if (!dtgt.exists()) { // Go through each subdirectory level starting from root and // create next subdir if it doesn't exist unsigned int spos = 1; // avoid creating root directory :p while (sposfilename()), "Abort Move", bytesleft, 0, "mvgdlprg", TRUE ); while (bytesleft>0) { // update progress bar progress.setProgress(totalwritten); // keep event processing running qApp->processEvents( ); // read a block ULLINT bytestowrite = fsrc.readBlock(buf, sizeof(buf)); // write the block long int byteswritten = ftgt.writeBlock(buf, bytestowrite); // See if anything has gone wrong or if user decided to // abort the operation if (byteswritten==-1 || progress.wasCancelled()) { ftgt.remove(); fsrc.close(); progress.setProgress(fi.size()); if (byteswritten==-1) QMessageBox::warning(0, "DC-QT: Error moving file", QString("Can't move downloaded file to directory\n\'%1\'\nThe target drive may be out of free space.") .arg(tgt_dir_name), QMessageBox::Ok, QMessageBox::NoButton); return fsize; // again, pretend to succeed } // if nothing is wrong, update byte counter and copy next chunk if (bytesleft < (ULLINT)byteswritten) bytesleft = 0; else bytesleft -= byteswritten; totalwritten += byteswritten; }// end of while-loop // Success! Remove the original file. ftgt.close(); fsrc.remove(); // close progress dialog progress.setProgress(fi.size()); // all done return fsize; } } } } } return 0; } // // activated when gdl info list ends // void transfer_widget::update_gdl_end() { // int autoresume_delay = settings.get_setting("autoresume_timeout"); // First check any files that we thought could be complete in the previous iteration NameMap::iterator ci = completed_gdl_list.begin(); while (ci != completed_gdl_list.end()) { // Find gdl by id. GDLsMap::iterator k = active_gdls.find(ci.data()), j = queued_gdls.find(ci.data()); // Remove from queued downloads if download is complete. if (j!=queued_gdls.end()) { QString first_user(""); if ((*j)->sources_first() != (*j)->sources_last()) { list::iterator l = (*j)->sources_first(); first_user = (*l).user_name; } // see if it's ready ULLINT readysize = process_completed_gdl((*j)); // is download complete? if (readysize>0) { // if download is complete and cleaned up and moved to target // directory, remove the gdl item and move filename to history move_to_history(history_dl, (*j)->get_target_directory() + (*j)->filename(), first_user, readysize); // talk to status monitor emit sig_gdl_removed((*j)->id()); // kill any loose signals that may hang in the queue remove_queued_signals((*j)->id()); remove_queued_signals((*j)->filename()); // remember the filename as deleted to prevent zombie-GDLs from reappearing deleted_gdls[(*j)->filename()] = timestamp(); // remove from queue if it's there queued_gdls.remove(j); delete (*j); // make sure no popup-menus get fucked up pointers lastClicked[T_Q] = NULL; } else { // if not all cleaned up, just mark outdated sources in queue and schedule for resume if (j!=queued_gdls.end()) { (*j)->mark_outdated(gdlinfo_timestamp); debugWin->print(QString("Checking if %1 should be resumed...").arg((*j)->filename())); long ts = timestamp(); NameMap::iterator it = autoresumed_gdls.find((*j)->filename()); if (it == autoresumed_gdls.end()) { autoresumed_gdls[(*j)->filename()] = ts; } else ts = it.data(); long delta = (timestamp() - ts); if ( !first_user.isEmpty() && !is_user_busy(first_user) && delta > AUTORESUME_THRESHOLD ) { resume_download((*j), false); } else { debugWin->print(QString("Not resuming yet. Threshold: %1 sec\n").arg((AUTORESUME_THRESHOLD-delta)/1000)); } } } } // Remove from active downloads if it's there, complete or not. if (k!=active_gdls.end()) { // tell status monitor that one of the active gdls is no longer active emit sig_gdl_queued(); active_gdls.remove(k); delete (*k); // make sure no popup-menus get fucked up pointers lastClicked[T_DL] = NULL; } // move on to next item in map ci++; } // We need to re-create from scratch the list of stopped GDLs. completed_gdl_list.clear(); // Now check if there are any un-updated downloads and store their // gdl-id in completed_gdl_list to check on next iteration. Look in // the queued list, in case the gdl is no longer in the active list. // // Also try to resume any waiting downloads that have waited too long. GDLsMap::iterator i = queued_gdls.begin(); while (i!=queued_gdls.end()) { // Hasn't been updated, and not because we queue it ourselves? Then put it in "maybe completed". if ((*i)->timestamp < gdlinfo_timestamp && (*i)->status_str()!="Queued") completed_gdl_list[(*i)->filename()] = (*i)->id(); else // here we check up on waiting, but not outdated, downloads if ((*i)->status_str() == "Waiting") { NameMap::iterator it = autoresumed_gdls.find((*i)->filename()); if (it == autoresumed_gdls.end()) { autoresumed_gdls[(*i)->filename()] = timestamp(); //start countdown } else { if ( (timestamp() - it.data()) > AUTORESUME_DELAY ) //check the time resume_download(*i); } } // move on to next gdl i++; } // Same thing again, but this time look in the active list, in case // the gdl is no longer in the queued list. (e.g. when we delete it // ourselves due to user command). i = active_gdls.begin(); while (i!=active_gdls.end()) { if ((*i)->timestamp < gdlinfo_timestamp) completed_gdl_list[(*i)->filename()] = (*i)->id(); i++; } // redraw the tabs for (int lvnum=0; lvnumtriggerUpdate(); // Set timestamp that we'll be using next iteration. gdlinfo_timestamp = timestamp(); // make us ready to receive next gdl update is_updating = false; if (!(active_gdls.empty() && queued_gdls.empty())) { gdlup_timer->start(GDL_UPDATE_INTERVAL, true); } else { gdlup_timer->start(2 * GDL_UPDATE_INTERVAL, true); } // look for unattached GDLs (only does anything if called for the first time) find_unattached_gdls(); } // // new upload // void transfer_widget::new_ul_info(const QString &info) { debugWin->print(info); QString ulids = info.section('|', 0,0); QString uname = info.section('|', 1,1); QString fname = info.section('|', 2,2); bool cnvOk = false; // Only insert uploads of valid users&files, but not shared file list. if (ulids.isEmpty() || fname.isEmpty() || uname.isEmpty() || uname.find("Shared file list")!=-1) return; unsigned int ul_id = ulids.toUInt(&cnvOk); if (cnvOk) { transfer_item *ul; UploadsMap::iterator i = ul_map.find ( ul_id ); if (i==ul_map.end()) { ul = new transfer_item(ul_id); ((transfer_lvi*)new transfer_lvi(lv[T_UL]))->set_layout(transfer_lvi::UL, ul); ul_map.insert(ul_id, ul); i = ul_map.find ( ul_id ); // talk to status monitor emit sig_ul_added(); } else { ul = (*i); } ul->user_name = uname; // info.section('|', 1, 1); debugWin->print(QString("New upload to: \'%1\'").arg(ul->user_name)); /* this whole thing is strange and ugly, fix it! paths are not relative... but for some stupid reason there's an "UL/" infront of the path/filename... QString rest = info.section('|', 2, 2); int index = rest.findRev('/'); ul->file_name = rest.remove(0,index+1); */ if (fname.find("UL/")!=-1) ul->file_name = fname.remove("UL/"); else ul->file_name = fname; ul->file_size = 0; ul->status = X_WAITING; ul->range = ull_pair(0,1); ul->bytes_done = 0; } } void transfer_widget::update_ul_info(const QString &info) { // aaa:bbb/ccc/ddd/eee // where aaa is an ID identifying the transfer (the same as in // /XFERR), bbb is the upload start position, ccc is the total // file length (not just the part to send), ddd is the amount of // data already sent (not including bbb) and eee is number of // bytes to send. Currently, we have the following relation: // bbb+eee=ccc and 0file_size = (rest.section('/', 1,1)).toULong(); ul->range = ull_pair((rest.section('/', 0,0)).toULong(), ul->file_size); ul->bytes_done = (rest.section('/', 2,2)).toULong(); ul->status = X_UPLOADING; // UGLY! estr fix this!! --rkrd static int ful_counter = 0; if(ful_counter==60) { ful_counter = 0; lv[T_UL]->triggerUpdate(); } ful_counter++; } void transfer_widget::closed_ul_info(const QString &user, const QString &file) { char dbg[256]; if (user.isEmpty() || file.isEmpty()) return; sprintf(dbg, "Closed upload: \"%s\"", file.ascii()); debugWin->print(dbg); // transfer_item *ul; UploadsMap::iterator i = ul_map.begin(); while (i != ul_map.end()) { if ((*i)==NULL || (*i)->user_name.isEmpty() || (*i)->file_name.isEmpty()) { ul_map.remove(i); i = ul_map.begin(); } else { if ((*i)->user_name != user || (*i)->file_name != file) i++; else break; } } if (i == ul_map.end()) return; move_to_history(history_ul, file, user, (*i)->bytes_done); //file_size); ul_map.remove(i); delete (*i); lv[T_UL]->triggerUpdate(); // talk to status monitor emit sig_ul_removed(); debugWin->print("Closed upload matched and moved to history."); } void transfer_widget::move_to_history(QListViewItem *h, const QString &file, const QString &user, const ULLINT size) { (void) new QListViewItem(h, file, "", bytes_to_string(size), user); int i = (h->text(2)).findRev(' '); int factor=1; if ((h->text(2)).endsWith("KB")) factor = 1024; else if ((h->text(2)).endsWith("MB")) factor = 1024*1024; else if ((h->text(2)).endsWith("GB")) factor = 1024*1024*1024; ULLINT total = (ULLINT) (factor * ((h->text(2)).left(i)).toDouble()) + size; h->setText(2, bytes_to_string(total)); // #files h->setText(1, QString::number((h->text(1).toUInt())+1)); // signal QString title, fname; if (file.findRev('/')!=-1) { title = QString(file.right(file.length()-1-file.findRev('/'))); fname = file; } else { title = file; fname = settings.get_setting("dl_dir") + "/" + file;; } emit sig_gdl_complete(title, fname); } // This is like the normal trigger_update, except it waits longer // before emitting the signal and doesn't check status monitor. void transfer_widget::hub_opened() { is_updating = false; if (gdlup_timer->isActive()) return; if (statMon==NULL) return; if (statMon->num_open_hubs() > 0) { gdlup_timer->start(6000, true); debugWin->print("Bootstrapped GDL-updates: 6 sec until first update"); } } void transfer_widget::trigger_gdl_update() { if (gdlup_timer->isActive()) // already have postponed an update? return; if (is_updating) { // already waiting for an update? gdlup_timer->start(GDL_UPDATE_INTERVAL, true); // postpone next update } else { // Don't emit signals unless we have at least one open hub. if (statMon != NULL) if (statMon->num_open_hubs()>0) { is_updating = true; emit request_gdl_update(); } } } void transfer_widget::enqueue_signal(unsigned int id, QString user, QString remote, QString local, ULLINT size) { queued_signal s; s.type = QS_NEWGDL; s.id = id; s.user = user; s.remote_file = remote; s.local_file = local; s.size = size; dl_signal_queue.push_back(s); } void transfer_widget::enqueue_signal(unsigned int id, QString user, QString remote, ULLINT size) { queued_signal s; s.type = QS_NEWSRC; s.id = id; s.user = user; s.remote_file = remote; s.size = size; dl_signal_queue.push_back(s); } void transfer_widget::enqueue_signal(QString local) { queued_signal s; s.type = QS_ATTACH; s.local_file = local; dl_signal_queue.push_back(s); } void transfer_widget::enqueue_signal(unsigned int id, QString local) { queued_signal s; s.type = QS_DETACH; s.id = id; s.local_file = local; dl_signal_queue.push_back(s); } void transfer_widget::emit_queued_signal() { int size = dl_signal_queue.size(); bool more=statMon->num_open_hubs()>0 && size>0; while (more) { queued_signal s = dl_signal_queue.front(); dl_signal_queue.pop_front(); size--; switch (s.type) { case QS_NEWGDL: emit start_gdl(s.id, s.user, s.remote_file, s.local_file, s.size); more = false; break; case QS_NEWSRC: emit add_gdl_source(s.id, s.user, s.remote_file, s.size); more = false; break; // enqueued detach doesn't apply to active gdls case QS_DETACH: { if (!is_gdl_active(s.local_file)) emit detach_gdl(s.id); more = size>0; } break; // enqueued attach doesn't apply to active gdls case QS_ATTACH: { if (!is_gdl_active(s.local_file)) emit attach_gdl(s.local_file); more = false; } break; default: more = size>0; break; } debugWin->print(QString(more ? "Q-Emit: more!\n" : "Q-Emit: no more\n")); } } /* * * $Log: transfer_widget.cc,v $ * Revision 1.27 2004/07/25 21:09:35 estrato * adjusted dl resume delay and dl items display * * Revision 1.26 2004/03/17 16:43:02 olof * connection profiles * * Revision 1.25 2004/03/10 03:16:18 estrato * working on preview widget * * Revision 1.24 2004/03/07 05:24:12 estrato * fixed a bunch of annoying bugs with transfers * * Revision 1.23 2004/03/01 02:18:26 estrato * Finished rewriting settings code * * Revision 1.22 2004/02/27 11:15:02 estrato * fixed autoresuming and attaching to gdls * * Revision 1.21 2004/02/26 22:31:55 estrato * saving more layout info * * Revision 1.20 2004/02/26 12:36:11 estrato * resuming aborted downloads, saving window pos and size * * Revision 1.19 2004/02/26 09:30:39 estrato * fixed connect to running and bug in transferwidget * * Revision 1.18 2004/02/26 07:14:32 estrato * fixed a bug with resume all * * Revision 1.17 2004/02/24 07:58:08 estrato * Added timestamps and nickhighlights, changed icon-mode popup * * Revision 1.16 2004/02/23 11:10:15 estrato * better popup menus * * Revision 1.15 2004/02/22 14:37:34 estrato * Resuming unfinished downloads from previous session * * Revision 1.14 2004/02/22 06:48:53 estrato * Bugfixes, cleaning up * * Revision 1.13 2004/02/21 23:14:49 estrato * Icon mode, bugfixes * * Revision 1.12 2004/02/18 07:58:42 estrato * download to dir, resume download, bugfixes * * Revision 1.11 2004/02/10 08:41:25 estrato * Fixed upload bug (for the 11th time) * * Revision 1.10 2004/01/27 16:09:44 estrato * little fix in upload updates * * Revision 1.9 2004/01/26 18:49:59 estrato * fixed upload bugs * * Revision 1.8 2003/12/08 00:13:40 estrato * Directory downloads * * Revision 1.7 2003/09/09 18:55:59 estrato * bugfix in handling of completed downloads * * Revision 1.6 2003/09/09 18:49:34 estrato * More stable transfer widget * * Revision 1.5 2003/08/22 16:45:20 olof * hola * * Revision 1.4 2003/08/20 15:56:26 estrato * Minor changes to transfer widget, fixed a bug with downloads from user file list. * * Revision 1.3 2003/08/18 12:19:43 estrato * Old transfer dialog now completely replaced, but some functionality is gone. Will add complete functionality in next commit. * * Revision 1.2 2003/08/16 21:34:19 estrato * New transfer dialog update, almost all internal functions done. Will replaceold transferdlg in next commit. * * Revision 1.1 2003/08/14 23:30:45 estrato * New transfer widget, basic storage and view functionality, no signals connected yet. * * * */