/* * 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 "kftpsession.h" #include "kftpapi.h" #include "browser/detailsview.h" #include "browser/treeview.h" #include "browser/view.h" #include "kftpbookmarks.h" #include "misc.h" #include "widgets/systemtray.h" #include "mainactions.h" #include "misc/config.h" #include "misc/filter.h" #include #include #include #include #include #include #include using namespace KFTPGrabberBase; using namespace KFTPEngine; using namespace KFTPCore::Filter; namespace KFTPSession { ////////////////////////////////////////////////////////////////// //////////////////////// Connection /////////////////////// ////////////////////////////////////////////////////////////////// Connection::Connection(Session *session, bool primary) : TQObject(session), m_primary(primary), m_busy(false), m_aborting(false), m_scanning(false) { // Create the actual connection client m_client = new KFTPEngine::Thread(); connect(m_client->eventHandler(), SIGNAL(engineEvent(KFTPEngine::Event*)), this, SLOT(slotEngineEvent(KFTPEngine::Event*))); // If this is not a core session connection, connect if (!primary) { // Connect to the server KURL url = session->getClient()->socket()->getCurrentUrl(); KFTPBookmarks::Manager::self()->setupClient(session->getSite(), m_client); m_client->connect(url); } } Connection::~Connection() { delete m_client; } bool Connection::isConnected() { return !static_cast(parent())->isRemote() || m_client->socket()->isConnected(); } void Connection::acquire(KFTPQueue::Transfer *transfer) { if (m_busy || !static_cast(parent())->isRemote()) return; m_curTransfer = transfer; m_busy = true; connect(transfer, SIGNAL(transferComplete(long)), this, SLOT(slotTransferCompleted())); connect(transfer, SIGNAL(transferAbort(long)), this, SLOT(slotTransferCompleted())); emit connectionAcquired(); } void Connection::remove() { // Disconnect all signals from the transfer if (m_curTransfer) m_curTransfer->TQObject::disconnect(this); m_curTransfer = 0L; m_busy = false; emit connectionRemoved(); emit static_cast(parent())->freeConnectionAvailable(); } void Connection::abort() { if (m_aborting || !m_client->socket()->isBusy()) return; // Emit the signal before aborting emit aborting(); // Abort transfer m_aborting = true; m_client->abort(); m_aborting = false; } void Connection::scanDirectory(KFTPQueue::Transfer *parent) { // Lock the connection and the transfer acquire(parent); parent->lock(); m_scanning = true; if (isConnected()) m_client->scan(parent->getSourceUrl()); } void Connection::addScannedDirectory(KFTPEngine::DirectoryTree *tree, KFTPQueue::Transfer *parent) { // Directories DirectoryTree::DirIterator dirEnd = tree->directories()->end(); for (DirectoryTree::DirIterator i = tree->directories()->begin(); i != dirEnd; i++) { KURL sourceUrlBase = parent->getSourceUrl(); KURL destUrlBase = parent->getDestUrl(); sourceUrlBase.addPath((*i)->info().filename()); destUrlBase.addPath((*i)->info().filename()); // Check if we should skip this entry const ActionChain *actionChain = Filters::self()->process(sourceUrlBase, 0, true); if (actionChain && actionChain->getAction(Action::Skip)) continue; // Add directory transfer KFTPQueue::TransferDir *transfer = new KFTPQueue::TransferDir(parent); transfer->setSourceUrl(sourceUrlBase); transfer->setDestUrl(destUrlBase); transfer->setTransferType(parent->getTransferType()); transfer->setId(KFTPQueue::Manager::self()->nextTransferId()); emit KFTPQueue::Manager::self()->newTransfer(transfer); addScannedDirectory(*i, transfer); if (KFTPCore::Config::skipEmptyDirs() && !transfer->hasChildren()) KFTPQueue::Manager::self()->removeTransfer(transfer, false); } // Files DirectoryTree::FileIterator fileEnd = tree->files()->end(); for (DirectoryTree::FileIterator i = tree->files()->begin(); i != fileEnd; i++) { KURL sourceUrlBase = parent->getSourceUrl(); KURL destUrlBase = parent->getDestUrl(); sourceUrlBase.addPath((*i).filename()); destUrlBase.addPath((*i).filename()); // Check if we should skip this entry const ActionChain *actionChain = Filters::self()->process(sourceUrlBase, (*i).size(), false); if (actionChain && actionChain->getAction(Action::Skip)) continue; // Add file transfer KFTPQueue::TransferFile *transfer = new KFTPQueue::TransferFile(parent); transfer->addSize((*i).size()); transfer->setSourceUrl(sourceUrlBase); transfer->setDestUrl(destUrlBase); transfer->setTransferType(parent->getTransferType()); transfer->setId(KFTPQueue::Manager::self()->nextTransferId()); emit KFTPQueue::Manager::self()->newTransfer(transfer); } } void Connection::slotEngineEvent(KFTPEngine::Event *event) { switch (event->type()) { case Event::EventDisconnect: { emit connectionLost(this); break; } case Event::EventConnect: { emit connectionEstablished(); if (m_scanning) { // Connected successfully, let's scan m_client->scan(m_curTransfer->getSourceUrl()); } break; } case Event::EventError: { ErrorCode error = event->getParameter(0).asErrorCode(); if (m_scanning && (error == ConnectFailed || error == LoginFailed || error == OperationFailed)) { // Scanning should be aborted, since there was an error m_scanning = false; m_curTransfer->unlock(); remove(); emit static_cast(parent())->dirScanDone(); } break; } case Event::EventScanComplete: { if (m_scanning) { // We have the listing DirectoryTree *tree = static_cast(event->getParameter(0).asData()); addScannedDirectory(tree, m_curTransfer); delete tree; m_scanning = false; m_curTransfer->unlock(); remove(); emit static_cast(parent())->dirScanDone(); } break; } default: break; } } void Connection::slotTransferCompleted() { // Remove the lock remove(); } void Connection::reconnect() { if (!m_client->socket()->isConnected()) { KFTPBookmarks::Manager::self()->setupClient(static_cast(parent())->getSite(), m_client); m_client->connect(m_client->socket()->getCurrentUrl()); } } //////////////////////////////////////////////////////// //////////////////// Session //////////////////// //////////////////////////////////////////////////////// Session::Session(Side side) : TQObject(), m_side(side), m_remote(false), m_aborting(false), m_registred(false), m_site(0) { // Register this session Manager::self()->registerSession(this); } Session::~Session() { } KFTPEngine::Thread *Session::getClient() { // Return the first (core) connection's client return m_connections.at(0)->getClient(); } bool Session::isConnected() { // If there are no connections, just check if the session is remote if (m_connections.count() == 0) return !m_remote; return m_connections.at(0)->isConnected(); } void Session::slotClientEngineEvent(KFTPEngine::Event *event) { switch (event->type()) { case Event::EventConnect: { // *************************************************************************** // ****************************** EventConnect ******************************* // *************************************************************************** m_remote = true; m_aborting = false; m_lastUrl = getClient()->socket()->getCurrentUrl(); TQString siteName; if (m_site) siteName = m_site->getAttribute("name"); else siteName = m_lastUrl.host(); Manager::self()->getTabs(m_side)->changeTab(m_fileView, siteName); Manager::self()->getStatTabs()->changeTab(m_log, i18n("Log (%1)").arg(siteName)); Manager::self()->getStatTabs()->showPage(m_log); Manager::self()->doEmitUpdate(); KURL homeUrl = getClient()->socket()->getCurrentUrl(); if (m_site && !m_site->getProperty("defremotepath").isEmpty()) homeUrl.setPath(m_site->getProperty("defremotepath")); else homeUrl.setPath(getClient()->socket()->getDefaultDirectory()); m_fileView->setHomeUrl(homeUrl); m_fileView->goHome(); Session *opposite = Manager::self()->getActive(oppositeSide(m_side)); if (m_site && !opposite->isRemote()) { TQString localPath = m_site->getProperty("deflocalpath"); if (!localPath.isEmpty()) opposite->getFileView()->openUrl(KURL(localPath)); } break; } case Event::EventDisconnect: { // *************************************************************************** // **************************** EventDisconnect ****************************** // *************************************************************************** m_remote = false; m_aborting = false; Manager::self()->getTabs(m_side)->changeTab(m_fileView, i18n("Local Session")); Manager::self()->getStatTabs()->changeTab(m_log, "[" + i18n("Log") + "]"); Manager::self()->doEmitUpdate(); m_fileView->setHomeUrl(KURL(KFTPCore::Config::defLocalDir())); m_fileView->goHome(); break; } case Event::EventCommand: m_log->ftpLog(1, event->getParameter(0).asString()); break; case Event::EventMultiline: m_log->ftpLog(2, event->getParameter(0).asString()); break; case Event::EventResponse: m_log->ftpLog(0, event->getParameter(0).asString()); break; case Event::EventMessage: m_log->ftpLog(3, event->getParameter(0).asString()); break; case Event::EventRetrySuccess: { // *************************************************************************** // ************************** EventRetrySuccess ****************************** // *************************************************************************** if (KFTPCore::Config::showRetrySuccessBalloon()) { KFTPWidgets::SystemTray::self()->showBalloon(i18n("Connection with %1 has been successfully established.").arg(getClient()->socket()->getCurrentUrl().host())); } break; } case Event::EventReloadNeeded: { // We should only do refreshes if the queue is not being processed if (KFTPQueue::Manager::self()->getNumRunning() == 0) m_fileView->reload(); break; } case Event::EventPubkeyPassword: { // A public-key authentication password was requested TQCString pass; int ret = KPasswordDialog::getPassword(pass, i18n("Please provide your private key decryption password.")); if (ret == KPasswordDialog::Accepted) { PubkeyWakeupEvent *event = new PubkeyWakeupEvent(); event->password = pass; getClient()->wakeup(event); } else { getClient()->abort(); } break; } default: break; } } void Session::scanDirectory(KFTPQueue::Transfer *parent, Connection *connection) { // Go trough all files in path and add them as transfers that have parent as their parent // transfer KURL path = parent->getSourceUrl(); if (path.isLocalFile()) { connect(new DirectoryScanner(parent), SIGNAL(completed()), this, SIGNAL(dirScanDone())); } else if (m_remote) { if (!connection) { if (!isFreeConnection()) { emit dirScanDone(); return; } // Assign a new connection (it might be unconnected!) connection = assignConnection(); } connection->scanDirectory(parent); } } void Session::abort() { if (m_aborting) return; m_aborting = true; emit aborting(); // Abort all connections Connection *conn; for (conn = m_connections.first(); conn; conn = m_connections.next()) { conn->abort(); } m_aborting = false; } void Session::reconnect(const KURL &url) { // Set the reconnect url m_reconnectUrl = url; if (m_remote && getClient()->socket()->isConnected()) { abort(); connect(getClient()->eventHandler(), SIGNAL(disconnected()), this, SLOT(slotStartReconnect())); getClient()->disconnect(); } else { // The session is already disconnected, just call the slot slotStartReconnect(); } } void Session::slotStartReconnect() { disconnect(getClient()->eventHandler(), SIGNAL(disconnected()), this, SLOT(slotStartReconnect())); // Reconnect only if this is a remote url if (!m_reconnectUrl.isLocalFile()) { KFTPBookmarks::Manager::self()->setupClient(m_site, getClient()); getClient()->connect(m_reconnectUrl); } // Invalidate the url m_reconnectUrl = KURL(); } int Session::getMaxThreadCount() { // First get the global thread count int count = KFTPCore::Config::threadCount(); if (!KFTPCore::Config::threadUsePrimary()) count++; // Try to see if threads are disabled for this site if (count > 1 && isRemote()) { if (m_site && m_site->getIntProperty("disableThreads")) return 1; } return count; } bool Session::isFreeConnection() { unsigned int max = getMaxThreadCount(); unsigned int free = 0; if ((m_connections.count() < max && max > 1) || !isRemote()) return true; Connection *conn; for (conn = m_connections.first(); conn; conn = m_connections.next()) { if (!conn->isBusy() && (!conn->isPrimary() || KFTPCore::Config::threadUsePrimary() || max == 1)) free++; } return free > 0; } Connection *Session::assignConnection() { unsigned int max = getMaxThreadCount(); if (m_connections.count() == 0) { // We need a new core connection Connection *conn = new Connection(this, true); m_connections.append(conn); Manager::self()->doEmitUpdate(); return conn; } else { // Find a free connection Connection *conn; for (conn = m_connections.first(); conn; conn = m_connections.next()) { if (!conn->isBusy() && (!conn->isPrimary() || KFTPCore::Config::threadUsePrimary() || max == 1)) return conn; } // No free connection has been found, but we may be able to create // another (if we are within limits) if (m_connections.count() < max) { conn = new Connection(this); m_connections.append(conn); Manager::self()->doEmitUpdate(); return conn; } } return 0; } void Session::disconnectAllConnections() { // Abort any possible transfers first abort(); // Now disconnect all connections Connection *conn; for (conn = m_connections.first(); conn; conn = m_connections.next()) { if (conn->getClient()->socket()->isConnected()) { conn->getClient()->disconnect(); } } } //////////////////////////////////////////////////////// /////////////////////// Manager //////////////////////// //////////////////////////////////////////////////////// Manager *Manager::m_self = 0; Manager *Manager::self() { return m_self; } Manager::Manager(TQObject *parent, TQTabWidget *stat, KFTPTabWidget *left, KFTPTabWidget *right) : TQObject(parent), m_statTabs(stat), m_leftTabs(left), m_rightTabs(right), m_active(0), m_leftActive(0), m_rightActive(0) { Manager::m_self = this; // Connect some signals connect(left, SIGNAL(currentChanged(TQWidget*)), this, SLOT(slotActiveChanged(TQWidget*))); connect(right, SIGNAL(currentChanged(TQWidget*)), this, SLOT(slotActiveChanged(TQWidget*))); connect(left, SIGNAL(closeRequest(TQWidget*)), this, SLOT(slotSessionCloseRequest(TQWidget*))); connect(right, SIGNAL(closeRequest(TQWidget*)), this, SLOT(slotSessionCloseRequest(TQWidget*))); } void Manager::registerSession(Session *session) { m_active = session; // Create some new stuff and assign it to the session session->assignConnection(); session->m_fileView = new KFTPWidgets::Browser::View(0L, "", session->getClient(), session); session->m_log = new KFTPWidgets::LogView(); // Install event filters session->getFileView()->getDetailsView()->installEventFilter(this); session->getFileView()->getTreeView()->installEventFilter(this); session->getFileView()->m_toolBarFirst->installEventFilter(this); session->getFileView()->m_toolBarSecond->installEventFilter(this); connect(session->getFileView()->getDetailsView(), SIGNAL(clicked(TQListViewItem*)), this, SLOT(slotSwitchFocus())); connect(session->getFileView()->getTreeView(), SIGNAL(clicked(TQListViewItem*)), this, SLOT(slotSwitchFocus())); connect(session->getFileView()->m_toolBarFirst, SIGNAL(clicked(int)), this, SLOT(slotSwitchFocus())); connect(session->getFileView()->m_toolBarSecond, SIGNAL(clicked(int)), this, SLOT(slotSwitchFocus())); // Connect some signals connect(session->getClient()->eventHandler(), SIGNAL(engineEvent(KFTPEngine::Event*)), session, SLOT(slotClientEngineEvent(KFTPEngine::Event*))); // Assign GUI positions m_statTabs->addTab(session->m_log, "[" + i18n("Log") + "]"); getTabs(session->m_side)->addTab(session->m_fileView, KFTPGrabberBase::loadSmallIcon("computer"), i18n("Session")); // Actually add the session m_sessionList.append(session); session->m_registred = true; } KFTPWidgets::Browser::View *Manager::getActiveView() { if (m_active) return m_active->getFileView(); return 0; } Session *Manager::getActiveSession() { return m_active; } bool Manager::eventFilter(TQObject *object, TQEvent *event) { if (event->type() == TQEvent::FocusIn || event->type() == TQEvent::MouseButtonPress) { switchFocusToObject(object); } return false; } void Manager::slotSwitchFocus() { switchFocusToObject(TQObject::sender()); } void Manager::switchFocusToObject(const TQObject *object) { if (!object) return; for (;;) { if (object->isA("KFTPWidgets::Browser::View")) break; if (!(object = object->parent())) break; } if (object) { // We have the proper object Session *session = find(static_cast(object)); if (session && session != m_active) { m_active = session; // Open the current session's log tab if (session->isRemote()) m_statTabs->showPage(session->getLog()); } } } void Manager::unregisterSession(Session *session) { // Destroy all objects related to the session and remove it getTabs(session->m_side)->removePage(session->m_fileView); m_statTabs->removePage(session->m_log); if (session->getClient()->socket()->isConnected()) { session->abort(); session->getClient()->disconnect(); } // Delete objects session->m_fileView->deleteLater(); session->m_log->deleteLater(); // Actually remove the session m_sessionList.remove(session); delete session; // Emit update emit update(); } void Manager::doEmitUpdate() { emit update(); } void Manager::disconnectAllSessions() { Session *i; for (i = m_sessionList.first(); i; i = m_sessionList.next()) i->disconnectAllConnections(); } Session *Manager::find(KFTPEngine::Thread *client) { Session *i; for (i = m_sessionList.first(); i; i = m_sessionList.next()) { if (i->getClient() == client) return i; } return 0L; } Session *Manager::find(KFTPWidgets::Browser::View *fileView) { Session *i; for (i = m_sessionList.first(); i; i = m_sessionList.next()) { if (i->m_fileView == fileView) return i; } return 0L; } Session *Manager::find(KFTPWidgets::LogView *log) { Session *i; for (i = m_sessionList.first(); i; i = m_sessionList.next()) { if (i->m_log == log) return i; } return 0L; } Session *Manager::find(const KURL &url, bool mustUnlock) { if (url.isLocalFile()) return find(true); Session *i; for (i = m_sessionList.first(); i; i = m_sessionList.next()) { KURL tmp = i->getClient()->socket()->getCurrentUrl(); tmp.setPath(url.path()); if (tmp == url && i->isRemote() && i->isConnected() && (!mustUnlock || i->isFreeConnection())) return i; } return 0L; } Session *Manager::find(bool local) { Session *i; for (i = m_sessionList.first(); i; i = m_sessionList.next()) { if (i->m_remote != local) return i; } return 0L; } Session *Manager::findLast(const KURL &url, Side side) { if (url.isLocalFile()) return find(true); Session *i; for (i = m_sessionList.first(); i; i = m_sessionList.next()) { KURL tmp = i->m_lastUrl; tmp.setPath(url.path()); if (tmp == url && !i->isRemote() && (i->getSide() || side == IgnoreSide)) return i; } return 0L; } Session *Manager::spawnLocalSession(Side side, bool forceNew) { // Creates a new local session Session *session = 0L; if (forceNew || (session = find(true)) == 0L || (session->m_side != side && side != IgnoreSide)) { side = side == IgnoreSide ? LeftSide : side; session = new Session(side); session->m_remote = false; getTabs(side)->changeTab(session->m_fileView, i18n("Local Session")); getStatTabs()->changeTab(session->m_log, "[" + i18n("Log") + "]"); } setActive(session); return session; } Session *Manager::spawnRemoteSession(Side side, const KURL &remoteUrl, KFTPBookmarks::Site *site, bool mustUnlock) { // Creates a new remote session and connects it to the correct server Session *session; if (remoteUrl.isLocalFile()) return spawnLocalSession(side); if ((session = find(remoteUrl, mustUnlock)) == 0L || (session->m_side != side && side != IgnoreSide)) { // Try to find the session that was last connected to this URL if ((session = findLast(remoteUrl, side)) == 0L) { // Attempt to reuse a local session if one exists one the right side session = getActive(RightSide); if (session->isRemote()) { side = side == IgnoreSide ? RightSide : side; session = new Session(side); } } // Try to find the site by url if it is not set if (!site) site = KFTPBookmarks::Manager::self()->findSite(remoteUrl); // Set properties session->m_remote = true; session->m_site = site; m_active = session; KFTPBookmarks::Manager::self()->setupClient(site, session->getClient()); session->getClient()->connect(remoteUrl); } return session; } void Manager::setActive(Session *session) { // Make a session active on its own side ;) Session *oldActive = getActive(session->m_side); oldActive ? oldActive->m_active = false : 0; session->m_active = true; switch (session->m_side) { case LeftSide: m_leftActive = session; break; case RightSide: m_rightActive = session; break; case IgnoreSide: tqDebug("Invalid side specified!"); return; } // Refresh the GUI getTabs(session->m_side)->showPage(session->m_fileView); } Session *Manager::getActive(Side side) { switch (side) { case LeftSide: return m_leftActive; case RightSide: return m_rightActive; case IgnoreSide: tqDebug("Invalid side specified!"); break; } return NULL; } KFTPTabWidget *Manager::getTabs(Side side) { switch (side) { case LeftSide: return m_leftTabs; case RightSide: return m_rightTabs; case IgnoreSide: tqDebug("Invalid side specified!"); break; } return NULL; } void Manager::slotActiveChanged(TQWidget *page) { Session *session = find(static_cast(page)); setActive(session); } void Manager::slotSessionCloseRequest(TQWidget *page) { Session *session = find(static_cast(page)); if (getTabs(session->m_side)->count() == 1) { KMessageBox::error(0L, i18n("At least one session must remain open on each side.")); return; } if ((session->m_remote && session->getClient()->socket()->isBusy()) || !session->isFreeConnection()) { KMessageBox::error(0L, i18n("Please finish all transfers before closing the session.")); return; } else { // Remove the session if (session->getClient()->socket()->isConnected()) { if (KMessageBox::questionYesNo(0L, i18n("This session is currently connected. Are you sure you wish to disconnect?"), i18n("Close Session")) == KMessageBox::No) return; } unregisterSession(session); } } } #include "kftpsession.moc"