diff options
Diffstat (limited to 'kftpgrabber/src/kftpqueue.cpp')
-rw-r--r-- | kftpgrabber/src/kftpqueue.cpp | 834 |
1 files changed, 834 insertions, 0 deletions
diff --git a/kftpgrabber/src/kftpqueue.cpp b/kftpgrabber/src/kftpqueue.cpp new file mode 100644 index 0000000..0186c4e --- /dev/null +++ b/kftpgrabber/src/kftpqueue.cpp @@ -0,0 +1,834 @@ +/* + * This file is part of the KFTPGrabber project + * + * Copyright (C) 2003-2004 by the KFTPGrabber developers + * Copyright (C) 2003-2004 Jernej Kos <kostko@jweb-network.net> + * + * 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 <math.h> + +#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 <kmessagebox.h> +#include <klocale.h> +#include <kio/renamedlg.h> +#include <kdiskfreesp.h> +#include <kfileitem.h> +#include <kopenwith.h> +#include <kstaticdeleter.h> +#include <kservice.h> +#include <kuserprofile.h> +#include <kstandarddirs.h> +#include <krun.h> +#include <kmdcodec.h> + +#include <qapplication.h> +#include <qregexp.h> +#include <qobjectlist.h> +#include <qfile.h> + +using namespace KFTPEngine; +using namespace KFTPCore::Filter; + +namespace KFTPQueue { + +OpenedFile::OpenedFile(TransferFile *transfer) + : m_source(transfer->getSourceUrl()), + m_dest(transfer->getDestUrl()), + m_hash(QString::null) +{ + // Calculate the file's MD5 hash + QFile file(m_dest.path()); + if (!file.open(IO_ReadOnly)) { + return; + } + + KMD5 context; + if (context.update(file)) + m_hash = QString(context.hexDigest()); + file.close(); +} + +bool OpenedFile::hasChanged() +{ + // Compare the file's MD5 hash with stored value + QFile file(m_dest.path()); + if (!file.open(IO_ReadOnly)) { + return false; + } + + QString tmp = QString::null; + KMD5 context; + if (context.update(file)) + tmp = QString(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<Manager> 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_lastQID = 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; + QPtrList<QueueObject> sites = topLevelObject()->getChildrenList(); + + for (i = sites.first(); i; i = sites.next()) { + if (i->isRunning()) { + i->abort(); + } else { + QueueObject *t; + QPtrList<QueueObject> 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[QString::number(id)]; + + if (!object) { + object = m_topLevel->findChildObject(id); + m_queueObjectCache.insert(QString::number(id), object); + } + + return static_cast<Transfer*>(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; + QPtrList<QueueObject> sites = topLevelObject()->getChildrenList(); + + for (i = sites.first(); i; i = sites.next()) { + if (i->getType() == QueueObject::Site) { + Site *site = static_cast<Site*>(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_lastQID++); + emit newSite(site); + + return site; + } + + return 0; +} + +void Manager::insertTransfer(Transfer *transfer) +{ + // Set id + transfer->setId(m_lastQID++); + + // 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 + KIO::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) { + QString p_data = p_meta[(*i).htmlURL().local8Bit()]; + QChar 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<KFTPQueue::Site*>(lastTransfer->parentObject())->delayedExecute(); +} + +Transfer *Manager::spawnTransfer(KURL sourceUrl, KURL destinationUrl, filesize_t size, bool dir, bool ignoreSkip, + bool insertToQueue, QObject *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_lastQID++); + 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(QString::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<Site*>(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; + QPtrListIterator<KFTPQueue::FailedTransfer> 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; + QPtrList<QueueObject> sites = topLevelObject()->getChildrenList(); + + for (i = sites.first(); i; i = sites.next()) { + QueueObject *t; + QPtrList<QueueObject> list = i->getChildrenList(); + + for (t = list.first(); t; t = list.next()) { + KFTPQueue::Transfer *tmp = static_cast<Transfer*>(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; + QPtrList<QueueObject> sites = topLevelObject()->getChildrenList(); + + for (i = sites.first(); i; i = sites.next()) { + QueueObject *t; + QPtrList<QueueObject> list = i->getChildrenList(); + + for (t = list.first(); t; t = list.next()) + removeTransfer(static_cast<Transfer*>(t)); + } +} + +int Manager::getTransferPercentage() +{ + return 0; +} + +int Manager::getNumRunning(bool onlyDirs) +{ + int running = 0; + + QueueObject *i; + QPtrList<QueueObject> sites = topLevelObject()->getChildrenList(); + + for (i = sites.first(); i; i = sites.next()) { + QueueObject *t; + QPtrList<QueueObject> 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; + QPtrList<QueueObject> transfers = site->getChildrenList(); + + for (i = transfers.first(); i; i = transfers.next()) { + if (i->isRunning()) + running++; + } + } + + return running; +} + +KFTPEngine::FileExistsWakeupEvent *Manager::fileExistsAction(TransferFile *transfer, + QValueList<KFTPEngine::DirectoryEntry> 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(KIO::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(KIO::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(); + + QString newDestPath; + KIO::RenameDlg_Result r = KIO::open_RenameDlg( + i18n("File Exists"), + transfer->getSourceUrl().prettyURL(), + transfer->getDestUrl().prettyURL(), + (KIO::RenameDlg_Mode) (KIO::M_OVERWRITE | KIO::M_RESUME | KIO::M_SKIP | KIO::M_MULTI), + newDestPath, + request->sourceSize(), + request->destinationSize(), + request->sourceTime(), + request->destinationTime() + ); + + switch (r) { + case KIO::R_RENAME: { + KURL url = transfer->getDestUrl(); + url.setPath(newDestPath); + transfer->setDestUrl(url); + + event->action = FileExistsWakeupEvent::Rename; + event->newFileName = newDestPath; + break; + } + case KIO::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 KIO::R_AUTO_SKIP: setDefaultFileExistsAction(FE_SKIP_ACT); + case KIO::R_SKIP: event->action = FileExistsWakeupEvent::Skip; break; + case KIO::R_RESUME_ALL: setDefaultFileExistsAction(FE_RESUME_ACT); + case KIO::R_RESUME: event->action = FileExistsWakeupEvent::Resume; break; + case KIO::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) +{ + QString 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() == QDialog::Accepted) { + offer = dialog.service(); + + if (!offer) + offer = new KService("", dialog.text(), ""); + } else { + return; + } + } + + QStringList params = KRun::processDesktopExec(*offer, KURL::List(transfer->getDestUrl()), false); + KProcess *p = new KProcess(this); + *p << params; + + connect(p, SIGNAL(processExited(KProcess*)), this, SLOT(slotEditProcessTerminated(KProcess*))); + + p->start(); + + // Save the process + m_editProcessList.insert(p->pid(), OpenedFile(transfer)); +} + +void Manager::slotEditProcessTerminated(KProcess *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" |