/* * This file is part of the KFTPGrabber project * * Copyright (C) 2003-2004 by the KFTPGrabber developers * Copyright (C) 2003-2004 Jernej Kos * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and * NON-INFRINGEMENT. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, * MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. */ #include #include "kftpqueue.h" #include "kftpbookmarks.h" #include "widgets/systemtray.h" #include "kftpqueueprocessor.h" #include "kftpsession.h" #include "misc/config.h" #include "misc/filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KFTPEngine; using namespace KFTPCore::Filter; namespace KFTPQueue { OpenedFile::OpenedFile(TransferFile *transfer) : m_source(transfer->getSourceUrl()), m_dest(transfer->getDestUrl()), m_hash(TQString::null) { // Calculate the file's MD5 hash TQFile file(m_dest.path()); if (!file.open(IO_ReadOnly)) { return; } KMD5 context; if (context.update(file)) m_hash = TQString(context.hexDigest()); file.close(); } bool OpenedFile::hasChanged() { // Compare the file's MD5 hash with stored value TQFile file(m_dest.path()); if (!file.open(IO_ReadOnly)) { return false; } TQString tmp = TQString::null; KMD5 context; if (context.update(file)) tmp = TQString(context.hexDigest()); file.close(); return tmp != m_hash; } UserDialogRequest::UserDialogRequest(TransferFile *transfer, filesize_t srcSize, time_t srcTime, filesize_t dstSize, time_t dstTime) : m_transfer(transfer), m_srcSize(srcSize), m_srcTime(srcTime), m_dstSize(dstSize), m_dstTime(dstTime) { } void UserDialogRequest::sendResponse(FileExistsWakeupEvent *event) { m_transfer->wakeup(event); delete this; } Manager *Manager::m_self = 0; static KStaticDeleter staticManagerDeleter; Manager *Manager::self() { if (!m_self) { staticManagerDeleter.setObject(m_self, new Manager()); } return m_self; } Manager::Manager() : m_topLevel(new QueueObject(this, QueueObject::Toplevel)), m_processingQueue(false), m_feDialogOpen(false), m_defaultFeAction(FE_DISABLE_ACT) { m_topLevel->setId(0); m_lastTQID = 1; m_curDownSpeed = 0; m_curUpSpeed = 0; m_emitUpdate = true; // Create the queue processor object m_queueProc = new KFTPQueueProcessor(this); connect(m_queueProc, SIGNAL(queueComplete()), this, SLOT(slotQueueProcessingComplete())); connect(m_queueProc, SIGNAL(queueAborted()), this, SLOT(slotQueueProcessingAborted())); // Create the queue converter object m_converter = new KFTPQueueConverter(this); } Manager::~Manager() { if (m_self == this) staticManagerDeleter.setObject(m_self, 0, false); } void Manager::stopAllTransfers() { if (isProcessing()) { abort(); } else { QueueObject *i; TQPtrList sites = topLevelObject()->getChildrenList(); for (i = sites.first(); i; i = sites.next()) { if (i->isRunning()) { i->abort(); } else { QueueObject *t; TQPtrList list = i->getChildrenList(); for (t = list.first(); t; t = list.next()) { if (t->isRunning()) t->abort(); } } } } } Transfer *Manager::findTransfer(long id) { // First try the cache QueueObject *object = m_queueObjectCache[TQString::number(id)]; if (!object) { object = m_topLevel->findChildObject(id); m_queueObjectCache.insert(TQString::number(id), object); } return static_cast(object); } Site *Manager::findSite(KURL url, bool noCreate) { // Reset path url.setPath("/"); if (url.isLocalFile()) return NULL; // Find the appropriate site and if one doesn't exist create a new one QueueObject *i; TQPtrList sites = topLevelObject()->getChildrenList(); for (i = sites.first(); i; i = sites.next()) { if (i->getType() == QueueObject::Site) { Site *site = static_cast(i); if (site->getUrl() == url) return site; } } // The site doesn't exist, let's create one if (!noCreate) { Site *site = new Site(topLevelObject(), url); site->setId(m_lastTQID++); emit newSite(site); return site; } return 0; } void Manager::insertTransfer(Transfer *transfer) { // Set id transfer->setId(m_lastTQID++); // Reparent transfer filesize_t size = transfer->getSize(); transfer->addSize(-size); if (transfer->hasParentObject()) transfer->parentObject()->delChildObject(transfer); if (transfer->parent()) transfer->parent()->removeChild(transfer); Site *site = 0; switch (transfer->getTransferType()) { case Download: site = findSite(transfer->getSourceUrl()); break; case Upload: site = findSite(transfer->getDestUrl()); break; case FXP: site = findSite(transfer->getSourceUrl()); break; } site->insertChild(transfer); site->addChildObject(transfer); transfer->addSize(size); emit newTransfer(transfer); if (m_emitUpdate) emit queueUpdate(); } void Manager::insertTransfer(KURLDrag *drag) { // Decode the drag TDEIO::MetaData p_meta; KURL::List p_urls; KURLDrag::decode(drag, p_urls, p_meta); // TODO make support for local drops - eg. from konqueror, where // we get no meta data, so we must get the file info ourselves and // reject remote urls (or show a dialog to ask the user if he // wants to connect to the remote site) // Now we should add transfers for all URLs Transfer *lastTransfer = 0L; KURL::List::iterator end(p_urls.end()); for (KURL::List::iterator i(p_urls.begin()); i != end; ++i) { TQString p_data = p_meta[(*i).htmlURL().local8Bit()]; TQChar type = p_data.at(0); filesize_t size = p_data.section(':', 1, 1).toULongLong(); KURL sourceUrl = (*i); KURL destinationUrl = KURL(p_meta["DestURL"]); destinationUrl.addPath(sourceUrl.fileName()); // Skip where both files are local if (sourceUrl.isLocalFile() && destinationUrl.isLocalFile()) continue; lastTransfer = spawnTransfer(sourceUrl, destinationUrl, size, type == 'D', true, true, 0L, true); } // Execute the transfer if set in configuration if (!KFTPCore::Config::queueOnDND() && lastTransfer) static_cast(lastTransfer->parentObject())->delayedExecute(); } Transfer *Manager::spawnTransfer(KURL sourceUrl, KURL destinationUrl, filesize_t size, bool dir, bool ignoreSkip, bool insertToQueue, TQObject *parent, bool noScan) { const ActionChain *actionChain = Filters::self()->process(sourceUrl, size, dir); if (!ignoreSkip && (actionChain && actionChain->getAction(Action::Skip))) return 0; // Determine transfer type TransferType type; if (sourceUrl.isLocalFile()) type = Upload; else if (destinationUrl.isLocalFile()) type = Download; else type = FXP; // Should we lowercase the destination path ? if (actionChain && actionChain->getAction(Action::Lowercase)) destinationUrl.setPath(destinationUrl.directory() + "/" + destinationUrl.fileName().lower()); // Reset a possible preconfigured default action setDefaultFileExistsAction(); if (!parent) parent = this; Transfer *transfer = 0L; if (dir) transfer = new TransferDir(parent); else { transfer = new TransferFile(parent); transfer->addSize(size); } transfer->setSourceUrl(sourceUrl); transfer->setDestUrl(destinationUrl); transfer->setTransferType(type); if (insertToQueue) { insertTransfer(transfer); } else { transfer->setId(m_lastTQID++); emit newTransfer(transfer); } if (dir && !noScan) { // This is a directory, we should scan the directory and add all files/dirs found // as parent of current object KFTPSession::Session *session = KFTPSession::Manager::self()->spawnRemoteSession(KFTPSession::IgnoreSide, sourceUrl, 0, true); session->scanDirectory(transfer); } return transfer; } void Manager::removeTransfer(Transfer *transfer, bool abortSession) { if (!transfer) return; transfer->abort(); long id = transfer->getId(); long sid = transfer->parentObject()->getId(); // Remove transfer from cache m_queueObjectCache.remove(TQString::number(id)); // Should the site be removed as well ? QueueObject *site = 0; if (transfer->parentObject()->getType() == QueueObject::Site && transfer->parentObject()->getChildrenList().count() == 1) site = transfer->parentObject(); // Signal destruction & delete transfer transfer->faceDestruction(abortSession); delete transfer; if (site) { delete site; emit siteRemoved(sid); } emit transferRemoved(id); if (m_emitUpdate) emit queueUpdate(); } void Manager::revalidateTransfer(Transfer *transfer) { QueueObject *i = transfer; while (i) { if (i->parentObject() == topLevelObject()) break; i = i->parentObject(); } // We have the site Site *curSite = static_cast(i); Site *site = 0; switch (transfer->getTransferType()) { case Download: site = findSite(transfer->getSourceUrl()); break; case Upload: site = findSite(transfer->getDestUrl()); break; case FXP: site = findSite(transfer->getSourceUrl()); break; } // If the sites don't match, reparent transfer if (site != curSite) { transfer->parentObject()->delChildObject(transfer); transfer->parent()->removeChild(transfer); site->insertChild(transfer); site->addChildObject(transfer); emit transferRemoved(transfer->getId()); emit newTransfer(transfer); if (curSite->getChildrenList().count() == 0) { emit siteRemoved(curSite->getId()); curSite->deleteLater(); } } } void Manager::removeFailedTransfer(FailedTransfer *transfer) { // Remove the transfer and signal removal m_failedTransfers.remove(transfer); emit failedTransferRemoved(transfer->getTransfer()->getId()); delete transfer; } void Manager::clearFailedTransferList() { // Clear the failed transfers list FailedTransfer *transfer; TQPtrListIterator i(m_failedTransfers); while ((transfer = i.current()) != 0) { ++i; removeFailedTransfer(transfer); } } void Manager::moveTransferUp(QueueObject *object) { object->parentObject()->moveChildUp(object); if (m_emitUpdate) emit queueUpdate(); } void Manager::moveTransferDown(QueueObject *object) { object->parentObject()->moveChildDown(object); if (m_emitUpdate) emit queueUpdate(); } void Manager::moveTransferTop(QueueObject *object) { object->parentObject()->moveChildTop(object); if (m_emitUpdate) emit queueUpdate(); } void Manager::moveTransferBottom(QueueObject *object) { object->parentObject()->moveChildBottom(object); if (m_emitUpdate) emit queueUpdate(); } bool Manager::canBeMovedUp(QueueObject *object) { return object ? object->parentObject()->canMoveChildUp(object) : false; } bool Manager::canBeMovedDown(QueueObject *object) { return object ? object->parentObject()->canMoveChildDown(object) : false; } void Manager::doEmitUpdate() { m_curDownSpeed = 0; m_curUpSpeed = 0; topLevelObject()->removeMarkedTransfers(); // Get download/upload speeds QueueObject *i; TQPtrList sites = topLevelObject()->getChildrenList(); for (i = sites.first(); i; i = sites.next()) { QueueObject *t; TQPtrList list = i->getChildrenList(); for (t = list.first(); t; t = list.next()) { KFTPQueue::Transfer *tmp = static_cast(t); switch (tmp->getTransferType()) { case Download: m_curDownSpeed += tmp->getSpeed(); break; case Upload: m_curUpSpeed += tmp->getSpeed(); break; case FXP: { m_curDownSpeed += tmp->getSpeed(); m_curUpSpeed += tmp->getSpeed(); break; } } } } // Emit global update to all GUI objects emit queueUpdate(); } void Manager::start() { if (m_processingQueue) return; m_processingQueue = true; // Now, go trough all queued files and execute them - try to do as little server connects // as possible m_queueProc->startProcessing(); } void Manager::abort() { m_processingQueue = false; // Stop further queue processing m_queueProc->stopProcessing(); emit queueUpdate(); } void Manager::slotQueueProcessingComplete() { m_processingQueue = false; // Queue processing is now complete if (KFTPCore::Config::showBalloons()) KFTPWidgets::SystemTray::self()->showBalloon(i18n("All queued transfers have been completed.")); emit queueUpdate(); } void Manager::slotQueueProcessingAborted() { m_processingQueue = false; } void Manager::clearQueue() { QueueObject *i; TQPtrList sites = topLevelObject()->getChildrenList(); for (i = sites.first(); i; i = sites.next()) { QueueObject *t; TQPtrList list = i->getChildrenList(); for (t = list.first(); t; t = list.next()) removeTransfer(static_cast(t)); } } int Manager::getTransferPercentage() { return 0; } int Manager::getNumRunning(bool onlyDirs) { int running = 0; QueueObject *i; TQPtrList sites = topLevelObject()->getChildrenList(); for (i = sites.first(); i; i = sites.next()) { QueueObject *t; TQPtrList list = i->getChildrenList(); for (t = list.first(); t; t = list.next()) { if (t->isRunning() && (!onlyDirs || t->isDir())) running++; } if (i->isRunning()) running++; } return running; } int Manager::getNumRunning(const KURL &remoteUrl) { int running = 0; Site *site = findSite(remoteUrl, true); if (site) { QueueObject *i; TQPtrList transfers = site->getChildrenList(); for (i = transfers.first(); i; i = transfers.next()) { if (i->isRunning()) running++; } } return running; } KFTPEngine::FileExistsWakeupEvent *Manager::fileExistsAction(TransferFile *transfer, TQValueList stat) { FileExistsWakeupEvent *event = new FileExistsWakeupEvent(); FileExistsActions *fa = NULL; FEAction action; filesize_t srcSize = 0; time_t srcTime = 0; filesize_t dstSize = 0; time_t dstTime = 0; // Check if there is a default action set action = getDefaultFileExistsAction(); if (action == FE_DISABLE_ACT) { switch (transfer->getTransferType()) { case KFTPQueue::Download: { KFileItem info(KFileItem::Unknown, KFileItem::Unknown, transfer->getDestUrl()); dstSize = info.size(); dstTime = info.time(TDEIO::UDS_MODIFICATION_TIME); srcSize = stat[0].size(); srcTime = stat[0].time(); fa = KFTPCore::Config::self()->dActions(); break; } case KFTPQueue::Upload: { KFileItem info(KFileItem::Unknown, KFileItem::Unknown, transfer->getSourceUrl()); srcSize = info.size(); srcTime = info.time(TDEIO::UDS_MODIFICATION_TIME); dstSize = stat[0].size(); dstTime = stat[0].time(); fa = KFTPCore::Config::self()->uActions(); break; } case KFTPQueue::FXP: { srcSize = stat[0].size(); srcTime = stat[0].time(); dstSize = stat[1].size(); dstTime = stat[1].time(); fa = KFTPCore::Config::self()->fActions(); break; } } // Now that we have all data, get the action and do it action = fa->getActionForSituation(srcSize, srcTime, dstSize, dstTime); } switch (action) { default: case FE_SKIP_ACT: event->action = FileExistsWakeupEvent::Skip; break; case FE_OVERWRITE_ACT: event->action = FileExistsWakeupEvent::Overwrite; break; case FE_RESUME_ACT: event->action = FileExistsWakeupEvent::Resume; break; case FE_RENAME_ACT: case FE_USER_ACT: { appendUserDialogRequest(new UserDialogRequest(transfer, srcSize, srcTime, dstSize, dstTime)); // Event shall be deferred delete event; event = 0; } } return event; } void Manager::appendUserDialogRequest(UserDialogRequest *request) { m_userDialogRequests.append(request); if (m_userDialogRequests.count() == 1) { processUserDialogRequest(); } } void Manager::processUserDialogRequest() { UserDialogRequest *request = m_userDialogRequests.getFirst(); if (!request) return; FEAction action = getDefaultFileExistsAction(); FileExistsWakeupEvent *event = new FileExistsWakeupEvent(); if (action == FE_DISABLE_ACT || action == FE_USER_ACT) { // A dialog really needs to be displayed TransferFile *transfer = request->getTransfer(); TQString newDestPath; TDEIO::RenameDlg_Result r = TDEIO::open_RenameDlg( i18n("File Exists"), transfer->getSourceUrl().prettyURL(), transfer->getDestUrl().prettyURL(), (TDEIO::RenameDlg_Mode) (TDEIO::M_OVERWRITE | TDEIO::M_RESUME | TDEIO::M_SKIP | TDEIO::M_MULTI), newDestPath, request->sourceSize(), request->destinationSize(), request->sourceTime(), request->destinationTime() ); switch (r) { case TDEIO::R_RENAME: { KURL url = transfer->getDestUrl(); url.setPath(newDestPath); transfer->setDestUrl(url); event->action = FileExistsWakeupEvent::Rename; event->newFileName = newDestPath; break; } case TDEIO::R_CANCEL: { // Abort queue processing abort(); transfer->abort(); // An event is not required, since we will not be recalling the process delete event; event = 0; break; } case TDEIO::R_AUTO_SKIP: setDefaultFileExistsAction(FE_SKIP_ACT); case TDEIO::R_SKIP: event->action = FileExistsWakeupEvent::Skip; break; case TDEIO::R_RESUME_ALL: setDefaultFileExistsAction(FE_RESUME_ACT); case TDEIO::R_RESUME: event->action = FileExistsWakeupEvent::Resume; break; case TDEIO::R_OVERWRITE_ALL: setDefaultFileExistsAction(FE_OVERWRITE_ACT); default: event->action = FileExistsWakeupEvent::Overwrite; break; } } else { switch (action) { default: case FE_SKIP_ACT: event->action = FileExistsWakeupEvent::Skip; break; case FE_OVERWRITE_ACT: event->action = FileExistsWakeupEvent::Overwrite; break; case FE_RESUME_ACT: event->action = FileExistsWakeupEvent::Resume; break; } } // Send a response to this request request->sendResponse(event); m_userDialogRequests.removeFirst(); if (!m_userDialogRequests.isEmpty()) processUserDialogRequest(); } void Manager::openAfterTransfer(TransferFile *transfer) { TQString mimeType = KMimeType::findByURL(transfer->getDestUrl(), 0, true, true)->name(); KService::Ptr offer = KServiceTypeProfile::preferredService(mimeType, "Application"); if (!offer) { KOpenWithDlg dialog(KURL::List(transfer->getDestUrl())); if (dialog.exec() == TQDialog::Accepted) { offer = dialog.service(); if (!offer) offer = new KService("", dialog.text(), ""); } else { return; } } TQStringList params = KRun::processDesktopExec(*offer, KURL::List(transfer->getDestUrl()), false); TDEProcess *p = new TDEProcess(this); *p << params; connect(p, SIGNAL(processExited(TDEProcess*)), this, SLOT(slotEditProcessTerminated(TDEProcess*))); p->start(); // Save the process m_editProcessList.insert(p->pid(), OpenedFile(transfer)); } void Manager::slotEditProcessTerminated(TDEProcess *p) { // A process has terminated, we should reupload OpenedFile file = m_editProcessList[p->pid()]; // Only upload a file if it has been changed if (file.hasChanged()) { TransferFile *transfer = new TransferFile(KFTPQueue::Manager::self()); transfer->setSourceUrl(file.destination()); transfer->setDestUrl(file.source()); transfer->setTransferType(KFTPQueue::Upload); transfer->addSize(KFileItem(KFileItem::Unknown, KFileItem::Unknown, file.destination()).size()); insertTransfer(transfer); // Execute the transfer transfer->delayedExecute(); } // Cleanup m_editProcessList.remove(p->pid()); p->deleteLater(); } } #include "kftpqueue.moc"