diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-20 06:57:47 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-20 06:57:47 +0000 |
commit | 2f234abde8e11481a9fdccadd37ba21ad6b4b94d (patch) | |
tree | f17fe279ab6fa97ea700cc44b110184dbe15dd3f /konq-plugins/rsync/rsyncplugin.cpp | |
parent | 0923a28904197f82783366aea1db27b07113fcca (diff) | |
download | tdeaddons-2f234abde8e11481a9fdccadd37ba21ad6b4b94d.tar.gz tdeaddons-2f234abde8e11481a9fdccadd37ba21ad6b4b94d.zip |
Added new rsync plugin for Konqueror
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdeaddons@1105435 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'konq-plugins/rsync/rsyncplugin.cpp')
-rw-r--r-- | konq-plugins/rsync/rsyncplugin.cpp | 721 |
1 files changed, 721 insertions, 0 deletions
diff --git a/konq-plugins/rsync/rsyncplugin.cpp b/konq-plugins/rsync/rsyncplugin.cpp new file mode 100644 index 0000000..11785ac --- /dev/null +++ b/konq-plugins/rsync/rsyncplugin.cpp @@ -0,0 +1,721 @@ +/* + Copyright (C) 2000, 2001, 2002 Dawit Alemayehu <adawit@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include <unistd.h> +#include <sys/types.h> +#include <signal.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define HAVE_TERMIOS_H 1 +#define HAVE_GRANTPT 1 + +#include <stdlib.h> +#ifdef HAVE_PTY_H +#include <pty.h> +#endif +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif +#ifdef HAVE_STROPTS +#include <stropts.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#ifdef HAVE_UTIL_H +#include <util.h> +#endif + +#include <qfile.h> +#include <qtimer.h> +#include <qapplication.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qhbox.h> +#include <qwhatsthis.h> +#include <qiconview.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpushbutton.h> +#include <qstring.h> +#include <qregexp.h> +#include <qstyle.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kinstance.h> + +#include <kwin.h> +#include <kaction.h> +#include <kpopupmenu.h> +#include <kmessagebox.h> +#include <kiconloader.h> +#include <kprogressbox.h> +#include <kpassdlg.h> +#include <klistview.h> +#include <kapplication.h> +#include <kconfigdialog.h> + +#include <kdirlister.h> +#include <kstandarddirs.h> +#include <klistviewsearchline.h> +#include <kiconviewsearchline.h> +#include <konq_dirpart.h> +#include <konq_propsview.h> +#include <kstaticdeleter.h> +#include <kgenericfactory.h> +#include <kparts/browserextension.h> + +#include <kio/global.h> +#include <kio/slavebase.h> +#include <kio/authinfo.h> + +#include "rsyncplugin.h" +#include "rsyncconfigdialog.h" + +#define myDebug(x) kdDebug(7127) << __LINE__ << ": " x +// #define myDebug(x) cout << __LINE__ << ": " x +#define infoMessage(x) printf("INFO: %s\n\r", x); + +static char *rsyncPath = NULL; +static char *suPath = NULL; + +static int open_pty_pair(int fd[2]) +{ +#if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY) +/** with kind regards to The GNU C Library +Reference Manual for Version 2.2.x of the GNU C Library */ + int master, slave; + char *name; + struct ::termios ti; + memset(&ti,0,sizeof(ti)); + + ti.c_cflag = CLOCAL|CREAD|CS8; + ti.c_cc[VMIN] = 1; + +#ifdef HAVE_GETPT + master = getpt(); +#else + master = open("/dev/ptmx", O_RDWR); +#endif + if (master < 0) return 0; + + if (grantpt(master) < 0 || unlockpt(master) < 0) goto close_master; + + name = ptsname(master); + if (name == NULL) goto close_master; + + slave = open(name, O_RDWR); + if (slave == -1) goto close_master; + +#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) + if (isastream(slave) && + (ioctl(slave, I_PUSH, "ptem") < 0 || + ioctl(slave, I_PUSH, "ldterm") < 0)) + goto close_slave; +#endif + + tcsetattr(slave, TCSANOW, &ti); + fd[0] = master; + fd[1] = slave; + return 0; + +#if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH) +close_slave: +#endif + close(slave); + +close_master: + close(master); + return -1; +#else +#ifdef HAVE_OPENPTY + struct ::termios ti; + memset(&ti,0,sizeof(ti)); + + ti.c_cflag = CLOCAL|CREAD|CS8; + ti.c_cc[VMIN] = 1; + + return openpty(fd,fd+1,NULL,&ti,NULL); +#else +#ifdef __GNUC__ +#warning "No tty support available. Password dialog won't work." +#endif + return socketpair(PF_UNIX,SOCK_STREAM,0,fd); +#endif +#endif +} +/** +creates the subprocess +*/ +bool RsyncPlugin::connectionStart(QString localfolder, QString remotepath) { + int fd[2]; + int rc, flags; + thisFn = QString::null; + + rc = open_pty_pair(fd); + if (rc == -1) { + myDebug( << "socketpair failed, error: " << strerror(errno) << endl); + return true; + } + + //myDebug( << "Exec: " << (local ? suPath : rsyncPath) << " Port: " << connectionPort << " User: " << connectionUser << endl); + //infoMessage(rsyncPath); + childPid = fork(); + if (childPid == -1) { + myDebug( << "fork failed, error: " << strerror(errno) << endl); + close(fd[0]); + close(fd[1]); + childPid = 0; + return true; + } + if (childPid == 0) { + // Create the rsync command to run + QString execstring; + execstring = QString(rsyncPath) + QString(" -avtzAXE --delete --progress ") + localfolder + QString("/ ") + remotepath; + + // taken from konsole, see TEPty.C for details + // note: if we're running on socket pairs, + // this will fail, but thats what we expect + + for (int sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL); + + struct rlimit rlp; + getrlimit(RLIMIT_NOFILE, &rlp); + for (int i = 0; i < (int)rlp.rlim_cur; i++) + if (i != fd[1]) close(i); + + dup2(fd[1],0); + dup2(fd[1],1); + dup2(fd[1],2); + if (fd[1] > 2) close(fd[1]); + + setsid(); + +#if defined(TIOCSCTTY) + ioctl(0, TIOCSCTTY, 0); +#endif + + int pgrp = getpid(); +#if defined( _AIX) || defined( __hpux) + tcsetpgrp(0, pgrp); +#else + ioctl(0, TIOCSPGRP, (char *)&pgrp); +#endif + + const char *dev = ttyname(0); + setpgid(0,0); + if (dev) close(open(dev, O_WRONLY, 0)); + setpgid(0,0); + + system(execstring.ascii()); + #undef common_args + myDebug( << "could not exec! " << strerror(errno) << endl); + ::exit(-1); + } + close(fd[1]); + rc = fcntl(fd[0],F_GETFL,&flags); + rc = fcntl(fd[0],F_SETFL,flags|O_NONBLOCK); + childFd = fd[0]; + + fd_set rfds, wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + char buf[32768]; + int offset = 0; + while (!isLoggedIn) { + FD_SET(childFd,&rfds); + FD_ZERO(&wfds); + if (outBufPos >= 0) FD_SET(childFd,&wfds); + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 1000; + rc = select(childFd+1, &rfds, &wfds, NULL, &timeout); + if (rc < 0) { + if (errno == EINTR) + continue; + myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl); + return true; + } + if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) { + if (outBuf) { + rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos); + fflush(stdout); + } + else { + rc = 0; + } + if (rc >= 0) outBufPos += rc; + else { + if (errno == EINTR) + continue; + myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl); + outBufPos = -1; + //return true; + } + if (outBufPos >= outBufLen) { + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + } + } + if (FD_ISSET(childFd,&rfds)) { + rc = read(childFd,buf+offset,32768-offset); + if (rc > 0) { + int noff = establishConnection(buf,rc+offset); + if (noff < 0) return false; + if (noff > 0) memmove(buf,buf+offset+rc-noff,noff); + offset = noff; + } else { + if (errno == EINTR) + continue; + //if (errno == EAGAIN) + // continue; + myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl); + return true; + } + } + } + return false; +} + +/** +writes one chunk of data to stdin of child process +*/ +void RsyncPlugin::writeChild(const char *buf, KIO::fileoffset_t len) { + if (outBufPos >= 0 && outBuf) { +#if 0 + QString debug; + debug.setLatin1(outBuf,outBufLen); + if (len > 0) myDebug( << "write request while old one is pending, throwing away input (" << outBufLen << "," << outBufPos << "," << debug.left(10) << "...)" << endl); +#endif + return; + } + outBuf = buf; + outBufPos = 0; + outBufLen = len; +} + +/** +manages initial communication setup including password queries +*/ +int RsyncPlugin::establishConnection(char *buffer, KIO::fileoffset_t len) { + QString buf; + buf.setLatin1(buffer,len); + int pos; + // Strip trailing whitespace + while (buf.length() && (buf[buf.length()-1] == ' ')) + buf.truncate(buf.length()-1); + + myDebug( << "establishing: got " << buf << endl); + while (childPid && ((pos = buf.find('\n')) >= 0 || buf.endsWith(":") || buf.endsWith("?"))) { + if (m_progressDialogExists == true) { + qApp->processEvents(); + } + pos++; + QString str = buf.left(pos); + buf = buf.mid(pos); + if (str == "\n") + continue; + //if (str.contains("rsync error:")) { + if (str.contains("rsync:") || str.contains("failed.") || (str.contains("Could not") && str.endsWith("."))) { + KMessageBox::error(NULL, str); + } + else if (!str.isEmpty()) { + thisFn += str; + if ((buf.endsWith(":") == false) && (buf.endsWith("?") == false)) { + // Display a nice little progress bar with text box + if (m_progressDialogExists == false) { + m_progressDialog = new KProgressBoxDialog(0, "rsyncProgress", i18n("Synchronizing Folder..."), i18n("Synchronizing Folder..."), true); + m_progressDialog->progressBar()->setFormat("%v / %m"); + m_progressDialog->setAutoClose(true); + m_progressDialog->progressBar()->setTotalSteps(2); + m_progressDialog->progressBar()->setValue(1); + connect (m_progressDialog, SIGNAL(cancelClicked()), SLOT(slotRsyncCancelled())); + m_progressDialog->show(); + m_progressDialogExists = true; + } + } + } + else if (buf.endsWith(":")) { + if (!redirectUser.isEmpty() && connectionUser != redirectUser) { + // FIXME: Possibly do something here; is this the success response? + return -1; + } else if (!connectionPassword.isEmpty()) { + myDebug( << "sending cpass" << endl); + connectionAuth.password = connectionPassword+"\n"; + connectionPassword = QString::null; + writeChild(connectionAuth.password.latin1(),connectionAuth.password.length()); + } else { + myDebug( << "sending mpass" << endl); + connectionAuth.prompt = thisFn+buf; + connectionAuth.password = QString::null; // don't prefill + QCString thispass; + if (KPasswordDialog::getPassword (thispass, i18n("Remote authorization required") + QString("\n") + i18n("Please input") + QString(" ") + QString(buf), NULL) != 1) { + shutdownConnection(true, false); + return -1; + } + else { + connectionAuth.password = QString(thispass); + } + connectionAuth.password += "\n"; + myDebug( << "sending pass" << endl); + writeChild(connectionAuth.password.latin1(),connectionAuth.password.length()); + } + thisFn = QString::null; + return 0; + } + else if (buf.endsWith("?")) { + int rc = KMessageBox::questionYesNo(NULL, thisFn+buf); + if (rc == KMessageBox::Yes) { + writeChild("yes\n",4); + } else { + writeChild("no\n",3); + } + thisFn = QString::null; + return 0; + } + + if (m_progressDialogExists == true) { + if (str.contains("exit()") && str.contains("ICE default IO")) { + if (m_progressDialogExists == true) { + m_progressDialog->progressBar()->setValue(m_progressDialog->progressBar()->totalSteps()); + } + } + else { + if (str.contains(", to-check=")) { + // Parse the to-check output + QString tocheck_out_cur; + QString tocheck_out_tot; + tocheck_out_cur = str.mid(str.find(", to-check=") + 11, str.length()); + tocheck_out_tot = tocheck_out_cur.mid(tocheck_out_cur.find("/") + 1, tocheck_out_cur.length()); + tocheck_out_cur = tocheck_out_cur.left(tocheck_out_cur.find("/")); + tocheck_out_tot = tocheck_out_tot.left(tocheck_out_tot.find(")")); + m_progressDialog->progressBar()->setTotalSteps(tocheck_out_tot.toInt()-1); + m_progressDialog->progressBar()->setValue(tocheck_out_tot.toInt()-tocheck_out_cur.toInt()-2); + } + else { + m_progressDialog->textEdit()->append(str); + m_progressDialog->textEdit()->scrollToBottom(); + } + } + } + } + return buf.length(); +} + +/** +Forced close of the connection + +This function gets called from the application side of the universe, +it shouldn't send any response. + */ +void RsyncPlugin::closeConnection(){ + myDebug( << "closeConnection()" << endl); + shutdownConnection(true, false); +} + +/** +Closes the connection + */ +void RsyncPlugin::shutdownConnection(bool forced, bool wait){ + if (childPid) { + kill(childPid,SIGTERM); // We may not have permission... + childPid = 0; + if (wait == false) { + close(childFd); // ...in which case this should do the trick + childFd = -1; + } + } + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + isLoggedIn = false; +} + +// -------------------------------------------------------------------------------------------- +// +// Here begins the standard load/save/search/Konqy stuff +// +// -------------------------------------------------------------------------------------------- + +void RsyncPlugin::saveSettings() +{ + KConfig cfg ("rsyncrc", false, false); + cfg.setGroup ("General"); + + bool min_entry = false; + QString longstring = QString(""); + for (QStringList::Iterator i(cfgfolderlist.begin()); i != cfgfolderlist.end(); ++i) { + if (min_entry) + longstring = longstring + QString(";"); + longstring = longstring + (*i); + i++; + longstring = longstring + QString(";") + (*i); + min_entry = true; + } + + cfg.writeEntry("LocalFolders", longstring); + cfg.sync(); +} + +void RsyncPlugin::loadSettings() +{ + if (m_bSettingsLoaded) + return; + + KConfig cfg ("rsyncrc", false, false); + cfg.setGroup ("General"); + + cfgfolderlist = cfg.readListEntry("LocalFolders", ';'); + + m_bSettingsLoaded = true; +} + +QString RsyncPlugin::findLocalFolderByName(QString folderurl) +{ + QString folderurl_stripped; + folderurl_stripped = folderurl; + folderurl_stripped.replace(QString("file://"), QString("")); + for (QStringList::Iterator i(cfgfolderlist.begin()); i != cfgfolderlist.end(); ++i) { + if (QString::compare((*i), folderurl_stripped) == 0) { + i++; + return (*i); + } + } + return NULL; +} + +int RsyncPlugin::deleteLocalFolderByName(QString folderurl) +{ + QString folderurl_stripped; + folderurl_stripped = folderurl; + folderurl_stripped.replace(QString("file://"), QString("")); + for (QStringList::Iterator i(cfgfolderlist.begin()); i != cfgfolderlist.end(); ++i) { + if (QString::compare((*i), folderurl_stripped) == 0) { + i=cfgfolderlist.remove(i); + cfgfolderlist.remove(i); + return 0; + } + } + return 1; +} + +int RsyncPlugin::addLocalFolderByName(QString folderurl, QString remoteurl) +{ + QString folderurl_stripped; + folderurl_stripped = folderurl; + folderurl_stripped.replace(QString("file://"), QString("")); + cfgfolderlist.append(folderurl); + cfgfolderlist.append(remoteurl); + return 1; +} + +RsyncPlugin::RsyncPlugin (QObject* parent, const char* name, + const QStringList&) + :KParts::Plugin (parent, name), + m_pSyncMenu(0) +{ + m_part = ::qt_cast<KonqDirPart*>(parent); + + if ( !m_part || !m_part->scrollWidget() ) + return; + + m_pSyncNow = new KAction(i18n("Synchronize F&older"), "syncnow", + actionCollection(), "syncnow"); + m_pSyncSetup = new KAction (i18n("Setup Syn&chronization"), "setupsync", + actionCollection(), "setupsync"); + //m_pSyncMenu = new KActionMenu (i18n("Remote Folder S&ynchronization"), "syncmenu", + // actionCollection(), "rsync"); + m_pSyncNow->setIcon("remotesync"); + m_pSyncSetup->setIcon("remotesyncconfig"); + m_pSyncNow->setEnabled (false); + //m_pSyncMenu->setDelayed (false); + //m_pSyncMenu->setWhatsThis(i18n("Synchronizes this folder with a remote server.")); + + //connect (m_pSyncMenu->popupMenu(), SIGNAL (aboutToShow()), + // SLOT (slotShowPopup())); + + connect (m_part, SIGNAL(aboutToOpenURL()), SLOT(slotOpenURL())); + +// // add a menu option for konqis icons/list views +// KAction *syncnow = new KAction(i18n("Synchronize folder contents with server"), +// QApplication::reverseLayout() ? "remotesync" : "remotesync", +// 0, 0, 0, actionCollection(), "syncnow"); +// +// syncnow->setWhatsThis(i18n("Synchronize Folder<p>Synchronizes folder contents with remote server.")); +// + connect(m_pSyncNow, SIGNAL(activated()), this, SLOT(slotSync())); + connect(m_pSyncSetup, SIGNAL(activated()), this, SLOT(slotSetup())); + + loadSettings(); + + // Initialize the rsync backend variables + if (rsyncPath == NULL) { + rsyncPath = strdup(QFile::encodeName(KStandardDirs::findExe("rsync"))); + } + if (suPath == NULL) { + suPath = strdup(QFile::encodeName(KStandardDirs::findExe("su"))); + } + childPid = 0; + isLoggedIn = false; + firstLogin = true; + connectionAuth.keepPassword = true; + //connectionAuth.url.setProtocol("fish"); + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + isStat = false; // FIXME: just a workaround for konq deficiencies + redirectUser = ""; // FIXME: just a workaround for konq deficiencies + redirectPass = ""; // FIXME: just a workaround for konq deficiencies +} + +RsyncPlugin::~RsyncPlugin() +{ + delete m_pSyncMenu; +} + +void RsyncPlugin::slotOpenURL () +{ + KURL url = m_part->url(); + + if (m_pURL != url) + { + // See if this URL is in the list of rsync-able directories + if (findLocalFolderByName(url.directory(true, true) + QString("/") + url.fileName(true)) != NULL) { + m_pSyncNow->setEnabled (true); + } + else { + m_pSyncNow->setEnabled (false); + } + } + m_pURL = url; +} + +// void RsyncPlugin::slotShowPopup() +// { +// if (!m_part) +// { +// m_pSyncMenu->setEnabled (false); +// return; +// } +// +// int id = 0; +// uint enableReset = 0; +// +// QString label; +// QStringList inodes; +// +// m_pSyncMenu->popupMenu()->clear(); +// m_pSyncMenu->popupMenu()->insertTitle (i18n("Remote Folder Synchronization")); +// id = m_pSyncMenu->popupMenu()->insertItem (i18n("Synchronize Now"), this, SLOT(slotSync())); +// } + +void RsyncPlugin::slotSetup() +{ + KURL url = m_part->url(); + + m_pSyncSetup->setEnabled (false); + + // Look up settings + QString localfolder = url.directory(true, true) + QString("/") + url.fileName(true); + QString remotefolder = findLocalFolderByName(url.directory(true, true) + QString("/") + url.fileName(true)); + + m_configDialog = new RsyncConfigDialog(0, "rsyncConfig", i18n("Remote Folder Synchronization"), i18n("Configuring Remote Folder Synchronization"), localfolder, remotefolder, true); + m_configDialog->show(); + + connect (m_configDialog, SIGNAL(okClicked()), SLOT(slotSetupOK())); + connect (m_configDialog, SIGNAL(cancelClicked()), SLOT(slotSetupCancelled())); +} + +void RsyncPlugin::slotSetupOK() +{ + if (!m_part) + return; + + KURL url = m_part->url(); + + // Look up settings + QString localfolder = url.directory(true, true) + QString("/") + url.fileName(true); + QString remotefolder = findLocalFolderByName(localfolder); + QString remotefolder_new = m_configDialog->lineEdit()->text().ascii(); + + // See if an old entry has to be deleted + if (remotefolder.isEmpty() == false) { + deleteLocalFolderByName(localfolder); + } + if (remotefolder_new.isEmpty() == false) { + addLocalFolderByName(localfolder, remotefolder_new); + } + saveSettings(); + + if (remotefolder_new.isEmpty() == false) { + m_pSyncNow->setEnabled (true); + } + else { + m_pSyncNow->setEnabled (false); + } + m_pSyncSetup->setEnabled (true); +} + +void RsyncPlugin::slotSetupCancelled() +{ + m_pSyncSetup->setEnabled (true); +} + +void RsyncPlugin::slotRsyncCancelled() +{ + shutdownConnection(true, true); + if (m_progressDialogExists == true) { + m_progressDialog->progressBar()->setValue(m_progressDialog->progressBar()->totalSteps()); + } + m_pSyncNow->setEnabled (true); +} + +void RsyncPlugin::slotSync() +{ + if (!m_part) + return; + + KURL url = m_part->url(); + + m_pSyncNow->setEnabled (false); + + // Initiate rsync + connectionStart(url.directory(true, true) + QString("/") + url.fileName(true), findLocalFolderByName(url.directory(true, true) + QString("/") + url.fileName(true))); + + m_progressDialogExists = false; + m_pSyncNow->setEnabled (true); +} + +typedef KGenericFactory<RsyncPlugin> RsyncFactory; +K_EXPORT_COMPONENT_FACTORY (librsyncplugin, RsyncFactory("rsyncplugin")) + +#include "rsyncplugin.moc" |