diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 4aed2c8219774f5d797760606b8489a92ddc5163 (patch) | |
tree | 3f8c130f7d269626bf6a9447407ef6c35954426a /kioslave | |
download | tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.tar.gz tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kioslave')
400 files changed, 62404 insertions, 0 deletions
diff --git a/kioslave/DEBUG.howto b/kioslave/DEBUG.howto new file mode 100644 index 000000000..5275a26eb --- /dev/null +++ b/kioslave/DEBUG.howto @@ -0,0 +1,89 @@ +This document describes how you can debug an io-slave with gdb. + +How does an io-slave get started? +================================= + +Your application request 'klauncher' via DCOP for a slave. If 'klauncher' does +not have an idle slave ready, it will ask kdeinit to start a new one. +kdeinit forks and dlopens the library that contains the io-slave. +Then it calls kdemain() or, if that is not present, main() in the library. + + +Attaching gdb to a io-slave +=========================== + +Due to the above sequence it is rather hard to get an io-slave in your +debugger. But wait there is hope. You can start klauncher in such a way +that slaves for a certain protocol are started in debug mode. + +E.g. to start all 'http' slaves in debug mode, you type: + + KDE_SLAVE_DEBUG_WAIT=http kdeinit + +This will restart 'kdeinit' and 'klauncher'. + +When your application now requests a http slave, the slave will be started +by kdeinit, but before it calls kdemain() (cq. main()) it will suspend the +slave by sending it a SIGSTOP signal. + +In the terminal from which you started kdeinit you will get the following +message: + +kdeinit: Suspending process +kdeinit: 'gdb kdeinit 16779' to debug +kdeinit: 'kill -SIGCONT 16779' to continue + +You can now debug your slave by typing (or pasting) 'gdb kdeinit 16779' in +a terminal. If you don't want to debug a slave you can let it continue by +sending it a SIGCONT by typing 'kill -SIGCONT 16779'. + +Be aware that slaves will not be killed while they are suspended. + +Once you have started gdb, you can set e.g. breakpoints and then resume the +slave by typing 'continue'. The debugger will return immediate with a message +that a SIGSTOP has been received so you will have to type 'continue' a second +time. + + +Debugging io-slaves with valgrind +================================= + +KLauncher can be told to run certain io-slaves through valgrind. The following +command can be used to let klauncher run all https io-slaves via valgrind: + + KDE_SLAVE_VALGRIND=https kdeinit + +The valgrind output will appear as the stderr output of the kdeinit process. +The $VALGRIND_OPTS environment variable can be used to pass options to valgrind. +If you want to use a different skin: + + KDE_SLAVE_VALGRIND_SKIN=calltree ( for example ) + + +How to get debug output +======================= + +It is useful to redirect the debug output of your particular slave to a file +instead of stderr. E.g. I myself use the following lines in +$KDEDIR/share/config/kdebugrc. + + [7113] + InfoOutput=0 + InfoFilename=/tmp/http + [7103] + InfoOutput=0 + InfoFilename=/tmp/http + +This redirects all debug info for areas 7103 and 7113 (as used by kio_http) +to the file /tmp/http. + +To get debug information from the SMB slave you can add the following to +kioslaverc: + +[SMB] +DebugLevel=100 + +This will print additional debug info to the stderr of your kdeinit process, +which typically ends up in ~/.X.err or ~/.xsession-errors + +Happy debugging. diff --git a/kioslave/DESIGN b/kioslave/DESIGN new file mode 100644 index 000000000..92a03b212 --- /dev/null +++ b/kioslave/DESIGN @@ -0,0 +1,43 @@ +What is a kioslave you ask yourself? + +A kioslave is a program designed to be intimately familiar with a certian +protocol, so that a standardized interface can be used to get at data from +any number of places. A few examples are the http and ftp kioslaves, +which using nearly identical methods will retrieve data from an http or +ftp server respectively. + +Well, that's nice. How do they work? + +To understand it, you'll need two ice cubes, a pair of handcuffs, and a +ferret. Some Crisco (or other shortening) is optional. Well, that aside, +this document focuses on the business end of the whole kio library. The +ioslave. See the documentation of the SlaveBase class for the methods +you need to reimplement, and see +http://developer.kde.org/documentation/design/kde/ioslaves/ for more docu +online. + +That's nice, but how can I use it? + +Any time you'd like to use non blocking IO over a high level protocol +(such as HTTP or FTP) a kioslave is for you. + +That's nice, but how do I use it? + +Basically, you create "jobs" by calling a public KIO::blah method +(the correct prototypes, etc, are in kio/job.h). Once this is done, you +connect to the result() signal, and wait for the result. There are +other signals emitted by jobs, see kio/jobclasses.h. Once again, +see the online documentation for more. + + +If you are interested in working on an ioslave, +the following slaves are severely lacking in functionality: + + SMTP + SMB + +------------- + +Original document by Rich. +Updated for KDE 2 by David. + diff --git a/kioslave/Makefile.am b/kioslave/Makefile.am new file mode 100644 index 000000000..67e21cd87 --- /dev/null +++ b/kioslave/Makefile.am @@ -0,0 +1,11 @@ +if include_kioslave_ldap +LDAP_SUBDIR=ldap +endif + +if include_kioslave_smb +SMB_SUBDIR=smb +endif + +SUBDIRS = about cgi floppy filter fish info mac man nfs nntp pop3 smtp \ + sftp tar finger thumbnail $(LDAP_SUBDIR) $(SMB_SUBDIR) settings trash media \ + remote home system diff --git a/kioslave/about/Makefile.am b/kioslave/about/Makefile.am new file mode 100644 index 000000000..9f0e959e1 --- /dev/null +++ b/kioslave/about/Makefile.am @@ -0,0 +1,21 @@ +## Makefile.am of kdebase/kioslave/about + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +####### Files + +kde_module_LTLIBRARIES = kio_about.la + +kio_about_la_SOURCES = kio_about.cpp +kio_about_la_LIBADD = $(LIB_KSYCOCA) +kio_about_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = kio_about.h + +kdelnk_DATA = about.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/kio_about.pot diff --git a/kioslave/about/about.protocol b/kioslave/about/about.protocol new file mode 100644 index 000000000..bc685bfe5 --- /dev/null +++ b/kioslave/about/about.protocol @@ -0,0 +1,9 @@ +[Protocol] +exec=kio_about +protocol=about +input=none +output=filesystem +reading=true +defaultMimetype=text/html +Icon=help_index +Class=:local diff --git a/kioslave/about/kio_about.cpp b/kioslave/about/kio_about.cpp new file mode 100644 index 000000000..226de2f4f --- /dev/null +++ b/kioslave/about/kio_about.cpp @@ -0,0 +1,76 @@ +/* This file is part of the KDE libraries + + Copyright (c) 2002 John Firebaugh <jfirebaugh@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kio_about.h" + +#include <stdlib.h> +#include <qstring.h> +#include <kinstance.h> +#include <kurl.h> + +using namespace KIO; + +AboutProtocol::AboutProtocol(const QCString &pool_socket, const QCString &app_socket) + : SlaveBase("about", pool_socket, app_socket) +{ +} + +AboutProtocol::~AboutProtocol() +{ +} + +void AboutProtocol::get( const KURL& ) +{ + QByteArray output; + + QTextStream os( output, IO_WriteOnly ); + os.setEncoding( QTextStream::Latin1 ); // In fast ASCII + + os << "<html><head><title>about:blank</title></head><body></body></html>"; + + data( output ); + finished(); +} + +void AboutProtocol::mimetype( const KURL& ) +{ + mimeType("text/html"); + finished(); +} + +extern "C" +{ + int KDE_EXPORT kdemain( int argc, char **argv ) { + + KInstance instance("kio_about"); + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_about protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + AboutProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + return 0; + } +} + diff --git a/kioslave/about/kio_about.h b/kioslave/about/kio_about.h new file mode 100644 index 000000000..29982dbe7 --- /dev/null +++ b/kioslave/about/kio_about.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE libraries + + Copyright (c) 2002 John Firebaugh <jfirebaugh@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ +#ifndef __kio_about_h__ +#define __kio_about_h__ + +#include <qcstring.h> + +#include <kio/global.h> +#include <kio/slavebase.h> + + +class AboutProtocol : public KIO::SlaveBase +{ +public: + AboutProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~AboutProtocol(); + + virtual void get(const KURL& url); + virtual void mimetype(const KURL& url); +}; + +#endif diff --git a/kioslave/cgi/Makefile.am b/kioslave/cgi/Makefile.am new file mode 100644 index 000000000..cc71753e9 --- /dev/null +++ b/kioslave/cgi/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = kcmcgi + +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = kio_cgi.la + +kio_cgi_la_SOURCES = cgi.cpp +kio_cgi_la_LIBADD = $(LIB_KIO) +kio_cgi_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = cgi.h + +METASOURCES = AUTO + +kdelnkdir = $(kde_servicesdir) +kdelnk_DATA = cgi.protocol diff --git a/kioslave/cgi/README b/kioslave/cgi/README new file mode 100644 index 000000000..d68fc9d2c --- /dev/null +++ b/kioslave/cgi/README @@ -0,0 +1,15 @@ +The CGI IO slave provides a way to execute CGI programs without the need to +have a running web server. This can for example be used for local testing of +CGI programs or for using search engines that only provide a CGI frontend like +the one from Doxygen. + +The IO slave implements the cgi: protocol. It uses the filename from the given +URL and searches a configurable list of directories. If it finds an executable +with the given name it executes it, passes the arguments of the URL and sets the +environment variables needed by CGI programs. + +The kcontrol module System/kcmcgi is used to configure the search paths for CGI +programs. + +If you have questions or comments please contact Cornelius Schumacher +<schumacher@kde.org>. diff --git a/kioslave/cgi/cgi.cpp b/kioslave/cgi/cgi.cpp new file mode 100644 index 000000000..011760e0b --- /dev/null +++ b/kioslave/cgi/cgi.cpp @@ -0,0 +1,273 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + Copyright 2006 Michael Pyne <michael.pyne@kde.org> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#include <stdio.h> +#include <stdlib.h> + +#include <qdir.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kprocess.h> +#include <kstandarddirs.h> +#include <kinstance.h> +#include <klocale.h> +#include <kconfig.h> + +#include "cgi.h" + +using namespace KIO; + +CgiProtocol::CgiProtocol( const QCString &pool, const QCString &app ) + : SlaveBase( "cgi", pool, app ) +{ + kdDebug(7124) << "CgiProtocol::CgiProtocol" << endl; + + KConfig cfg( "kcmcgirc" ); + cfg.setGroup( "General" ); + mCgiPaths = cfg.readListEntry( "Paths" ); +} + +CgiProtocol::~CgiProtocol() +{ + kdDebug(7124) << "CgiProtocol::~CgiProtocol" << endl; +} + +/** + * Search in reverse order through a QByteArray for a given character. The position + * of the character is returned, or -1 if it was not found. + */ +static int qByteArrayFindRev( const QByteArray &ba, char c, int startIndex ) +{ + for ( int i = startIndex; i >= 0; --i ) + if ( ba[i] == c ) return i; + + return -1; +} + +/** + * Extract data in ba from start, including no more than len characters from ba. + * Should be exactly comparable to QCString::mid() + */ +static QCString extractQCString( const QByteArray &ba, uint start, uint len = 0xffffffff ) +{ + uint realLen = len; + + if ( ( ba.size() - start ) < len ) + realLen = ba.size() - start; + + return QCString( &ba[ start ], realLen + 1 ); +} + +/** + * Search through a QByteArray for a given string. The position of the string + * is returned, or -1 if it was not found. + */ +static int qByteArrayFindStr( const QByteArray &ba, const char *str ) +{ + int strLen = qstrlen( str ); + int searchLen = ba.size() - strLen; + + for ( int i = 0; i <= searchLen; ++i ) { + QCString temp = extractQCString( ba, i, strLen ); + if ( temp == str ) + return i; + } + + return -1; +} + +void CgiProtocol::get( const KURL& url ) +{ + kdDebug(7124) << "CgiProtocol::get()" << endl; + kdDebug(7124) << " URL: " << url.url() << endl; +#if 0 + kdDebug(7124) << " Path: " << url.path() << endl; + kdDebug(7124) << " Query: " << url.query() << endl; + kdDebug(7124) << " Protocol: " << url.protocol() << endl; + kdDebug(7124) << " Filename: " << url.filename() << endl; +#endif + QCString protocol = "SERVER_PROTOCOL=HTTP"; + putenv( protocol.data() ); + + QCString requestMethod = "REQUEST_METHOD=GET"; + putenv( requestMethod.data() ); + + QCString query = url.query().mid( 1 ).local8Bit(); + query.prepend( "QUERY_STRING=" ); + putenv( query.data() ); + + QString path = url.path(); + + QString file; + + int pos = path.findRev('/'); + if ( pos >= 0 ) file = path.mid( pos + 1 ); + else file = path; + + QString cmd; + + bool stripHeader = false; + bool forwardFile = true; + + QStringList::ConstIterator it; + for( it = mCgiPaths.begin(); it != mCgiPaths.end(); ++it ) { + cmd = *it; + if ( !(*it).endsWith("/") ) + cmd += "/"; + cmd += file; + if ( KStandardDirs::exists( cmd ) ) { + forwardFile = false; + stripHeader = true; + break; + } + } + + FILE *fd; + + if ( forwardFile ) { + kdDebug(7124) << "Forwarding to '" << path << "'" << endl; + + QCString filepath = QFile::encodeName( path ); + + fd = fopen( filepath.data(), "r" ); + + if ( !fd ) { + kdDebug(7124) << "Error opening '" << filepath << "'" << endl; + error( KIO::ERR_CANNOT_OPEN_FOR_READING, filepath ); + return; + } + } else { + kdDebug(7124) << "Cmd: " << cmd << endl; + + fd = popen( QFile::encodeName(KProcess::quote( cmd )).data(), "r" ); + + if ( !fd ) { + kdDebug(7124) << "Error running '" << cmd << "'" << endl; + error( KIO::ERR_CANNOT_OPEN_FOR_READING, cmd ); + return; + } + } + + char buffer[ 4090 ]; + + while ( !feof( fd ) ) + { + int n = fread( buffer, 1, 2048, fd ); + + if ( n == -1 ) + { + // ERROR + if ( forwardFile ) { + fclose( fd ); + } else { + pclose( fd ); + } + return; + } + + buffer[n] = 0; + + if ( stripHeader ) { + QByteArray output; + + // Access the buffer in-place by using setRawData() + output.setRawData( buffer, n ); + + int colon = output.find( ':' ); + int newline = output.find( '\n' ); + int semicolon = qByteArrayFindRev( output, ';', newline ); + int end; + if ( semicolon < 0 ) end = newline; + else end = semicolon; + +#if 0 + kdDebug(7124) << " colon: " << colon << endl; + kdDebug(7124) << " newline: " << newline << endl; + kdDebug(7124) << " semicolon: " << semicolon << endl; + kdDebug(7124) << " end: " << end << endl; +#endif + + QCString contentType = extractQCString( output, colon + 1, end - colon - 1 ); + + contentType = contentType.stripWhiteSpace(); + + kdDebug(7124) << "ContentType: '" << contentType << "'" << endl; + + mimeType( contentType ); + + int start = qByteArrayFindStr( output, "\r\n\r\n" ); + if ( start >= 0 ) start += 4; + else { + start = qByteArrayFindStr( output, "\n\n" ); + if ( start >= 0 ) start += 2; + } + + if ( start < 0 ) + start = 0; + + // We're done with the part of the buffer we're using. + output.resetRawData ( buffer, n ); + + // Now access the part of the buffer after the header. + output.setRawData ( buffer + start, n - start ); + data( output ); + output.resetRawData ( buffer + start, n - start ); + + stripHeader = false; + } else { + QByteArray array; + array.setRawData( buffer, n ); + data( array ); + array.resetRawData( buffer, n ); + } + } + + if ( forwardFile ) { + fclose( fd ); + } else { + pclose( fd ); + } + + finished(); + + kdDebug(7124) << "CgiProtocol::get - done" << endl; +} + +extern "C" { int KDE_EXPORT kdemain( int argc, char **argv ); } + +/*! The kdemain function generates an instance of the ioslave and starts its + * dispatch loop. */ + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_cgi" ); + + kdDebug(7124) << "kio_cgi starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_cgi protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + CgiProtocol slave( argv[2], argv[3] ); + slave.dispatchLoop(); + + return 0; +} diff --git a/kioslave/cgi/cgi.h b/kioslave/cgi/cgi.h new file mode 100644 index 000000000..fec90aa59 --- /dev/null +++ b/kioslave/cgi/cgi.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#ifndef KIO_CGI_H +#define KIO_CGI_H + +#include <qobject.h> + +#include <kio/slavebase.h> + +class KProcess; + +/*! + This class implements an ioslave for viewing CGI script output without the + need to run a web server. +*/ +class CgiProtocol : public KIO::SlaveBase +{ + public: + CgiProtocol( const QCString &pool, const QCString &app ); + virtual ~CgiProtocol(); + + virtual void get( const KURL& url ); + +// virtual void mimetype( const KURL& url ); + + protected: +// QCString errorMessage(); + + private: + QStringList mCgiPaths; +}; + +#endif diff --git a/kioslave/cgi/cgi.protocol b/kioslave/cgi/cgi.protocol new file mode 100644 index 000000000..9c6cc378e --- /dev/null +++ b/kioslave/cgi/cgi.protocol @@ -0,0 +1,8 @@ +[Protocol] +exec=kio_cgi +protocol=cgi +input=none +output=filesystem +reading=true +DocPath=kioslave/cgi.html +Icon=html diff --git a/kioslave/cgi/kcmcgi/Makefile.am b/kioslave/cgi/kcmcgi/Makefile.am new file mode 100644 index 000000000..abfef594b --- /dev/null +++ b/kioslave/cgi/kcmcgi/Makefile.am @@ -0,0 +1,17 @@ + +kde_module_LTLIBRARIES = kcm_cgi.la + +kcm_cgi_la_SOURCES = kcmcgi.cpp +kcm_cgi_la_LDFLAGS = $(all_libraries) -module -avoid-version -no-undefined +kcm_cgi_la_LIBADD = -lkdeui $(LIB_KIO) + +INCLUDES= $(all_includes) + +kcm_cgi_la_METASOURCES = AUTO + +noinst_HEADERS = kcmcgi.h + +xdg_apps_DATA = kcmcgi.desktop + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kcmcgi.pot diff --git a/kioslave/cgi/kcmcgi/kcmcgi.cpp b/kioslave/cgi/kcmcgi/kcmcgi.cpp new file mode 100644 index 000000000..18436e8d9 --- /dev/null +++ b/kioslave/cgi/kcmcgi/kcmcgi.cpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include <kconfig.h> +#include <klocale.h> +#include <kglobal.h> +#include <kaboutdata.h> +#include <kfiledialog.h> + +#include <qlayout.h> +#include <qlistbox.h> +#include <qpushbutton.h> +#include <qgroupbox.h> +#include <qhbox.h> + +#include "kcmcgi.h" +#include "kcmcgi.moc" + +extern "C" +{ + KDE_EXPORT KCModule *create_cgi( QWidget *parent, const char * ) + { + KGlobal::locale()->insertCatalogue("kcmcgi"); + return new KCMCgi( parent, "kcmcgi" ); + } +} + + +KCMCgi::KCMCgi(QWidget *parent, const char *name) + : KCModule(parent, name) +{ + setButtons(Default|Apply); + + QVBoxLayout *topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint()); + + QGroupBox *topBox = new QGroupBox( 1, Horizontal, i18n("Paths to Local CGI Programs"), this ); + topLayout->addWidget( topBox ); + + mListBox = new QListBox( topBox ); + + QHBox *buttonBox = new QHBox( topBox ); + buttonBox->setSpacing( KDialog::spacingHint() ); + + mAddButton = new QPushButton( i18n("Add..."), buttonBox ); + connect( mAddButton, SIGNAL( clicked() ), SLOT( addPath() ) ); + + mRemoveButton = new QPushButton( i18n("Remove"), buttonBox ); + connect( mRemoveButton, SIGNAL( clicked() ), SLOT( removePath() ) ); + connect( mListBox, SIGNAL( clicked ( QListBoxItem * )),this, SLOT( slotItemSelected( QListBoxItem *))); + + mConfig = new KConfig("kcmcgirc"); + + load(); + updateButton(); + KAboutData *about = + new KAboutData( I18N_NOOP("kcmcgi"), + I18N_NOOP("CGI KIO Slave Control Module"), + 0, 0, KAboutData::License_GPL, + I18N_NOOP("(c) 2002 Cornelius Schumacher") ); + + about->addAuthor( "Cornelius Schumacher", 0, "schumacher@kde.org" ); + setAboutData(about); +} + +KCMCgi::~KCMCgi() +{ + delete mConfig; +} + +void KCMCgi::slotItemSelected( QListBoxItem * ) +{ + updateButton(); +} + +void KCMCgi::updateButton() +{ + mRemoveButton->setEnabled( mListBox->selectedItem ()); +} + +void KCMCgi::defaults() +{ + mListBox->clear(); + updateButton(); +} + +void KCMCgi::save() +{ + QStringList paths; + + uint i; + for( i = 0; i < mListBox->count(); ++i ) { + paths.append( mListBox->text( i ) ); + } + + mConfig->setGroup( "General" ); + mConfig->writeEntry( "Paths", paths ); + + mConfig->sync(); +} + +void KCMCgi::load() +{ + mConfig->setGroup( "General" ); + QStringList paths = mConfig->readListEntry( "Paths" ); + + mListBox->insertStringList( paths ); +} + +void KCMCgi::addPath() +{ + QString path = KFileDialog::getExistingDirectory( QString::null, this ); + + if ( !path.isEmpty() ) { + mListBox->insertItem( path ); + emit changed( true ); + } + updateButton(); +} + +void KCMCgi::removePath() +{ + int index = mListBox->currentItem(); + if ( index >= 0 ) { + mListBox->removeItem( index ); + emit changed( true ); + } + updateButton(); +} + +QString KCMCgi::quickHelp() const +{ + return i18n("<h1>CGI Scripts</h1> The CGI KIO slave lets you execute " + "local CGI programs without the need to run a web server. " + "In this control module you can configure the paths that " + "are searched for CGI scripts."); +} diff --git a/kioslave/cgi/kcmcgi/kcmcgi.desktop b/kioslave/cgi/kcmcgi/kcmcgi.desktop new file mode 100644 index 000000000..a948a38b4 --- /dev/null +++ b/kioslave/cgi/kcmcgi/kcmcgi.desktop @@ -0,0 +1,231 @@ +[Desktop Entry] +Icon=run +Type=Application +Exec=kcmshell kcmcgi +DocPath= +X-KDE-ModuleType=Library +X-KDE-Library=cgi + +Name=CGI Scripts +Name[af]=CGI Skripte +Name[ar]=نصوص CGI البرمجية +Name[az]=CGI SkriptlÉ™ri +Name[be]=СцÑнары CGI +Name[bg]=CGI Ñкриптове +Name[bn]=সি-জি-আই সà§à¦•à§à¦°à¦¿à¦ªà§à¦Ÿ +Name[br]=Urzhiaouegoù CGI +Name[bs]=CGI skripte +Name[ca]=Scripts CGI +Name[cs]=CGI skripty +Name[csb]=Skriptë CGI +Name[cy]=Sgriptiau CGI +Name[da]=CGI Scripter +Name[de]=CGI-Skripte +Name[el]=ΣενάÏια CGI +Name[eo]=CGI-Skriptaĵoj +Name[es]=Procedimientos CGI +Name[et]=CGI skriptid +Name[eu]=CGI scriptak +Name[fa]=دست‌نوشته‌های CGI +Name[fi]=CGI-komentosarjat +Name[fr]=Scripts CGI +Name[fy]=CGI-skripts +Name[ga]=Scripteanna CGI +Name[gl]=Guións CGI +Name[he]=תסריטי CGI +Name[hi]=सीजीआई सà¥à¤•à¥à¤°à¤¿à¤ªà¥à¤Ÿ +Name[hr]=CGI skripte +Name[hu]=CGI-programok +Name[is]=CGI Skriftur +Name[it]=Script CGI +Name[ja]=CGI スクリプト +Name[ka]=CGI სკრიპტები +Name[kk]=CGI Ñкрипттері +Name[km]=ស្គ្រីប CGI +Name[ko]=CGI 스í¬ë¦½íŠ¸ +Name[lo]=ໂà»àºŠàº¥àº²àº¥àºµàºª +Name[lt]=CGI scenarijai +Name[lv]=CGI Skripts +Name[mk]=CGI-Ñкрипти +Name[mn]=CGI-Скрипт +Name[ms]=Skrip CGI +Name[mt]=Scripts CGI +Name[nb]=CGI-skript +Name[nds]=CGI-Skripten +Name[ne]=CGI सà¥à¤•à¥à¤°à¤¿à¤ªà¥à¤Ÿ +Name[nl]=CGI-scripts +Name[nn]=CGI-skript +Name[nso]=Ditshwaelo tsa CGI +Name[pa]=CGI ਸਕà©à¨°à¨¿à¨ªà¨Ÿà¨¾à¨‚ +Name[pl]=Skrypty CGI +Name[pt]=Programas CGI +Name[pt_BR]=Scripts CGI +Name[ro]=Scripturi CGI +Name[ru]=Сценарии CGI +Name[rw]=Agaporogaramu CGI +Name[se]=CGI-skriptat +Name[sk]=Skripty CGI +Name[sl]=Skripte CGI +Name[sr]=CGI Скрипте +Name[sr@Latn]=CGI Skripte +Name[sv]=CGI-skript +Name[ta]=CGI எழà¯à®¤à¯à®¤à®¾à®•à¯à®•à®™à¯à®•à®³à¯ +Name[te]=సిజిఠసà±à°•à±à°°à°¿à°ªà±à°Ÿà±à°²à± +Name[tg]=ДаÑтнавиÑи CGI +Name[th]=สคริปต์ CGI +Name[tr]=CD Betikleri +Name[tt]=CGI Ämerleklär +Name[uk]=Скрипти CGI +Name[uz]=CGI skriptlar +Name[uz@cyrillic]=CGI Ñкриптлар +Name[ven]=Zwikiriputi zwa CGI +Name[vi]=Văn lệnh CGI +Name[wa]=Scripe CGI +Name[xh]=Amagama ashicilelwe phantsi CGI +Name[zh_CN]=CGI 脚本 +Name[zh_TW]=CGI 命令稿 +Name[zu]=Izikript ze-CGI +Comment=Configure the CGI KIO slave +Comment[af]=Stel die CGI KIO slaaf op +Comment[ar]=تهيئة CGI KIO slave +Comment[be]=ÐаÑтаўленні CGI KIO slave +Comment[bg]=ÐаÑтройване на модула за изпълнение на Ñкриптове без уеб Ñървър - CGI KIO +Comment[bn]=CGI KIO সà§à¦²à§‡à¦ কনফিগার করà§à¦¨ +Comment[bs]=PodeÅ¡avanje CGI KIO slave-a +Comment[ca]=Configura l'esclau KIO CGI +Comment[cs]=Nastavenà CGI pro KDE +Comment[csb]=Kònfigùracëjô procedurë òbsÅ‚użënkù CGI +Comment[cy]=Ffurfweddu'r gwas CGI KIO +Comment[da]=Indstilling af CGI KIO-slaven +Comment[de]=Ein-/Ausgabemodul für CGI einrichten +Comment[el]=Ρυθμίστε το CGI KIO slave +Comment[eo]=Agordu CGI-enel-sklavon +Comment[es]=Configuración del KIO slave de CGI +Comment[et]=CGI KIO mooduli seadistamine +Comment[eu]=CGI KIO slave-a konfiguratu +Comment[fa]=پیکربندی پی‌رو CGI KIO +Comment[fi]=Muokkaa CGI-KIO-palvelun asetuksia +Comment[fr]=Configuration du CGI KIO slave +Comment[fy]=Hjir kinne jo de CGI Kio-slave ynstelle +Comment[ga]=Cumraigh an sclábhaà CGI KIO +Comment[gl]=Configuración do escravo KIO de CGI +Comment[he]=×©×™× ×•×™ הגדרות פרוטוקול ×”Ö¾CGI +Comment[hi]=सीजीआई केआईओ सà¥à¤²à¥‡à¤µ कॉनà¥à¤«à¤¼à¤¿à¤—र करें +Comment[hr]=Konfiguriranje CGI KIO podÄinjenog +Comment[hu]=A CGI KDE-protokoll beállÃtásai +Comment[is]=Stilla CGI þrælinn +Comment[it]=Configura il KIO-slave CGI +Comment[ja]=CGI KIO スレーブã®è¨å®š +Comment[ka]=CGI KIO slave-ის კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ +Comment[kk]=CGI KIO slave-Ñ‚Ñ‹ баптау +Comment[km]=កំណážáŸ‹â€‹ážšáž…នាសម្ពáŸáž“្ធ​កូនចៅ CGI KIO +Comment[ko]=CGI KIO ìŠ¬ë ˆì´ë¸Œ ì„¤ì • +Comment[lo]=ປັບà»àº•à»ˆàº‡àºà»‰àºàº‡ +Comment[lt]=KonfigÅ«ruoti CGI KIO slave +Comment[lv]=KonfigurÄ“t CGI KIO vergu +Comment[mk]=Конфигурација на CGI KIO Ñлужителот +Comment[mn]=CGI-Оролт/Гаралтын-Модул тохируулах +Comment[ms]=Konfigur hamba CGI KIO +Comment[mt]=Ikkonfigura l-iskjav CGI +Comment[nb]=Tilpass CGI KIO slave +Comment[nds]=Den CGI-In-/Utgaavdeenst instellen +Comment[ne]=CGI KIO सà¥à¤²à¥‡à¤ कनà¥à¤«à¤¿à¤—र गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Hier kunt u de CGI Kio-slave instellen +Comment[nn]=Set opp CGI-KIO-slaven +Comment[nso]=Beakanya lekgoba la KIO ya CGI +Comment[pa]=CGI KIO ਸਲੇਵ ਸੰਰਚਨਾ +Comment[pl]=Konfiguracja procedury obsÅ‚ugi CGI +Comment[pt]=Configuração do KIO slave de CGIs +Comment[pt_BR]=Configurar o KIO (escravo) do CGI +Comment[ro]=Configurează dispozitivul I/O CGI +Comment[ru]=ÐаÑтройка CGI KIO slave +Comment[rw]=Kuboneza CGI KIO umugaragu +Comment[se]=Heivet CGI-KIO-Å¡láva +Comment[sk]=Nastavenie IO klienta CGI +Comment[sl]=Nastavi podrejenega KIO CGI +Comment[sr]=Подешавање CGI KIO slave-а +Comment[sr@Latn]=PodeÅ¡avanje CGI KIO slave-a +Comment[sv]=Anpassa I/O-slaven för CGI +Comment[ta]=CGI KIO slave஠வடிவமை +Comment[tg]=Бандаи CGI KIO-ро танзим кунед +Comment[th]=ปรับà¹à¸•à¹ˆà¸‡ CGI KIO slave +Comment[tr]=CGI KIO aracısını yapılandır +Comment[tt]=CGI KIO slave caylawı +Comment[uk]=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ð»ÐµÐ³Ð»Ð¾Ð³Ð¾ KIO CGI +Comment[uz]=CGI KIO sleyvni moslash +Comment[uz@cyrillic]=CGI KIO Ñлейвни моÑлаш +Comment[ven]=Dzudzanyani phuli CGI KIO +Comment[vi]=Cấu hình Ä‘Ã y tá»›CGI KIO +Comment[wa]=Apontyî li mandaye KIO CGI +Comment[xh]=Qwlalsela i CGI KIO slave +Comment[zh_CN]=é…ç½® CGI KIO 仆人 +Comment[zh_TW]=è¨å®š CGI KIO slave +Comment[zu]=Hlanganisela i-CGI KIO slave + +Keywords=CGI,KIO,Slave,Paths +Keywords[ar]=CGI,KIO,Slave,Paths,مسارات +Keywords[az]=CGI,KIO,Slave,Paths,Cığırlar +Keywords[be]=ШлÑÑ…Ñ–,CGI,KIO,Slave,Paths +Keywords[bg]=Ñкриптове, уеб, динамичен, Ñкрипт, Интернет, път, пътища, CGI, KIO, Slave, Paths +Keywords[br]=CGI,KIO,sklav,hentoù +Keywords[ca]=CGI,KIO,Esclau,Rutes +Keywords[cs]=CGI,KIO,slave,cesty +Keywords[csb]=CGI,KIO,procedurë wé/wi,stegnë +Keywords[cy]=CGI,KIO,Gwas,Llwybrau +Keywords[da]=CGI,KIO,Slave,Stier +Keywords[de]=CGI,KIO,Ein-/Ausgabemodul,Pfade +Keywords[el]=CGI,KIO,Slave,ΔιαδÏομÎÏ‚ +Keywords[eo]=CGI,Enel,K-enel,sklavo,servo,vojoj +Keywords[es]=CGI,KIO,Slave,Rutas +Keywords[et]=CGI,KIO,moodul,otsinguteed +Keywords[fa]=CGIØŒ KIOØŒ SlaveØŒ مسیرها +Keywords[fi]=CGI,KIO,KIO-palvelu,palvelu,Polut +Keywords[fr]=CGI,KIO,Slave,Paths,Chemins,Emplacements +Keywords[fy]=cgi,kio,slave,paths,paden +Keywords[ga]=CGI,KIO,SclábhaÃ,Bealaà +Keywords[gl]=CGI,KIO,Escravo,Camiños +Keywords[he]=CGI,KIO,פרוטוקול,× ×ª×™×‘×™×, Slave,Paths +Keywords[hi]=सीजीआई,केआईओ,सà¥à¤²à¥‡à¤µ,पथ +Keywords[hr]=CGI,KIO,Slave,Paths,podÄinjeni,putanje +Keywords[hu]=CGI,KIO,protokoll,elérési utak +Keywords[is]=CGI,KIO,þræll,slóðir +Keywords[it]=CGI,KIO,kioslave,percorsi +Keywords[ja]=CGI,KIO,スレーブ,パス +Keywords[km]=CGI,KIO,កូនចៅ,ផ្លូវ +Keywords[lt]=CGI,KIO,Slave,Paths, keliai +Keywords[lv]=CGI,KIO,vergi,ceļi +Keywords[mk]=CGI,KIO,Slave,Paths,Патеки +Keywords[mn]=CGI,KIO,Оролт/Гаралтын-Модул,Зам +Keywords[mt]=CGI,KIO,Slave,Paths,skjav,passaÄ¡Ä¡ +Keywords[nb]=CGI,KIO,Slave,slave,stier +Keywords[nds]=CGI,KIO,Slave,IU,In-/Utgaavdeenst,Deenst,Padden +Keywords[ne]=CGI,KIO,सà¥à¤²à¥‡à¤, मारà¥à¤— +Keywords[nl]=cgi,kio,slave,paths,paden +Keywords[nn]=CGI,KIO,slave,stiar +Keywords[nso]=CGI,KIO,Lekgoba,Ditsejana +Keywords[pa]=CGI,KIO,ਸਲੇਵ,ਮਾਰਗ +Keywords[pl]=CGI,KIO,procedury we/wy,Å›cieżki +Keywords[pt]=CGI,KIO,Slave,Localizações +Keywords[pt_BR]=CGI,KIO,Escravo,Caminhos +Keywords[ro]=I/E,IE,CGI,KIO,dispozitiv,căi +Keywords[rw]=CGI,KIO,Umugaragu,Inzira +Keywords[se]=CGI,KIO,Å¡láva,bálgát +Keywords[sk]=CGI,KIO,klient,cesty +Keywords[sl]=CGI,KIO,podrejeni,pot +Keywords[sr]=CGI,KIO,Slave,Путање +Keywords[sr@Latn]=CGI,KIO,Slave,Putanje +Keywords[sv]=CGI,KIO,Slav,Sökvägar +Keywords[ta]=CGI,KIO,ஸà¯à®²à¯‡à®µà¯,பாதைகள௠+Keywords[te]=సిజిà°,కెà°à°’,బానిస,దారà±à°²à± +Keywords[th]=CGI,KIO,Slave,เส้นทาง +Keywords[tr]=CGI,KIO,Aracı,Yollar +Keywords[uk]=CGI,KIO,підлеглий,шлÑÑ… +Keywords[uz]=CGI,KIO,Sleyv,YoÊ»llar +Keywords[uz@cyrillic]=CGI,KIO,Слейв,Йўллар +Keywords[ven]=CGI,KIO,Phuli,Ludila +Keywords[vi]=CGI,KIO,Äà y tá»›,ÄÆ°á»ng dẫn +Keywords[wa]=CGI,KIO,Slave,Paths,tchimins,mandaye +Keywords[zh_CN]=CGI,KIO,Slave,Paths,路径 +Keywords[zh_TW]=CGI,KIO,Slave,Paths,路徑 +Keywords[zu]=CGI,KIO,Slave,Izindlela +Categories=Qt;KDE;X-KDE-settings-webbrowsing; diff --git a/kioslave/cgi/kcmcgi/kcmcgi.h b/kioslave/cgi/kcmcgi/kcmcgi.h new file mode 100644 index 000000000..10e4e3385 --- /dev/null +++ b/kioslave/cgi/kcmcgi/kcmcgi.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +#ifndef KCMCGI_H +#define KCMCGI_H + +#include <kcmodule.h> + +class QListBox; +class QPushButton; + +class KConfig; + +class KCMCgi : public KCModule +{ + Q_OBJECT + public: + KCMCgi( QWidget *parent = 0, const char *name = 0 ); + ~KCMCgi(); + + void load(); + void save(); + void defaults(); + QString quickHelp() const; + + public slots: + + protected slots: + void addPath(); + void removePath(); + void slotItemSelected( QListBoxItem * item ); + private: + void updateButton(); + QListBox *mListBox; + QPushButton *mAddButton; + QPushButton *mRemoveButton; + + KConfig *mConfig; +}; + +#endif diff --git a/kioslave/configure.in.bot b/kioslave/configure.in.bot new file mode 100644 index 000000000..11b66748d --- /dev/null +++ b/kioslave/configure.in.bot @@ -0,0 +1,7 @@ +if test -z "$SASL2_LIBS"; then + echo "" + echo "cyrus-sasl 2 library is missing. The pop3 and smtp ioslaves will lack of a lot of authentication methods." + echo "See http://asg.web.cmu.edu/sasl/sasl-library.html or your distribution's packages." + echo "" + all_tests=bad +fi diff --git a/kioslave/configure.in.in b/kioslave/configure.in.in new file mode 100644 index 000000000..331a300b8 --- /dev/null +++ b/kioslave/configure.in.in @@ -0,0 +1,15 @@ +KDE_CHECK_SSL + +sasl2_header="no" +SASL2_LIBS="" + +KDE_CHECK_HEADERS(sasl.h)dnl SASL1 header is enough for kio_ldap +KDE_CHECK_HEADERS(sasl/sasl.h, sasl2_header="yes") +if test "$sasl2_header" = "yes" ; then + KDE_CHECK_LIB(sasl2, sasl_client_init, SASL2_LIBS="-lsasl2") +fi + +if test "x$SASL2_LIBS" != "x" ; then + AC_DEFINE_UNQUOTED(HAVE_LIBSASL2, 1, [Define if you have cyrus-sasl2 libraries]) +fi +AC_SUBST(SASL2_LIBS) diff --git a/kioslave/filter/Makefile.am b/kioslave/filter/Makefile.am new file mode 100644 index 000000000..f315064c8 --- /dev/null +++ b/kioslave/filter/Makefile.am @@ -0,0 +1,21 @@ +KDE_CPPFLAGS = -DQT_NO_CAST_ASCII + +INCLUDES = $(all_includes) + +kde_module_LTLIBRARIES = kio_filter.la + +kio_filter_la_SOURCES = filter.cc +kio_filter_la_LIBADD = $(LIB_KSYCOCA) +kio_filter_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = filter.h + +METASOURCES = AUTO + +protocoldir = $(kde_servicesdir) + +if include_BZIP2 +BZIP2FILES=bzip.protocol bzip2.protocol +endif + +protocol_DATA = gzip.protocol $(BZIP2FILES) + diff --git a/kioslave/filter/bzip.protocol b/kioslave/filter/bzip.protocol new file mode 100644 index 000000000..65564992a --- /dev/null +++ b/kioslave/filter/bzip.protocol @@ -0,0 +1,10 @@ +[Protocol] +exec=kio_filter +protocol=bzip +mimetype=application/x-bzip +input=stream +output=stream +reading=true +source=false +DocPath=kioslave/bzip.html +Icon=ark diff --git a/kioslave/filter/bzip2.protocol b/kioslave/filter/bzip2.protocol new file mode 100644 index 000000000..c7d7c782a --- /dev/null +++ b/kioslave/filter/bzip2.protocol @@ -0,0 +1,10 @@ +[Protocol] +exec=kio_filter +protocol=bzip2 +mimetype=application/x-bzip2 +input=stream +output=stream +reading=true +source=false +DocPath=kioslave/bzip2.html +Icon=ark diff --git a/kioslave/filter/configure.in.in b/kioslave/filter/configure.in.in new file mode 100644 index 000000000..5ef522b6f --- /dev/null +++ b/kioslave/filter/configure.in.in @@ -0,0 +1 @@ +AC_FIND_BZIP2 diff --git a/kioslave/filter/filter.cc b/kioslave/filter/filter.cc new file mode 100644 index 000000000..fe7cbc154 --- /dev/null +++ b/kioslave/filter/filter.cc @@ -0,0 +1,167 @@ +/* +This file is part of KDE + + Copyright (C) 1999-2000 Waldo Bastian (bastian@kde.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> + +#include <kinstance.h> +#include <kdebug.h> +#include <kmimemagic.h> +#include <kfilterbase.h> + +#include "filter.h" + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char ** argv) +{ + KInstance instance( "kio_filter" ); + + kdDebug(7110) << "Starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_filter protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + FilterProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7110) << "Done" << endl; + return 0; +} + +FilterProtocol::FilterProtocol( const QCString & protocol, const QCString &pool, const QCString &app ) + : KIO::SlaveBase( protocol, pool, app ) +{ + QString mimetype = QString::fromLatin1("application/x-") + QString::fromLatin1(protocol); + filter = KFilterBase::findFilterByMimeType( mimetype ); + Q_ASSERT(filter); +} + +void FilterProtocol::get( const KURL & ) +{ + if (subURL.isEmpty()) + { + error( KIO::ERR_NO_SOURCE_PROTOCOL, mProtocol ); + return; + } + if (!filter) + { + error( KIO::ERR_INTERNAL, mProtocol ); + return; + } + needSubURLData(); + + filter->init(IO_ReadOnly); + + bool bNeedHeader = true; + bool bNeedMimetype = true; + bool bError = true; + int result; + + QByteArray inputBuffer; + QByteArray outputBuffer(8*1024); // Start with a modest buffer + filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); + while(true) + { + if (filter->inBufferEmpty()) + { + dataReq(); // Request data + result = readData( inputBuffer); + kdDebug(7110) << "requestData: got " << result << endl; + if (result <= 0) + { + bError = true; + break; // Unexpected EOF. + } + filter->setInBuffer( inputBuffer.data(), inputBuffer.size() ); + } + if (bNeedHeader) + { + bError = !filter->readHeader(); + if (bError) + break; + bNeedHeader = false; + } + result = filter->uncompress(); + if ((filter->outBufferAvailable() == 0) || (result == KFilterBase::END)) + { + kdDebug(7110) << "avail_out = " << filter->outBufferAvailable() << endl; + if (filter->outBufferAvailable() != 0) + { + // Discard unused space :-) + outputBuffer.resize(outputBuffer.size() - filter->outBufferAvailable()); + } + if (bNeedMimetype) + { + KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( outputBuffer, subURL.fileName() ); + kdDebug(7110) << "Emitting mimetype " << result->mimeType() << endl; + mimeType( result->mimeType() ); + bNeedMimetype = false; + } + data( outputBuffer ); // Send data + filter->setOutBuffer( outputBuffer.data(), outputBuffer.size() ); + if (result == KFilterBase::END) + break; // Finished. + } + if (result != KFilterBase::OK) + { + bError = true; + break; // Error + } + } + + if (!bError) + { + dataReq(); // Request data + result = readData( inputBuffer); + kdDebug(7110) << "requestData: got " << result << "(expecting 0)" << endl; + data( QByteArray() ); // Send EOF + } + + filter->terminate(); + + if (bError) + { + error(KIO::ERR_COULD_NOT_READ, subURL.url()); + subURL = KURL(); // Clear subURL + return; + } + + subURL = KURL(); // Clear subURL + finished(); +} + +void FilterProtocol::put( const KURL &/*url*/, int, bool /*_overwrite*/, bool /*_resume*/ ) +{ + error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("put")); +} + +void FilterProtocol::setSubURL(const KURL &url) +{ + subURL = url; +} + diff --git a/kioslave/filter/filter.h b/kioslave/filter/filter.h new file mode 100644 index 000000000..51b56061b --- /dev/null +++ b/kioslave/filter/filter.h @@ -0,0 +1,46 @@ +/* +This file is part of KDE + + Copyright (C) 2000 Waldo Bastian (bastian@kde.org) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef __filter_h__ +#define __filter_h__ + +#include <qobject.h> +#include <kio/global.h> +#include <kio/slavebase.h> + +class FilterProtocol : public QObject, public KIO::SlaveBase +{ +public: + FilterProtocol( const QCString & protocol, const QCString &pool, const QCString &app ); + + virtual void get( const KURL &url ); + virtual void put( const KURL &url, int _mode, bool _overwrite, + bool _resume ); + virtual void setSubURL(const KURL &url); + +private: + KURL subURL; + KFilterBase * filter; +}; + +#endif diff --git a/kioslave/filter/gzip.protocol b/kioslave/filter/gzip.protocol new file mode 100644 index 000000000..75908e381 --- /dev/null +++ b/kioslave/filter/gzip.protocol @@ -0,0 +1,10 @@ +[Protocol] +exec=kio_filter +protocol=gzip +mimetype=application/gzip +input=stream +output=stream +reading=true +source=false +DocPath=kioslave/gzip.html +Icon=ark diff --git a/kioslave/finger/Makefile.am b/kioslave/finger/Makefile.am new file mode 100644 index 000000000..6ddf78726 --- /dev/null +++ b/kioslave/finger/Makefile.am @@ -0,0 +1,27 @@ +## Makfile.am for kio_finger +## Edit from Makefile.am of kdebase/kioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +####### Files + +kde_module_LTLIBRARIES = kio_finger.la + +kio_finger_la_SOURCES = kio_finger.cpp +kio_finger_la_LIBADD = -lkio +kio_finger_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_finger.h + +kdelnk_DATA = finger.protocol +kdelnkdir = $(kde_servicesdir) + +kio_finger_data_DATA = kio_finger.pl kio_finger.css +kio_finger_datadir = $(kde_datadir)/kio_finger +EXTRA_DIST=$(kio_finger_data_DATA) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_finger.pot diff --git a/kioslave/finger/finger.protocol b/kioslave/finger/finger.protocol new file mode 100644 index 000000000..71d1b8e93 --- /dev/null +++ b/kioslave/finger/finger.protocol @@ -0,0 +1,9 @@ +[Protocol] +exec=kio_finger +protocol=finger +input=none +output=stream +reading=true +defaultMimetype=text/html +DocPath=kioslave/finger.html +Icon=kdmconfig diff --git a/kioslave/finger/kio_finger.cpp b/kioslave/finger/kio_finger.cpp new file mode 100644 index 000000000..c940998b9 --- /dev/null +++ b/kioslave/finger/kio_finger.cpp @@ -0,0 +1,266 @@ + +/*************************************************************************** + kio_finger.cpp - description + ------------------- + begin : Sun Aug 12 2000 + copyright : (C) 2000 by Andreas Schlapbach + email : schlpbch@iam.unibe.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <string.h> + +#include <qtextstream.h> +#include <qdict.h> +#include <qcstring.h> +#include <qfile.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kurl.h> + +#include "kio_finger.h" + + +using namespace KIO; + +static const QString defaultRefreshRate = "60"; + +extern "C" +{ + KDE_EXPORT int kdemain( int argc, char **argv ) + { + KInstance instance( "kio_finger" ); + + //kdDebug() << "*** Starting kio_finger " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_finger protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + FingerProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + //kdDebug() << "*** kio_finger Done" << endl; + return 0; + } +} + + +/* ---------------------------------------------------------------------------------- */ + + +FingerProtocol::FingerProtocol(const QCString &pool_socket, const QCString &app_socket) + : QObject(), SlaveBase("finger", pool_socket, app_socket) +{ + myStdStream = new QString(); + getProgramPath(); +} + + +/* ---------------------------------------------------------------------------------- */ + + +FingerProtocol::~FingerProtocol() +{ + //kdDebug() << "FingerProtocol::~FingerProtocol()" << endl; + delete myURL; + delete myPerlPath; + delete myFingerPath; + delete myFingerPerlScript; + delete myFingerCSSFile; + delete myStdStream; +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::get(const KURL& url ) +{ + //kdDebug() << "kio_finger::get(const KURL& url)" << endl ; + + this->parseCommandLine(url); + + //kdDebug() << "myURL: " << myURL->prettyURL() << endl; + + // Reset the stream + *myStdStream=""; + + QString query = myURL->query(); + QString refreshRate = defaultRefreshRate; + + //kdDebug() << "query: " << query << endl; + + // Check the validity of the query + + QRegExp regExp("?refreshRate=[0-9][0-9]*", true, true); + if (query.contains(regExp)) { + //kdDebug() << "looks like a valid query" << endl; + QRegExp regExp( "([0-9]+)" ); + regExp.search(query); + refreshRate = regExp.cap(0); + } + + //kdDebug() << "Refresh rate: " << refreshRate << endl; + + myKProcess = new KProcess(); + *myKProcess << *myPerlPath << *myFingerPerlScript + << *myFingerPath << *myFingerCSSFile + << refreshRate << myURL->host() << myURL->user() ; + + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + //connect(myKProcess, SIGNAL(receivedStderr(KProcess *, char *, int)), + // this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + data(QCString(myStdStream->local8Bit())); + + data(QByteArray()); + finished(); + + //clean up + + delete myKProcess; +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::slotGetStdOutput(KProcess* /* p */, char *s, int len) +{ + //kdDebug() << "void FingerProtocol::slotGetStdoutOutput()" << endl; + *myStdStream += QString::fromLocal8Bit(s, len); +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::mimetype(const KURL & /*url*/) +{ + mimeType("text/html"); + finished(); +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::getProgramPath() +{ + //kdDebug() << "kfingerMainWindow::getProgramPath()" << endl; + // Not to sure wether I'm using the right error number here. - schlpbch - + + myPerlPath = new QString(KGlobal::dirs()->findExe("perl")); + if (myPerlPath->isEmpty()) + { + //kdDebug() << "Perl command not found" << endl; + this->error(ERR_CANNOT_LAUNCH_PROCESS, + i18n("Could not find the Perl program on your system, please install.")); + exit(); + } + else + { + //kdDebug() << "Perl command found:" << *myPerlPath << endl; + } + + myFingerPath = new QString(KGlobal::dirs()->findExe("finger")); + if ((myFingerPath->isEmpty())) + { + //kdDebug() << "Finger command not found" << endl; + this->error(ERR_CANNOT_LAUNCH_PROCESS, + i18n("Could not find the Finger program on your system, please install.")); + exit(); + } + else + { + //kdDebug() << "Finger command found:" << *myFingerPath << endl; + } + + myFingerPerlScript = new QString(locate("data","kio_finger/kio_finger.pl")); + if (myFingerPerlScript->isEmpty()) + { + //kdDebug() << "kio_finger.pl script not found" << endl; + this->error(ERR_CANNOT_LAUNCH_PROCESS, + i18n("kio_finger Perl script not found.")); + exit(); + } + else + { + //kdDebug() << "kio_finger perl script found: " << *myFingerPerlScript << endl; + } + + myFingerCSSFile = new QString(locate("data","kio_finger/kio_finger.css")); + if (myFingerCSSFile->isEmpty()) + { + //kdDebug() << "kio_finger.css file not found" << endl; + this->warning(i18n("kio_finger CSS script not found. Output will look ugly.")); + } + else + { + //kdDebug() << "kio_finger CSS file found: " << *myFingerCSSFile << endl; + } +} + + +/* --------------------------------------------------------------------------- */ + + +void FingerProtocol::parseCommandLine(const KURL& url) +{ + myURL = new KURL(url); + + /* + * Generate a valid finger url + */ + + if(myURL->isEmpty() || !myURL->isValid() || + (myURL->user().isEmpty() && myURL->host().isEmpty())) + { + myURL->setProtocol("finger"); + myURL->setUser(""); + myURL->setHost("localhost"); + } + + /* + * If no specific port is specified, set it to 79. + */ + + if(myURL->port() == 0) { + myURL->setPort(79); + } + + /* + * If no refresh rate is given, set it to defaultRefreshRate + */ + + if (myURL->query().isEmpty()) { + myURL->setQuery("?refreshRate="+defaultRefreshRate); + } +} + +/* ---------------------------------------------------------------------------------- */ +#include "kio_finger.moc" +/* ---------------------------------------------------------------------------------- */ + diff --git a/kioslave/finger/kio_finger.css b/kioslave/finger/kio_finger.css new file mode 100644 index 000000000..06deb81aa --- /dev/null +++ b/kioslave/finger/kio_finger.css @@ -0,0 +1,69 @@ +BODY { + color: #FFFFCC; + background-color: #000000; + padding: 2em; + margin: auto; +} + +A:link {color: #82A7D0} +A:visited {color: #999999} +A:active {color: #999999} + +H1 { + color: #999999; + background-color: #000000; + font: 200% Helvetica, sans-serif; + font-variant: normal; + padding: 1em; + margin: auto; +} + +.mainTable { + background-color: #000000; + border: thin solid; + margin: auto; +} + + +.courierText { + color: #FFFFCC; + background-color: #000000; + font: 120% Courier, sans-serif; + font-variant: normal; + text-align: left; + padding: 0em; +} + +.commandText { + color: #FFFFCC; + background-color: #000000; + font: 120% Courier, sans-serif; + font-variant: normal; + text-align: center; + padding: 0.5em; +} + +.niceText { + color: #009999; + background-color: #000000; + font: 120% Arial, sans-serif; + font-variant: normal; + text-align: center; + padding: 0.5em; +} + +.finger { color: #82A7D0} +.domainName { color: #D0A000} +.ipNumber { color: #D0A000} +.os { color: #82A7D0} +.username { color: #82A7D0} +.directory { color: #D0A000} +.shell { color: #D0A000} +.notLoggedIn { color: #00A000} +.loggedIn { color: #B00000} +.newMail { color: #82A7D0} +.plan { color: #D0A000} +.noNewMail { color: #BB0000} +.noPlan { color: #BB0000} + + diff --git a/kioslave/finger/kio_finger.h b/kioslave/finger/kio_finger.h new file mode 100644 index 000000000..8d63236d4 --- /dev/null +++ b/kioslave/finger/kio_finger.h @@ -0,0 +1,64 @@ + +/*************************************************************************** + kio_finger.h - description + ------------------- + begin : Sun Aug 12 2000 + copyright : (C) 2000 by Andreas Schlapbach + email : schlpbch@iam.unibe.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + + +#ifndef __kio_finger_h__ +#define __kio_finger_h__ + +#include <qstring.h> +#include <qcstring.h> + +#include <kurl.h> +#include <kprocess.h> +#include <kio/global.h> +#include <kio/slavebase.h> + +class FingerProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT + +public: + + FingerProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~FingerProtocol(); + + virtual void mimetype(const KURL& url); + virtual void get(const KURL& url); + +private slots: + void slotGetStdOutput(KProcess*, char*, int); + +private: + KURL *myURL; + + QString *myPerlPath; + QString *myFingerPath; + QString *myFingerPerlScript; + QString *myFingerCSSFile; + + QString *myStdStream; + + + KProcess *myKProcess; + + void getProgramPath(); + void parseCommandLine(const KURL& url); +}; + + +#endif diff --git a/kioslave/finger/kio_finger.pl b/kioslave/finger/kio_finger.pl new file mode 100644 index 000000000..8965ea523 --- /dev/null +++ b/kioslave/finger/kio_finger.pl @@ -0,0 +1,175 @@ +##!/usr/bin/perl +# +# Copyright Andreas Schlapbach, schlpbch@iam.unibe.ch, 2001 +# http://iamexwiwww.unibe.ch/studenten/kfinger +# +# Touch at your own risk. + + +# Highlight mail addresses or url + +$mails = '<A HREF="mailto:'; +$urls = '<A HREF="'; +$urlspace = '">'; +$urlend = '</A>'; + +# Highlight various information, configurable via the CSS file, + +$finger = '<CODE class="finger">'; +$domainName = '<CODE class="domainName">'; +$ipNumber = '<CODE class="ipNumber">'; +$os = '<CODE class="os">'; +$username = '<CODE class="username">'; +$directory = '<CODE class="directory">'; +$shell = '<CODE class="shell">'; +$notLoggedIn = '<CODE class="Login">'; +$loggedIn = '<CODE class="noLogin">'; +$newMail = '<CODE class="newMail">'; +$plan = '<CODE class="plan">'; +$noNewMail = '<CODE class="noNewMail">'; +$noPlan = '<CODE class="noPlan">'; +$close = '</CODE>'; + +# Those names get skipped, so if there's a user with such a name, bad luck. + +@keywords=('Welcome','Login','finger','No'); +$keywordlist = join '|', @keywords; + +$FINGERCMD = "$ARGV[0]"; # The complete path to the finger cmd +$CSSFILE = "$ARGV[1]"; # The complete path to the CSS file +$REFRESHRATE = "$ARGV[2]"; # The intervals in seconds until the page gets updated +$HOST = "$ARGV[3]"; # host name +$USER = "$ARGV[4]"; # user name + +$HOST =~ s/&/&/g; +$HOST =~ s/</</g; +$HOST =~ s/>/>/g; + +$USER =~ s/&/&/g; +$USER =~ s/</</g; +$USER =~ s/>/>/g; + +# HTML Header + +print <<HTMLHeader; +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> + <meta http-equiv="refresh" content="$REFRESHRATE"> + <TITLE>finger $USER\@$HOST</TITLE> + <LINK type="text/css" rel="stylesheet" href="file:$CSSFILE"> +</HEAD> +<BODY> + <TABLE class="mainTable" cellspacing="0"> + <TR> + <TH colspan="1"> + <H1>finger $USER\@$HOST</H1> + </TH> + </TR> + <TR> + <TH> + <TABLE class="courierText" cellpadding="0" cellspacing="2"> +HTMLHeader + +# Run finger command and save it into a buffer + +open(F, "-|") || exec $FINGERCMD, "$USER\@$HOST"; +@lines = <F>; +close(F); + +# Do highlighting using perl regular expressions on every line received. +# Order is important here. + +foreach $output (@lines) + { + $output =~ s/((\w)+\@((\w)+(.))*(\w)+)/$mails$1$urlspace$1$urlend/gi; # Highlight email address + $output =~ s/((http|ftp)(:\/\/)(\S)+)/$urls$1$urlspace$1$urlend/gi; # Highlight urls + $output =~ s/((\d)+\.(\d)+\.(\d)+\.(\d)+)/$ipNumber$1$close/gi; # Highlight IP number + $output =~ s/((\w)+\.(\w)+\.(\w|-)+\s)/$domainName$1$close/gi; # Highlight domain name (\s is important) + $output =~ s/(finger:)/$finger$1$close/gim; # Highlight finger + $output =~ s/(Linux)/$os$1$close/gim; # Highlight Linux + if ($USER) # is $USER nil ? + { + $output =~ s/^Login:\s*(\w*)/Login: $mails$1\@$HOST$urlspace$1$urlend/gi; + $output =~ s/^Login Name:\s*(\w*)/Login Name:$mails$1\@$HOST$urlspace$1$urlend/gi; + $output =~ s/Name:(((\s*)(\w+))+\n)/Name:$username$1$close\n/gi; # Linux + $output =~ s/In real life:(((\s*)(\w+))+\n)/In real life:$username$1$close\n/gi; # Solaris + $output =~ s/^Directory:((\s*)(\/(\w)+)+)/Directory:$directory$1$close/gi; # Highlight Directory + $output =~ s/Shell:((\s*)(\/(\w)+)+)/Shell:$shell$1$close/gi; # Highlight Shell + $output =~ s/(not presently logged)/$notLoggedIn$1$close/gi; + $output =~ s/con (\w*)/con $loggedIn$1$close/gi; + $output =~ s/^(New mail)/$newMail$1$close/gi; + $output =~ s/^(No mail.)/$noNewMail$1$close/gim; + $output =~ s/^(Plan:)/$plan$1$close/gi; + $output =~ s/^(No plan.)/$noPlan$1$close/gim; + } + else + { + $output =~ s/^(\w+)/$mails$1\@$HOST$urlspace$1$urlend/m unless ($output =~ m/$keywordlist/m); + } + # line consists of white space only? + if ($output =~ m/\S/gi) + { + print " <TR><TD><PRE>$output</PRE></TD></TR>\n"; + } + else + { + print " <TR><TD><PRE> </PRE></TD></TR>\n"; + } +} + +print " </TABLE>\n"; +print " </TH>\n"; + +# Finger-Talk options + +if ($USER) # is $USER nil ? +{ +print <<UserQuery; + </TR> + <TR> + <TH class="commandText" colspan="2"> + <A HREF='finger://$USER\@$HOST'>finger</A> + </TH> + </TR> +UserQuery +} +else +{ +print <<HostQueryHead; + <TH> + <TABLE class="courierText" cellpadding="0" cellspacing="2"> +HostQueryHead + + @lines = split /^/m, $buffer; + foreach $output2 (@lines) + { + if ($output2 =~ m/^(\w+)/gi and not ($output2 =~ m/$keywordlist/m)) + { + $USER = $&; + print " <TR><TD><PRE><A HREF='finger://$USER\@$HOST'>finger</A>\n</PRE></TD></TR>\n"; + # - <A HREF='talk://$USER\@$HOST'>talk</A>\n</PRE></TD></TR>\n"; + } + else + { + print " <TR><TD><PRE> </PRE></TD></TR>\n"; + } + } + +print <<HostQueryTail; + </TABLE> + </TH> + </TR> +HostQueryTail +} + +# HTMLTail + +print <<HTMLTail; + <TR> + <TH class="niceText">refresh rate: $REFRESHRATE seconds.</TH> + </TR> +</TABLE> +</BODY> +</HTML> +HTMLTail diff --git a/kioslave/fish/AUTHORS b/kioslave/fish/AUTHORS new file mode 100644 index 000000000..fc4f1567e --- /dev/null +++ b/kioslave/fish/AUTHORS @@ -0,0 +1 @@ +Jörg Walter <trouble@garni.ch> diff --git a/kioslave/fish/COPYING b/kioslave/fish/COPYING new file mode 100644 index 000000000..2d08eab44 --- /dev/null +++ b/kioslave/fish/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) 19yy <name of author> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kioslave/fish/ChangeLog b/kioslave/fish/ChangeLog new file mode 100644 index 000000000..7f621f3d4 --- /dev/null +++ b/kioslave/fish/ChangeLog @@ -0,0 +1,71 @@ +1.2.3 by Willy De la Court <willy.delacourt@pandora.be> + Changes in the EXEC code as Jörg Walter proposed. + fixed 2 bugs when executing in Shell mode + +1.2.2 by Willy De la Court <willy.delacourt@pandora.be> + Security fix tempfile should not be world readable + bugfix write to the file not the command + +1.2.1 by Willy De la Court <willy.delacourt@pandora.be> + implemented su for fish://localhost/ + fish://root@localhost/ will su to root + fish://someuser@localhost/ will su to someuser + fish://localhost:22/ will still use ssh + strange problem with su when sending password need to wait a while (1 sec) + after reception of the password prompt. + some indentations fixed + i18n all messages + +1.2 by Willy De la Court <willy.delacourt@pandora.be> + implementation of the EXEC function + made sure all the VER lines where the same + used eval and system() for executing the command + Send the VER command directly after the FISH command. + After using kill to close the child process used wait to really make sure the child + has died. If the child took some time to die the select() + returns "Interrupted system call" error. This should solve some hanging problems. + added hasExec so it can be tested. + backport to BRANCH sendCommand(FISH_VER); and wait(NULL) to fix potential bugs. + +1.1.4 by Willy De la Court <willy.delacourt@pandora.be> + fixes Bug 49881: file time differs by 1 hour + and backported to BRANCH + +1.1.3 + removed compression option, which fixes 2 bugs: #45448 and an + untracked user report about ssh version misdetect; also, is + more consistent with overall design policy: leave connection + details to the ssh client + + fixed a bug which made lots of ssh zombie processes hang around + +1.1.2 + fixed a bug which made inserting shell args via fish:-URL possible + +1.1.1 + fixed shell mode symlink display + + made perl server compatible with 5.005 + +1.1 + added a perl server implementation which is transferred + and started automatically if perl is present + + added KDE3 support + + added support for commercial SSH + + modifed shell commands to use file(1)'s -i option (version + 3.37 and up) instead of local hack + + fixed an annoying bug with copying/moving dir trees + + fixed bug which made creating new files fail sometimes + + added support for changing the user name in the pass dialog + +1.0.1 + added #include <sys/resource.h> (needed on some platforms) + +1.0 + initial release diff --git a/kioslave/fish/FAQ b/kioslave/fish/FAQ new file mode 100644 index 000000000..dce0aef41 --- /dev/null +++ b/kioslave/fish/FAQ @@ -0,0 +1,37 @@ +Freqeuently Asked Questions, last updated for kio_fish 1.1 + +Q: Typing fish:/some.host.com does not work +A: It is fish://some.host.com (double slash) + +Q: How can I use a different port? +A: Use regular URL syntax: fish://some.host.com:2222 + +Q: Something isn't working. I get strange/no displays +A: Could be a bug, could be a problem with the remote + tools. Try having perl somewhere in the PATH on the + remote machine, that should work reliably. Shell- + only mode is prone to different tool's opinion about + parameters and what they mean. Shell-only mode is + thouroughly tested only on GNU tools, and has + superficial testing on BSD and Digital Unix. Solaris + seems to have problems. Any reports for shell mode on + non-GNU machines welcome (BTW, if you see a file + .fishsrv.pl in your remote home directory, fish did + use perl mode) + +Q: The connection stays open. How do I disconnect? +A: Just wait. The system default idle timeout is used. + (about a minute or so) + +Q: Why are there no icons? +A: With this release, you should have icons almost always, + but best results are obtained if you install a recent + version of the 'file' utility that supports the '-i' + option. + +Q: How do I specify which program to use for SSH? +A: Not at all, sorry. After evaluating other programs (rsh, + rlogin, telnet) I came to the conclusion it was way too + complex to support these, as only ssh supports both, + password authentication and automatic execution. + diff --git a/kioslave/fish/INSTALL b/kioslave/fish/INSTALL new file mode 100644 index 000000000..02a4a0740 --- /dev/null +++ b/kioslave/fish/INSTALL @@ -0,0 +1,167 @@ +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, a file +`config.cache' that saves the results of its tests to speed up +reconfiguring, and a file `config.log' containing compiler output +(useful mainly for debugging `configure'). + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If at some point `config.cache' +contains results you don't want to keep, you may remove or edit it. + + The file `configure.in' is used to create `configure' by a program +called `autoconf'. You only need `configure.in' if you want to change +it or regenerate `configure' using a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes a while. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Type `make install' to install the programs and any data files and + documentation. + + 4. You can remove the program binaries and object files from the + source code directory by typing `make clean'. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. You can give `configure' +initial values for variables by setting them in the environment. Using +a Bourne-compatible shell, you can do that on the command line like +this: + CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure + +Or on systems that have the `env' program, you can do it like this: + env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not supports the `VPATH' +variable, you have to compile the package for one architecture at a time +in the source code directory. After you have installed the package for +one architecture, use `make distclean' before reconfiguring for another +architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' can not figure out +automatically, but needs to determine by the type of host the package +will run on. Usually `configure' can figure that out, but if it prints +a message saying it can not guess the host type, give it the +`--host=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name with three fields: + CPU-COMPANY-SYSTEM + +See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the host type. + + If you are building compiler tools for cross-compiling, you can also +use the `--target=TYPE' option to select the type of system they will +produce code for and the `--build=TYPE' option to select the type of +system on which you are compiling the package. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Operation Controls +================== + + `configure' recognizes the following options to control how it +operates. + +`--cache-file=FILE' + Use and save the results of the tests in FILE instead of + `./config.cache'. Set FILE to `/dev/null' to disable caching, for + debugging `configure'. + +`--help' + Print a summary of the options to `configure', and exit. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--version' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`configure' also accepts some other, not widely useful, options. + diff --git a/kioslave/fish/Makefile.am b/kioslave/fish/Makefile.am new file mode 100644 index 000000000..27308245b --- /dev/null +++ b/kioslave/fish/Makefile.am @@ -0,0 +1,33 @@ +kde_module_LTLIBRARIES = kio_fish.la + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +kio_fish_la_SOURCES = fish.cpp +kio_fish_la_LIBADD = $(LIB_KSYCOCA) #$(LIBUTIL) +kio_fish_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = fishcode.h fish.h + +EXTRA_DIST = AUTHORS COPYING ChangeLog INSTALL README TODO FAQ fish.pl + +DISTCLEANFILES = fishcode.h + +kdelnk_DATA = fish.protocol nxfish.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO + +fish.lo: fishcode.h + +fishcode.h: fish.pl + SUM=`$(MD5SUM) $(srcdir)/fish.pl | cut -d ' ' $(MD5SUM_CUT)`; \ + echo '#define CHECKSUM "'$$SUM'"' > $@; \ + echo 'static const char *fishCode(' >> $@; \ + sed -e 's/\\/\\\\/g;s/"/\\"/g;s/^[ ]*/"/;/^"# /d;s/[ ]*$$/\\n"/;/^"\\n"$$/d;s/{CHECKSUM}/'$$SUM'/;' $(srcdir)/fish.pl >> $@; \ + echo ');' >> $@; + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_fish.pot + + + diff --git a/kioslave/fish/README b/kioslave/fish/README new file mode 100644 index 000000000..d1afdc3d1 --- /dev/null +++ b/kioslave/fish/README @@ -0,0 +1,258 @@ +Overview of kio_fish +==================== + + ------------------------------------------------------------------------ + NOTE FOR KDE2 USERS: This is the last release supporting KDE2. However, + you might need to modify Makefiles to get things installed into the + right directories. + ------------------------------------------------------------------------ + + FISH is a protocol to get filesystem access without special server + software, only using a remote shell. (Hence the name: FIles transferred + over SHell protocol). + It was first devised by Pavel Machek <pavel@bug.ucw.cz> and implemented + as a Midnight Commander vfs module in 1998. + + This is a complete client implementation using his original version + 0.0.2 protocol, extending it with 2 commands (which are only optional - + should a real FISH server exist on server side that doesn't understand + them, this ioslave still works, only slower). Moreover, this client does + complete shell metacharacter quoting on all arguments, a fact that is + neccessary but missing from the specs. + Extensions used are: append (APPEND command), copy (COPY command), + lscount (LIST first prints number of files to be listed), lslinks (LIST + shows symlink info instead of info about link targets), lsmime (LIST + determines the MIME type on the server side) + Password and host key queries are handled via dialog boxes. + The goal of this client is to make a remote directory look and feel exactly + like a local directory, with all comfort, only slower. + + NOTE: From version 1.1.3 on, compression is no longer turned on auto- + matically. You have to specify it via ~/.ssh/config or wherever + your local ssh client reads its settings. The same goes for all other + connection parameters. OpenSSH for example has a powerful configuration + file syntax which lets you configure access differently for each host, + something I do not intend to duplicate. Read the ssh_config(5) man page + for details. If someone knows the docs to read for commercial ssh please + tell me so I can include that here as well. + + Included below is the original posting from the mc mailing list archives. + + If perl is installed on the remote machine and in the default PATH, it will + be used to transfer a custom server script which is much faster than + shell-only mode and more predictable as well. The script is stored in a + file called .fishsrv.pl in the working directory directly after login and + will be reused on subsequent connections. + + 2001/10/07 Jörg Walter <trouble@garni.ch> + + + +From: Pavel Machek <pavel@bug.ucw.cz> +Subject: New virtual filesystem - fish +Date: Tue, 15 Sep 1998 22:30:07 +0200 + +Hi! + +New virtual filesystem has been created, which allows you to access +files on remote computer over rsh/ssh connection, with _no_ server +needed on the other side. To use it from mc or any program using +libvfs.so, do + +cd /#sh:user@host.to.connect.to/ + +Note that password authentication will not work, so you must be +authenticated using [rs]hosts or RSA key. + +For protocol, see mc/vfs/README.fish. If someone wants to write +server, it would be good idea, since it works without server but +performance is not optimal. + + Pavel + +PS: Protocol looks like this. If you have any comments, it is time to +speak. + + + FIles transferred over SHell protocol (V 0.0.2) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This protocol was designed for transferring files over secureshell +(ssh) connection. It can be as well used for transfers over rsh, and +there may be other uses. + +Client sends requests of following form: + +#FISH_COMMAND +equivalent shell commands, +which may be multiline + +Only fish commands are defined here, shell equivalents are for your +information only and will probably vary from implementation to +implementation. Fish commands always have priority: server is +expected to execute fish command if it understands it. If it does not, +however, it can try the luck and execute shell command. + +Server's reply is multiline, but alwyas ends with + +### 000<optional text> + +line. ### is prefix to mark this line, 000 is return code. Return +codes are superset to those used in ftp. + +There are few new exit codes defined: + +000 don't know; if there were no previous lines, this marks COMPLETE +success, if they were, it marks failure. + +001 don't know; if there were no previous lines, this marks +PRELIMinary success, if they were, it marks failure + + Connecting + ~~~~~~~~~~ +Client uses "echo FISH:;/bin/sh" as command executed on remote +machine. This should make it possible for server to distinguish FISH +connections from normal rsh/ssh. + + Commands + ~~~~~~~~ +#FISH +echo; start_fish_server; echo '### 200' + +This command is sent at the begining. It marks that client wishes to +talk via FISH protocol. #VER command must follow. If server +understands FISH protocol, it has option to put FISH server somewhere +on system path and name it start_fish_server. + +#VER 0.0.2 <feature1> <feature2> <...> +echo '### 000' + +This command is the second one. It sends client version and extensions +to the server. Server should reply with protocol version to be used, +and list of extensions accepted. + +VER 0.0.0 <feature2> +### 200 + +#PWD +pwd; echo '### 200' + +Server should reply with current directory (in form /abc/def/ghi) +followed by line indicating success. + +#LIST /directory +ls -lLa $1 | grep '^[^cbt]' | ( while read p x u g s m d y n; do echo "P$p $u.$g +S$s +d$m $d $y +:$n +"; done ) +ls -lLa $1 | grep '^[cb]' | ( while read p x u g a i m d y n; do echo "P$p $u.$g +E$a$i +dD$m $d $y +:$n +"; done ) +echo '### 200' + +This allows client to list directory or get status information about +single file. Output is in following form (any line except :<filename> +may be ommited): + +P<unix permissions> <owner>.<group> +S<size> +d<3-letters month name> <day> <year or HH:MM> +D<year> <month> <day> <hour> <minute> <second>[.1234] +E<major-of-device>,<minor> +:<filename> +L<filename symlink points to> +<blank line to separate items> + +Unix permissions are of form X--------- where X is type of +file. Currently, '-' means regular file, 'd' means directory, 'c', 'b' +means character and block device, 'l' means symbolic link, 'p' means +FIFO and 's' means socket. + +'d' has three fields: month (one of strings Jan Feb Mar Apr May Jun +Jul Aug Sep Oct Nov Dec), day of month, and third is either single +number indicating year, or HH:MM field (assume current year in such +case). As you've probably noticed, this is pretty broken; it is for +compatibility with ls listing. + +#RETR /some/name +ls -l /some/name | ( read a b c d x e; echo $x ); echo '### 100'; cat /some/name; echo '### 200' + +Server sends line with filesize on it, followed by line with ### 100 +indicating partial success, then it sends binary data (exactly +filesize bytes) and follows them with (with no preceeding newline) ### +200. + +Note that there's no way to abort running RETR command - except +closing the connection. + +#STOR <size> /file/name +<i><font color="#008000">> /file/name; echo '### 001'; ( dd bs=4096 count=<size/4096>; dd bs=<size%4096> count=1 ) 2>/dev/null | ( cat > %s; cat > /dev/null ); echo '### 200' +</font></i> +This command is for storing /file/name, which is exactly size bytes +big. You probably think I went crazy. Well, I did not: that strange +cat > /dev/null has purpose to discard any extra data which was not +written to disk (due to for example out of space condition). + +[Why? Imagine uploading file with "rm -rf /" line in it.] + +#CWD /somewhere +cd /somewhere; echo '### 000' + +It is specified here, but I'm not sure how wise idea is to use this +one: it breaks stateless-ness of the protocol. + +Following commands should be rather self-explanatory: + +#CHMOD 1234 file +chmod 1234 file; echo '### 000' + +#DELE /some/path +rm -f /some/path; echo '### 000' + +#MKD /some/path +mkdir /some/path; echo '### 000' + +#RMD /some/path +rmdir /some/path; echo '### 000' + +#RENAME /path/a /path/b +mv /path/a /path/b; echo '### 000' + +#LINK /path/a /path/b +ln /path/a /path/b; echo '### 000' + +#SYMLINK /path/a /path/b +ln -s /path/a /path/b; echo '### 000' + +#CHOWN user /file/name +chown user /file/name; echo '### 000' + +#CHGRP group /file/name +chgrp group /file/name; echo '### 000' + +#READ <offset> <size> /path/and/filename +cat /path/and/filename | ( dd bs=4096 count=<offset/4096> > /dev/null; +dd bs=<offset%4096> count=1 > /dev/null; +dd bs=4096 count=<offset/4096>; +dd bs=<offset%4096> count=1; ) + +Returns ### 200 on successfull exit, ### 291 on successfull exit when +reading ended at eof, ### 292 on successfull exit when reading did not +end at eof. + +#WRITE <offset> <size> /path/and/filename + +Hmm, shall we define these ones if we know our client is not going to +use them? + + +That's all, folks! + pavel@ucw.cz + + +-- +I'm really pavel@atrey.karlin.mff.cuni.cz. Pavel +Look at http://atrey.karlin.mff.cuni.cz/~pavel/ ;-). diff --git a/kioslave/fish/TODO b/kioslave/fish/TODO new file mode 100644 index 000000000..ba3bf69bb --- /dev/null +++ b/kioslave/fish/TODO @@ -0,0 +1,10 @@ +L resume (could be very slow in shell mode due to WRITE being slow) +L other remote shells (rlogin, telnet) - difficult, would need a shell prompt detector which is impossible to get 100% accurate. Contributions welcome. +L show host list when called as fish:// +L plug into sidebar, show directory tree there +M employ locking to only show one password dialog, so that loading many files at once from the same host would use a cahced password instead of opening tons of dialog boxes +M more meaningful error messages (need perl server first) +H use rsync or a similar technique (if possible at all) +M proxying via intermediate ssh account +H make it work with charsets other than latin1 + diff --git a/kioslave/fish/configure.in.in b/kioslave/fish/configure.in.in new file mode 100644 index 000000000..086dc0dae --- /dev/null +++ b/kioslave/fish/configure.in.in @@ -0,0 +1,9 @@ +CFLAGS="$CFLAGS -D_GNU_SOURCE" +CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE" + +AC_CHECK_HEADERS(termios.h pty.h libutil.h util.h sys/types.h sys/ioctl.h stropts.h) + +kde_save_LIBS="$LIBS" +LIBS="$LIBS $LIBUTIL" +AC_CHECK_FUNCS(getpt openpty isastream) +LIBS="$kde_save_LIBS" diff --git a/kioslave/fish/fish.cpp b/kioslave/fish/fish.cpp new file mode 100644 index 000000000..3967bcd6b --- /dev/null +++ b/kioslave/fish/fish.cpp @@ -0,0 +1,1661 @@ +/*************************************************************************** + fish.cpp - a FISH kioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001-2003 by J�rg Walter + email : jwalt-kde@garni.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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, version 2 of the License * + * * + ***************************************************************************/ + +/* + This code contains fragments and ideas from the ftp kioslave + done by David Faure <faure@kde.org>. + + Structure is a bit complicated, since I made the mistake to use + KProcess... now there is a lightweight homebrew async IO system + inside, but if signals/slots become available for ioslaves, switching + back to KProcess should be easy. +*/ + +#include "config.h" + +#include <qcstring.h> +#include <qfile.h> +#include <qsocket.h> +#include <qdatetime.h> +#include <qbitarray.h> +#include <qregexp.h> + +#include <stdlib.h> +#ifdef HAVE_PTY_H +#include <pty.h> +#endif +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif +#include <math.h> +#include <unistd.h> +#include <signal.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/types.h> +#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 <kdebug.h> +#include <kmessagebox.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kremoteencoding.h> +#include <kurl.h> +#include <ksock.h> +#include <stdarg.h> +#include <time.h> +#include <sys/stat.h> +#include <kmimetype.h> +#include <kmimemagic.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <errno.h> +#include <sys/resource.h> + +#include "fish.h" +#include "fishcode.h" + +#ifndef NDEBUG +#define myDebug(x) kdDebug(7127) << __LINE__ << ": " x +#define connected() do{myDebug( << "_______ emitting connected()" << endl); connected();}while(0) +#define dataReq() do{myDebug( << "_______ emitting dataReq()" << endl); dataReq();}while(0) +#define needSubURLData() do{myDebug( << "_______ emitting needSubURLData()" << endl); needSubURLData();}while(0) +#define slaveStatus(x,y) do{myDebug( << "_______ emitting slaveStatus(" << x << ", " << y << ")" << endl); slaveStatus(x,y);}while(0) +#define statEntry(x) do{myDebug( << "_______ emitting statEntry("<<x.size()<<")" << endl); statEntry(x);}while(0) +#define listEntries(x) do{myDebug( << "_______ emitting listEntries(...)" << endl); listEntries(x);}while(0) +#define canResume(x) do{myDebug( << "_______ emitting canResume("<<(int)x<<")" << endl); canResume(x);}while(0) +#define totalSize(x) do{myDebug( << "_______ emitting totalSize("<<(int)x<<")" << endl); totalSize(x);}while(0) +#define processedSize(x) do{myDebug( << "_______ emitting processedSize("<<x<<")" << endl); processedSize(x);}while(0) +#define speed(x) do{myDebug( << "_______ emitting speed("<<(int)x<<")" << endl); speed(x);}while(0) +#define redirection(x) do{myDebug( << "_______ emitting redirection("<<x<<")" << endl); redirection(x);}while(0) +#define errorPage() do{myDebug( << "_______ emitting errorPage()" << endl); errorPage();}while(0) +#define sendmimeType(x) do{myDebug( << "_______ emitting mimeType("<<x<<")" << endl); mimeType(x);}while(0) +#define warning(x) do{myDebug( << "_______ emitting warning("<<x<<")" << endl); warning(x);}while(0) +#define infoMessage(x) do{myDebug( << "_______ emitting infoMessage("<<x<<")" << endl); infoMessage(x);}while(0) +#else +#define myDebug(x) +#define sendmimeType(x) mimeType(x) +#endif + +static char *sshPath = NULL; +static char *suPath = NULL; +// disabled: currently not needed. Didn't work reliably. +// static int isOpenSSH = 0; + +static int isNXFish = 0; + +#define E(x) ((const char*)remoteEncoding()->encode(x).data()) + +using namespace KIO; +extern "C" { + +static void ripper(int) +{ + while (waitpid(-1,0,WNOHANG) > 0) { + // do nothing, go on + } +} + +int KDE_EXPORT kdemain( int argc, char **argv ) +{ + KLocale::setMainCatalogue("kio_fish"); + KInstance instance("fish"); + + myDebug( << "*** Starting fish " << endl); + if (argc != 4) { + myDebug( << "Usage: fish protocol domain-socket1 domain-socket2" << endl); + exit(-1); + } + + setenv("TZ", "UTC", true); + + struct sigaction act; + memset(&act,0,sizeof(act)); + act.sa_handler = ripper; + act.sa_flags = 0 +#ifdef SA_NOCLDSTOP + | SA_NOCLDSTOP +#endif +#ifdef SA_RESTART + | SA_RESTART +#endif + ; + sigaction(SIGCHLD,&act,NULL); + + if (qstrcmp(argv[1],"nxfish")==0) { + // Set NXFish - Mode + isNXFish=1; + } + + fishProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + myDebug( << "*** fish Done" << endl); + return 0; +} + +} + +const struct fishProtocol::fish_info fishProtocol::fishInfo[] = { + { ("FISH"), 0, + ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM " 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while(<STDIN>) { last if /^__END__/; $code.=$_; } exit(eval($code));' 2>/dev/null;"), + 1 }, + { ("VER 0.0.3 copy append lscount lslinks lsmime exec stat"), 0, + ("echo 'VER 0.0.3 copy append lscount lslinks lsmime exec stat'"), + 1 }, + { ("PWD"), 0, + ("pwd"), + 1 }, + { ("LIST"), 1, + ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -Lla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" + "ls -Lla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"), + 0 }, + { ("STAT"), 1, + ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -dLla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );" + "ls -dLla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"), + 0 }, + { ("RETR"), 1, + ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"), + 1 }, + { ("STOR"), 2, + ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;" + "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"), + 0 }, + { ("CWD"), 1, + ("cd %1"), + 0 }, + { ("CHMOD"), 2, + ("chmod %1 %2"), + 0 }, + { ("DELE"), 1, + ("rm -f %1"), + 0 }, + { ("MKD"), 1, + ("mkdir %1"), + 0 }, + { ("RMD"), 1, + ("rmdir %1"), + 0 }, + { ("RENAME"), 2, + ("mv -f %1 %2"), + 0 }, + { ("LINK"), 2, + ("ln -f %1 %2"), + 0 }, + { ("SYMLINK"), 2, + ("ln -sf %1 %2"), + 0 }, + { ("CHOWN"), 2, + ("chown %1 %2"), + 0 }, + { ("CHGRP"), 2, + ("chgrp %1 %2"), + 0 }, + { ("READ"), 3, + ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;" + "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;" + "dd bs=%2 count=1; ) 2>/dev/null;"), + 0 }, + // Yes, this is "ibs=1", since dd "count" is input blocks. + // On network connections, read() may not fill the buffer + // completely (no more data immediately available), but dd + // does ignore that fact by design. Sorry, writes are slow. + // OTOH, WRITE is not used by the current ioslave methods, + // we use APPEND. + { ("WRITE"), 3, + (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | " + "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"), + 0 }, + { ("COPY"), 2, + ("if [ -L %1 ]; then if cp -pdf %1 %2 2>/dev/null; then :; else LINK=\"`readlink %1`\"; ln -sf $LINK %2; fi; else cp -pf %1 %2; fi"), + 0 }, + { ("APPEND"), 2, + (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"), + 0 }, + { ("EXEC"), 2, + ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"), + 0 } +}; + +fishProtocol::fishProtocol(const QCString &pool_socket, const QCString &app_socket) + : SlaveBase("fish", pool_socket, app_socket), mimeBuffer(1024), + mimeTypeSent(false) +{ + myDebug( << "fishProtocol::fishProtocol()" << endl); + if (sshPath == NULL) { + // disabled: currently not needed. Didn't work reliably. + // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null"); + if (isNXFish) + sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("nxfish"))); + else + sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("ssh"))); + } + if (suPath == NULL) { + suPath = strdup(QFile::encodeName(KStandardDirs::findExe("su"))); + } + childPid = 0; + connectionPort = 0; + isLoggedIn = false; + writeReady = true; + isRunning = false; + firstLogin = true; + errorCount = 0; + rawRead = 0; + rawWrite = -1; + recvLen = -1; + sendLen = -1; + setMultipleAuthCaching( true ); + connectionAuth.keepPassword = true; + connectionAuth.url.setProtocol("fish"); + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + typeAtom.m_uds = UDS_FILE_TYPE; + typeAtom.m_long = 0; + mimeAtom.m_uds = UDS_MIME_TYPE; + mimeAtom.m_long = 0; + mimeAtom.m_str = QString::null; + + hasAppend = false; + + 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 + fishCodeLen = strlen(fishCode); +} +/* ---------------------------------------------------------------------------------- */ + + +fishProtocol::~fishProtocol() +{ + myDebug( << "fishProtocol::~fishProtocol()" << endl); + shutdownConnection(true); +} + +/* --------------------------------------------------------------------------- */ + +/** +Connects to a server and logs us in via SSH. Then starts FISH protocol. +*/ +void fishProtocol::openConnection() { + if (childPid) return; + + if (connectionHost.isEmpty() && !isNXFish) + { + error( KIO::ERR_UNKNOWN_HOST, QString::null ); + return; + } + + infoMessage(i18n("Connecting...")); + + myDebug( << "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort << endl); + sendCommand(FISH_FISH); + sendCommand(FISH_VER); + if (connectionStart()) { + error(ERR_COULD_NOT_CONNECT,connectionHost); + shutdownConnection(); + return; + }; + myDebug( << "subprocess is running" << endl); +} + +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 fishProtocol::connectionStart() { + 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; + } + + if (!requestNetwork()) return true; + myDebug( << "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser << endl); + childPid = fork(); + if (childPid == -1) { + myDebug( << "fork failed, error: " << strerror(errno) << endl); + close(fd[0]); + close(fd[1]); + childPid = 0; + dropNetwork(); + return true; + } + if (childPid == 0) { + // 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); + + if (local) { + execl(suPath, "su", "-", connectionUser.latin1(), "-c", "cd ~;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0); + } else { + #define common_args "-l", connectionUser.latin1(), "-x", "-e", "none", \ + "-q", connectionHost.latin1(), \ + "echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0 + // disabled: leave compression up to the client. + // (isOpenSSH?"-C":"+C"), + + if (connectionPort) + execl(sshPath, "ssh", "-p", QString::number(connectionPort).latin1(), common_args); + else + execl(sshPath, "ssh", common_args); + #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); + 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; + myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl); + return true; + } + } + } + return false; +} + +/** +writes one chunk of data to stdin of child process +*/ +void fishProtocol::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 fishProtocol::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("?"))) { + pos++; + QString str = buf.left(pos); + buf = buf.mid(pos); + if (str == "\n") + continue; + if (str == "FISH:\n") { + thisFn = QString::null; + infoMessage(i18n("Initiating protocol...")); + if (!connectionAuth.password.isEmpty()) { + connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length()-1); + cacheAuthentication(connectionAuth); + } + isLoggedIn = true; + return 0; + } else if (!str.isEmpty()) { + thisFn += str; + } else if (buf.endsWith(":")) { + if (!redirectUser.isEmpty() && connectionUser != redirectUser) { + KURL dest = url; + dest.setUser(redirectUser); + dest.setPass(redirectPass); + redirection(dest); + commandList.clear(); + commandCodes.clear(); + finished(); + redirectUser = ""; + redirectPass = ""; + return -1; + } else if (!connectionPassword.isEmpty()) { + myDebug( << "sending cpass" << endl); + connectionAuth.password = connectionPassword+"\n"; + connectionPassword = QString::null; + // su does not like receiving a password directly after sending + // the password prompt so we wait a while. + if (local) + sleep(1); + writeChild(connectionAuth.password.latin1(),connectionAuth.password.length()); + } else { + myDebug( << "sending mpass" << endl); + connectionAuth.prompt = thisFn+buf; + if (local) + connectionAuth.caption = i18n("Local Login") + " - " + url.user() + "@" + url.host(); + else + connectionAuth.caption = i18n("SSH Authorization") + " - " + url.user() + "@" + url.host(); + if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) { + connectionAuth.password = QString::null; // don't prefill + if ( !openPassDlg(connectionAuth)) { + error(ERR_USER_CANCELED,connectionHost); + shutdownConnection(); + return -1; + } + } + firstLogin = false; + connectionAuth.password += "\n"; + if (connectionAuth.username != connectionUser) { + KURL dest = url; + dest.setUser(connectionAuth.username); + dest.setPass(connectionAuth.password); + redirection(dest); + if (isStat) { // FIXME: just a workaround for konq deficiencies + redirectUser = connectionAuth.username; + redirectPass = connectionAuth.password; + } + commandList.clear(); + commandCodes.clear(); + finished(); + return -1; + } + myDebug( << "sending pass" << endl); + if (local) + sleep(1); + writeChild(connectionAuth.password.latin1(),connectionAuth.password.length()); + } + thisFn = QString::null; + return 0; + } else if (buf.endsWith("?")) { + int rc = messageBox(QuestionYesNo,thisFn+buf); + if (rc == KMessageBox::Yes) { + writeChild("yes\n",4); + } else { + writeChild("no\n",3); + } + thisFn = QString::null; + return 0; + } else { + myDebug( << "unmatched case in initial handling! shouldn't happen!" << endl); + } + } + return buf.length(); +} +/** +sets connection information for subsequent commands +*/ +void fishProtocol::setHost(const QString & host, int port, const QString & u, const QString & pass){ + QString user(u); + + if (isNXFish) + local = 0; + else + local = (host == "localhost" && port == 0); + + if (port <= 0) port = 0; + if (user.isEmpty()) user = getenv("LOGNAME"); + + if (host == connectionHost && port == connectionPort && user == connectionUser) + return; + myDebug( << "setHost " << u << "@" << host << endl); + + if (childPid) shutdownConnection(); + + connectionHost = host; + connectionAuth.url.setHost(host); + + connectionUser = user; + connectionAuth.username = user; + connectionAuth.url.setUser(user); + + connectionPort = port; + connectionPassword = pass; + firstLogin = true; +} + +/** +Forced close of the connection + +This function gets called from the application side of the universe, +it shouldn't send any response. + */ +void fishProtocol::closeConnection(){ + myDebug( << "closeConnection()" << endl); + shutdownConnection(true); +} + +/** +Closes the connection + */ +void fishProtocol::shutdownConnection(bool forced){ + if (childPid) { + kill(childPid,SIGTERM); // We may not have permission... + childPid = 0; + close(childFd); // ...in which case this should do the trick + childFd = -1; + if (!forced) + { + dropNetwork(); + infoMessage(i18n("Disconnected.")); + } + } + outBufPos = -1; + outBuf = NULL; + outBufLen = 0; + qlist.clear(); + commandList.clear(); + commandCodes.clear(); + isLoggedIn = false; + writeReady = true; + isRunning = false; + rawRead = 0; + rawWrite = -1; + recvLen = -1; + sendLen = -1; +} +/** +builds each FISH request and sets the error counter +*/ +bool fishProtocol::sendCommand(fish_command_type cmd, ...) { + const fish_info &info = fishInfo[cmd]; + myDebug( << "queueing: cmd="<< cmd << "['" << info.command << "'](" << info.params <<"), alt=['" << info.alt << "'], lines=" << info.lines << endl); + + va_list list; + va_start(list, cmd); + QString realCmd = info.command; + QString realAlt = info.alt; + static QRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]"); + for (int i = 0; i < info.params; i++) { + QString arg(va_arg(list, const char *)); + int pos = -2; + while ((pos = rx.search(arg,pos+2)) >= 0) { + arg.replace(pos,0,QString("\\")); + } + //myDebug( << "arg " << i << ": " << arg << endl); + realCmd.append(" ").append(arg); + realAlt.replace(QRegExp("%"+QString::number(i+1)),arg); + } + QString s("#"); + s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 000'\n"); + if (realCmd == "FISH") + s.prepend(" "); + commandList.append(s); + commandCodes.append(cmd); + return true; +} + +/** +checks response string for result code, converting 000 and 001 appropriately +*/ +int fishProtocol::handleResponse(const QString &str){ + myDebug( << "handling: " << str << endl); + if (str.startsWith("### ")) { + bool isOk = false; + int result = str.mid(4,3).toInt(&isOk); + if (!isOk) result = 500; + if (result == 0) result = (errorCount != 0?500:200); + if (result == 1) result = (errorCount != 0?500:100); + myDebug( << "result: " << result << ", errorCount: " << errorCount << endl); + return result; + } else { + errorCount++; + return 0; + } +} + +int fishProtocol::makeTimeFromLs(const QString &monthStr, const QString &dayStr, const QString &timeyearStr) +{ + QDateTime dt(QDate::currentDate(Qt::UTC)); + int year = dt.date().year(); + int month = dt.date().month(); + int currentMonth = month; + int day = dayStr.toInt(); + + static const char * const monthNames[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + for (int i=0; i < 12; i++) if (monthStr.startsWith(monthNames[i])) { + month = i+1; + break; + } + + int pos = timeyearStr.find(':'); + if (timeyearStr.length() == 4 && pos == -1) { + year = timeyearStr.toInt(); + } else if (pos == -1) { + return 0; + } else { + if (month > currentMonth + 1) year--; + dt.time().setHMS(timeyearStr.left(pos).toInt(),timeyearStr.mid(pos+1).toInt(),0); + } + dt.date().setYMD(year,month,day); + + return dt.toTime_t(); +} + +/** +parses response from server and acts accordingly +*/ +void fishProtocol::manageConnection(const QString &l) { + QString line(l); + int rc = handleResponse(line); + UDSAtom atom; + QDateTime dt; + KIO::filesize_t fsize; + int pos, pos2, pos3; + bool isOk = false; + if (!rc) { + switch (fishCommand) { + case FISH_VER: + if (line.startsWith("VER 0.0.3")) { + line.append(" "); + hasAppend = line.contains(" append "); + } else { + error(ERR_UNSUPPORTED_PROTOCOL,line); + shutdownConnection(); + } + break; + case FISH_PWD: + url.setPath(line); + redirection(url); + break; + case FISH_LIST: + myDebug( << "listReason: " << listReason << endl); + /* Fall through */ + case FISH_STAT: + if (line.length() > 0) { + switch (line[0].cell()) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + fsize = line.toULongLong(&isOk); + if (fsize > 0 && isOk) errorCount--; + if ((fishCommand == FISH_LIST) && (listReason == LIST)) + totalSize(fsize); + break; + + case 'P': + errorCount--; + if (line[1] == 'd') { + mimeAtom.m_str = "inode/directory"; + typeAtom.m_long = S_IFDIR; + } else { + if (line[1] == '-') { + typeAtom.m_long = S_IFREG; + } else if (line[1] == 'l') { + typeAtom.m_long = S_IFLNK; + } else if (line[1] == 'c') { + typeAtom.m_long = S_IFCHR; + } else if (line[1] == 'b') { + typeAtom.m_long = S_IFBLK; + } else if (line[1] == 's') { + typeAtom.m_long = S_IFSOCK; + } else if (line[1] == 'p') { + typeAtom.m_long = S_IFIFO; + } else { + myDebug( << "unknown file type: " << line[1].cell() << endl); + errorCount++; + break; + } + } + //myDebug( << "file type: " << atom.m_long << endl); + //udsEntry.append(atom); + + atom.m_uds = UDS_ACCESS; + atom.m_long = 0; + if (line[2] == 'r') atom.m_long |= S_IRUSR; + if (line[3] == 'w') atom.m_long |= S_IWUSR; + if (line[4] == 'x' || line[4] == 's') atom.m_long |= S_IXUSR; + if (line[4] == 'S' || line[4] == 's') atom.m_long |= S_ISUID; + if (line[5] == 'r') atom.m_long |= S_IRGRP; + if (line[6] == 'w') atom.m_long |= S_IWGRP; + if (line[7] == 'x' || line[7] == 's') atom.m_long |= S_IXGRP; + if (line[7] == 'S' || line[7] == 's') atom.m_long |= S_ISGID; + if (line[8] == 'r') atom.m_long |= S_IROTH; + if (line[9] == 'w') atom.m_long |= S_IWOTH; + if (line[10] == 'x' || line[10] == 't') atom.m_long |= S_IXOTH; + if (line[10] == 'T' || line[10] == 't') atom.m_long |= S_ISVTX; + udsEntry.append(atom); + + atom.m_uds = UDS_USER; + atom.m_long = 0; + pos = line.find('.',12); + if (pos < 0) { + errorCount++; + break; + } + atom.m_str = line.mid(12,pos-12); + udsEntry.append(atom); + + atom.m_uds = UDS_GROUP; + atom.m_long = 0; + atom.m_str = line.mid(pos+1); + udsEntry.append(atom); + break; + + case 'd': + atom.m_uds = UDS_MODIFICATION_TIME; + pos = line.find(' '); + pos2 = line.find(' ',pos+1); + if (pos < 0 || pos2 < 0) break; + errorCount--; + atom.m_long = makeTimeFromLs(line.mid(1,pos-1), line.mid(pos+1,pos2-pos), line.mid(pos2+1)); + udsEntry.append(atom); + break; + + case 'D': + atom.m_uds = UDS_MODIFICATION_TIME; + pos = line.find(' '); + pos2 = line.find(' ',pos+1); + pos3 = line.find(' ',pos2+1); + if (pos < 0 || pos2 < 0 || pos3 < 0) break; + dt.setDate(QDate(line.mid(1,pos-1).toInt(),line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt())); + pos = pos3; + pos2 = line.find(' ',pos+1); + pos3 = line.find(' ',pos2+1); + if (pos < 0 || pos2 < 0 || pos3 < 0) break; + dt.setTime(QTime(line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt(),line.mid(pos3+1).toInt())); + errorCount--; + atom.m_long = dt.toTime_t(); + udsEntry.append(atom); + break; + + case 'S': + atom.m_uds = UDS_SIZE; + atom.m_long = line.mid(1).toULongLong(&isOk); + if (!isOk) break; + errorCount--; + udsEntry.append(atom); + break; + + case 'E': + errorCount--; + break; + + case ':': + atom.m_uds = UDS_NAME; + atom.m_long = 0; + pos = line.findRev('/'); + atom.m_str = thisFn = line.mid(pos < 0?1:pos+1); + if (fishCommand == FISH_LIST) + udsEntry.append(atom); + // By default, the mimetype comes from the extension + // We'll use the file(1) result only as fallback [like the rest of KDE does] + { + KURL kurl("fish://host/"); + kurl.setFileName(thisFn); // properly encode special chars + KMimeType::Ptr mime = KMimeType::findByURL(kurl); + if ( mime->name() != KMimeType::defaultMimeType() ) + mimeAtom.m_str = mime->name(); + } + errorCount--; + break; + + case 'M': { + QString type = line.mid(1); + + // First thing's first. If remote says this is a directory, throw out any + // name-based file type guesses. + if (type == "inode/directory" && mimeAtom.m_str != type) { + mimeAtom.m_str = type; + typeAtom.m_long = S_IFDIR; + } + // This is getting ugly. file(1) makes some uneducated + // guesses, so we must try to ignore them (#51274) + else if (mimeAtom.m_str.isEmpty() && line.right(8) != "/unknown" && + (thisFn.find('.') < 0 || (line.left(8) != "Mtext/x-" + && line != "Mtext/plain"))) { + mimeAtom.m_str = type; + } + errorCount--; + break; + } + + case 'L': + atom.m_uds = UDS_LINK_DEST; + atom.m_long = 0; + atom.m_str = line.mid(1); + udsEntry.append(atom); + if (!typeAtom.m_long) typeAtom.m_long = S_IFLNK; + errorCount--; + break; + } + } else { + if (!mimeAtom.m_str.isNull()) + udsEntry.append(mimeAtom); + mimeAtom.m_str = QString::null; + + udsEntry.append(typeAtom); + typeAtom.m_long = 0; + + if (fishCommand == FISH_STAT) + udsStatEntry = udsEntry; + else if (listReason == LIST) { + listEntry(udsEntry, false); //1 + } else if (listReason == CHECK) checkExist = true; //0 + errorCount--; + udsEntry.clear(); + } + break; + + case FISH_RETR: + if (line.length() == 0) { + error(ERR_IS_DIRECTORY,url.prettyURL()); + recvLen = 0; + break; + } + recvLen = line.toLongLong(&isOk); + if (!isOk) { + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + break; + } + break; + default : break; + } + + } else if (rc == 100) { + switch (fishCommand) { + case FISH_FISH: + writeChild(fishCode, fishCodeLen); + break; + case FISH_READ: + recvLen = 1024; + /* fall through */ + case FISH_RETR: + myDebug( << "reading " << recvLen << endl); + if (recvLen == -1) { + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + } else { + rawRead = recvLen; + dataRead = 0; + mimeTypeSent = false; + if (recvLen == 0) + { + mimeType("application/x-zerosize"); + mimeTypeSent = true; + } + } + break; + case FISH_STOR: + case FISH_WRITE: + case FISH_APPEND: + rawWrite = sendLen; + //myDebug( << "sending " << sendLen << endl); + writeChild(NULL,0); + break; + default : break; + } + } else if (rc/100 != 2) { + switch (fishCommand) { + case FISH_STOR: + case FISH_WRITE: + case FISH_APPEND: + error(ERR_COULD_NOT_WRITE,url.prettyURL()); + shutdownConnection(); + break; + case FISH_RETR: + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + break; + case FISH_READ: + if ( rc == 501 ) + { + mimeType("inode/directory"); + mimeTypeSent = true; + recvLen = 0; + finished(); + } + else + { + error(ERR_COULD_NOT_READ,url.prettyURL()); + shutdownConnection(); + } + break; + case FISH_FISH: + case FISH_VER: + error(ERR_SLAVE_DEFINED,line); + shutdownConnection(); + break; + case FISH_PWD: + case FISH_CWD: + error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL()); + break; + case FISH_LIST: + myDebug( << "list error. reason: " << listReason << endl); + if (listReason == LIST) error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyURL()); + else if (listReason == CHECK) { + checkExist = false; + finished(); + } + break; + case FISH_STAT: + error(ERR_DOES_NOT_EXIST,url.prettyURL()); + udsStatEntry.clear(); + break; + case FISH_CHMOD: + error(ERR_CANNOT_CHMOD,url.prettyURL()); + break; + case FISH_CHOWN: + case FISH_CHGRP: + error(ERR_ACCESS_DENIED,url.prettyURL()); + break; + case FISH_MKD: + if ( rc == 501 ) + error(ERR_DIR_ALREADY_EXIST,url.prettyURL()); + else + error(ERR_COULD_NOT_MKDIR,url.prettyURL()); + break; + case FISH_RMD: + error(ERR_COULD_NOT_RMDIR,url.prettyURL()); + break; + case FISH_DELE: + error(ERR_CANNOT_DELETE,url.prettyURL()); + break; + case FISH_RENAME: + error(ERR_CANNOT_RENAME,url.prettyURL()); + break; + case FISH_COPY: + case FISH_LINK: + case FISH_SYMLINK: + error(ERR_COULD_NOT_WRITE,url.prettyURL()); + break; + default : break; + } + } else { + if (fishCommand == FISH_STOR) fishCommand = (hasAppend?FISH_APPEND:FISH_WRITE); + if (fishCommand == FISH_FISH) { + connected(); + } else if (fishCommand == FISH_LIST) { + if (listReason == LIST) { + listEntry(UDSEntry(),true); + } else if (listReason == CHECK) { + if (!checkOverwrite && checkExist) + { + error(ERR_FILE_ALREADY_EXIST,url.prettyURL()); + return; // Don't call finished! + } + } + } else if (fishCommand == FISH_STAT) { + UDSAtom atom; + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = url.fileName(); + udsStatEntry.append( atom ); + statEntry(udsStatEntry); + } else if (fishCommand == FISH_APPEND) { + dataReq(); + if (readData(rawData) > 0) sendCommand(FISH_APPEND,E(QString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); + sendLen = rawData.size(); + } else if (fishCommand == FISH_WRITE) { + dataReq(); + if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(QString::number(putPos)),E(QString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path())); + putPos += rawData.size(); + sendLen = rawData.size(); + } else if (fishCommand == FISH_RETR) { + data(QByteArray()); + } + finished(); + } +} + +void fishProtocol::writeStdin(const QString &line) +{ + qlist.append(line); + + if (writeReady) { + writeReady = false; + //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl); + myDebug( << "Writing: " << qlist.first() << endl); + myDebug( << "---------" << endl); + writeChild((const char *)qlist.first().latin1(), qlist.first().length()); + } +} + +void fishProtocol::sent() +{ + if (rawWrite > 0) { + myDebug( << "writing raw: " << rawData.size() << "/" << rawWrite << endl); + writeChild(rawData.data(),(rawWrite > rawData.size()?rawData.size():rawWrite)); + rawWrite -= rawData.size(); + if (rawWrite > 0) { + dataReq(); + if (readData(rawData) <= 0) { + shutdownConnection(); + } + } + return; + } else if (rawWrite == 0) { + // workaround: some dd's insist in reading multiples of + // 8 bytes, swallowing up to seven bytes. Sending + // newlines is safe even when a sane dd is used + writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",15); + rawWrite = -1; + return; + } + if (qlist.count() > 0) qlist.remove(qlist.begin()); + if (qlist.count() == 0) { + writeReady = true; + } else { + //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().find('\n')) << endl); + myDebug( << "Writing: " << qlist.first() << endl); + myDebug( << "---------" << endl); + writeChild((const char *)qlist.first().latin1(),qlist.first().length()); + } +} + +int fishProtocol::received(const char *buffer, KIO::fileoffset_t buflen) +{ + int pos = 0; + do { + if (buflen <= 0) break; + + if (rawRead > 0) { + //myDebug( << "processedSize " << dataRead << ", len " << buflen << "/" << rawRead << endl); + int dataSize = (rawRead > buflen?buflen:rawRead); + if (!mimeTypeSent) + { + int mimeSize = QMIN(dataSize, (int)mimeBuffer.size()-dataRead); + memcpy(mimeBuffer.data()+dataRead,buffer,mimeSize); + dataRead += mimeSize; + rawRead -= mimeSize; + buffer += mimeSize; + buflen -= mimeSize; + if (rawRead == 0) // End of data + mimeBuffer.resize(dataRead); + if (dataRead < (int)mimeBuffer.size()) + { + myDebug( << "wait for more" << endl); + break; + } + + // We need a KMimeType::findByNameAndContent(filename,data) + // For now we do: find by extension, and if not found (or extension not reliable) + // then find by content. + bool accurate = false; + KMimeType::Ptr mime = KMimeType::findByURL( url, 0, false, true, &accurate ); + if ( !mime || mime->name() == KMimeType::defaultMimeType() + || !accurate ) + { + KMimeType::Ptr p_mimeType = KMimeType::findByContent(mimeBuffer); + if ( p_mimeType && p_mimeType->name() != KMimeType::defaultMimeType() ) + mime = p_mimeType; + } + + sendmimeType(mime->name()); + + + mimeTypeSent = true; + if (fishCommand != FISH_READ) { + totalSize(dataRead + rawRead); + data(mimeBuffer); + processedSize(dataRead); + } + mimeBuffer.resize(1024); + pos = 0; + continue; // Process rest of buffer/buflen + } + + QByteArray bdata; + bdata.duplicate(buffer,dataSize); + data(bdata); + + dataRead += dataSize; + rawRead -= dataSize; + processedSize(dataRead); + if (rawRead <= 0) { + buffer += dataSize; + buflen -= dataSize; + } else { + return 0; + } + } + + if (buflen <= 0) break; + + pos = 0; + // Find newline + while((pos < buflen) && (buffer[pos] != '\n')) + ++pos; + + if (pos < buflen) + { + QString s = remoteEncoding()->decode(QCString(buffer,pos+1)); + + buffer += pos+1; + buflen -= pos+1; + + manageConnection(s); + + pos = 0; + // Find next newline + while((pos < buflen) && (buffer[pos] != '\n')) + ++pos; + } + } while (childPid && buflen && (rawRead > 0 || pos < buflen)); + return buflen; +} +/** get a file */ +void fishProtocol::get(const KURL& u){ + myDebug( << "@@@@@@@@@ get " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + recvLen = -1; + sendCommand(FISH_RETR,E(url.path())); + } + run(); +} + +/** put a file */ +void fishProtocol::put(const KURL& u, int permissions, bool overwrite, bool /*resume*/){ + myDebug( << "@@@@@@@@@ put " << u << " " << permissions << " " << overwrite << " " /* << resume */ << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + putPerm = permissions; + checkOverwrite = overwrite; + checkExist = false; + putPos = 0; + listReason = CHECK; + sendCommand(FISH_LIST,E(url.path())); + sendCommand(FISH_STOR,"0",E(url.path())); + } + run(); +} +/** executes next command in sequence or calls finished() if all is done */ +void fishProtocol::finished() { + if (commandList.count() > 0) { + fishCommand = (fish_command_type)commandCodes.first(); + errorCount = -fishInfo[fishCommand].lines; + rawRead = 0; + rawWrite = -1; + udsEntry.clear(); + udsStatEntry.clear(); + writeStdin(commandList.first()); + //if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending "+(commandList.first().mid(1,commandList.first().find("\n")-1))+"..."); + commandList.remove(commandList.begin()); + commandCodes.remove(commandCodes.begin()); + } else { + myDebug( << "_______ emitting finished()" << endl); + SlaveBase::finished(); + isRunning = false; + } +} +/** aborts command sequence and calls error() */ +void fishProtocol::error(int type, const QString &detail) { + commandList.clear(); + commandCodes.clear(); + myDebug( << "ERROR: " << type << " - " << detail << endl); + SlaveBase::error(type,detail); + isRunning = false; +} +/** executes a chain of commands */ +void fishProtocol::run() { + if (!isRunning) { + int rc; + isRunning = true; + finished(); + fd_set rfds, wfds; + FD_ZERO(&rfds); + char buf[32768]; + int offset = 0; + while (isRunning) { + 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); + error(ERR_CONNECTION_BROKEN,connectionHost); + shutdownConnection(); + return; + } + if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) { +#if 0 + QString debug; + debug.setLatin1(outBuf+outBufPos,outBufLen-outBufPos); + myDebug( << "now writing " << (outBufLen-outBufPos) << " " << debug.left(40) << "..." << endl); +#endif + if (outBufLen-outBufPos > 0) rc = write(childFd,outBuf+outBufPos,outBufLen-outBufPos); + else rc = 0; + if (rc >= 0) outBufPos += rc; + else { + if (errno == EINTR) + continue; + myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl); + error(ERR_CONNECTION_BROKEN,connectionHost); + shutdownConnection(); + return; + } + if (outBufPos >= outBufLen) { + outBufPos = -1; + outBuf = NULL; + sent(); + } + } + else if (FD_ISSET(childFd,&rfds)) { + rc = read(childFd,buf+offset,32768-offset); + //myDebug( << "read " << rc << " bytes" << endl); + if (rc > 0) { + int noff = received(buf,rc+offset); + if (noff > 0) memmove(buf,buf+offset+rc-noff,noff); + //myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset) << endl); + offset = noff; + } else { + if (errno == EINTR) + continue; + myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl); + error(ERR_CONNECTION_BROKEN,connectionHost); + shutdownConnection(); + return; + } + } + if (wasKilled()) + return; + } + } +} +/** stat a file */ +void fishProtocol::stat(const KURL& u){ + myDebug( << "@@@@@@@@@ stat " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + isStat = true; // FIXME: just a workaround for konq deficiencies + openConnection(); + isStat = false; // FIXME: just a workaround for konq deficiencies + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + sendCommand(FISH_STAT,E(url.path(-1))); + } + run(); +} +/** find mimetype for a file */ +void fishProtocol::mimetype(const KURL& u){ + myDebug( << "@@@@@@@@@ mimetype " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + recvLen = 1024; + sendCommand(FISH_READ,"0","1024",E(url.path())); + } + run(); +} +/** list a directory */ +void fishProtocol::listDir(const KURL& u){ + myDebug( << "@@@@@@@@@ listDir " << u << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + listReason = LIST; + sendCommand(FISH_LIST,E(url.path())); + } + run(); +} +/** create a directory */ +void fishProtocol::mkdir(const KURL& u, int permissions) { + myDebug( << "@@@@@@@@@ mkdir " << u << " " << permissions << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + sendCommand(FISH_MKD,E(url.path())); + if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); + } + run(); +} +/** rename a file */ +void fishProtocol::rename(const KURL& s, const KURL& d, bool overwrite) { + myDebug( << "@@@@@@@@@ rename " << s << " " << d << " " << overwrite << endl); + if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) { + error(ERR_UNSUPPORTED_ACTION,s.prettyURL()); + return; + } + setHost(s.host(),s.port(),s.user(),s.pass()); + url = d; + openConnection(); + if (!isLoggedIn) return; + KURL src = s; + url.cleanPath(); + src.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (!overwrite) { + listReason = CHECK; + checkOverwrite = false; + sendCommand(FISH_LIST,E(url.path())); + } + sendCommand(FISH_RENAME,E(src.path()),E(url.path())); + } + run(); +} +/** create a symlink */ +void fishProtocol::symlink(const QString& target, const KURL& u, bool overwrite) { + myDebug( << "@@@@@@@@@ symlink " << target << " " << u << " " << overwrite << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (!overwrite) { + listReason = CHECK; + checkOverwrite = false; + sendCommand(FISH_LIST,E(url.path())); + } + sendCommand(FISH_SYMLINK,E(target),E(url.path())); + } + run(); +} +/** change file permissions */ +void fishProtocol::chmod(const KURL& u, int permissions){ + myDebug( << "@@@@@@@@@ chmod " << u << " " << permissions << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); + } + run(); +} +/** copies a file */ +void fishProtocol::copy(const KURL &s, const KURL &d, int permissions, bool overwrite) { + myDebug( << "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << overwrite << endl); + if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) { + error(ERR_UNSUPPORTED_ACTION,s.prettyURL()); + return; + } + //myDebug( << s << endl << d << endl); + setHost(s.host(),s.port(),s.user(),s.pass()); + url = d; + openConnection(); + if (!isLoggedIn) return; + KURL src = s; + url.cleanPath(); + src.cleanPath(); + if (!src.hasPath()) { + sendCommand(FISH_PWD); + } else { + if (!overwrite) { + listReason = CHECK; + checkOverwrite = false; + sendCommand(FISH_LIST,E(url.path())); + } + sendCommand(FISH_COPY,E(src.path()),E(url.path())); + if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path())); + } + run(); +} +/** removes a file or directory */ +void fishProtocol::del(const KURL &u, bool isFile){ + myDebug( << "@@@@@@@@@ del " << u << " " << isFile << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + url.cleanPath(); + if (!url.hasPath()) { + sendCommand(FISH_PWD); + } else { + sendCommand((isFile?FISH_DELE:FISH_RMD),E(url.path())); + } + run(); +} +/** special like background execute */ +void fishProtocol::special( const QByteArray &data ){ + int tmp; + + QDataStream stream(data, IO_ReadOnly); + + stream >> tmp; + switch (tmp) { + case FISH_EXEC_CMD: // SSH EXEC + { + KURL u; + QString command; + QString tempfile; + stream >> u; + stream >> command; + myDebug( << "@@@@@@@@@ exec " << u << " " << command << endl); + setHost(u.host(),u.port(),u.user(),u.pass()); + url = u; + openConnection(); + if (!isLoggedIn) return; + sendCommand(FISH_EXEC,E(command),E(url.path())); + run(); + break; + } + default: + // Some command we don't understand. + error(ERR_UNSUPPORTED_ACTION,QString().setNum(tmp)); + break; + } +} +/** report status */ +void fishProtocol::slave_status() { + myDebug( << "@@@@@@@@@ slave_status" << endl); + if (childPid > 0) + slaveStatus(connectionHost,isLoggedIn); + else + slaveStatus(QString::null,false); +} diff --git a/kioslave/fish/fish.h b/kioslave/fish/fish.h new file mode 100644 index 000000000..e2665a320 --- /dev/null +++ b/kioslave/fish/fish.h @@ -0,0 +1,211 @@ +/*************************************************************************** + fish.h - a FISH kioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001 by Jörg Walter + email : trouble@garni.ch + ***************************************************************************/ + +/*************************************************************************** + * * + * 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, version 2 of the License * + * * + ***************************************************************************/ +#ifndef __fish_h__ +#define __fish_h__ + +#include <qstring.h> +#include <qcstring.h> + + +#include <kurl.h> +#include <kio/global.h> +#include <kio/slavebase.h> +#include <kprocess.h> +#include <kio/authinfo.h> +#include <time.h> + +#define FISH_EXEC_CMD 'X' + +class fishProtocol : public KIO::SlaveBase +{ +public: + fishProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~fishProtocol(); + + /** +Connects to a server and logs us in via SSH. Then starts FISH protocol. +@ref isConnected is set to true if logging on was successful. +It is set to false if the connection becomes closed. + + */ + void openConnection(); + + /** + Clean up connection + */ + void shutdownConnection(bool forced=false); + /** sets connection information for subsequent commands */ + void setHost(const QString & host, int port, const QString & user, const QString & pass); + /** Forced close of the connection */ + void closeConnection(); + /** get a file */ + void get(const KURL& url); + /** put a file */ + void put(const KURL& url, int permissions, bool overwrite, bool resume); + /** aborts command sequence and calls error() */ + void error(int type, const QString &detail); + /** executes next command in sequence or calls finished() if all is done */ + void finished(); + /** stat a file */ + void stat(const KURL& url); + /** find mimetype for a file */ + void mimetype(const KURL& url); + /** list a directory */ + void listDir(const KURL& url); + /** create a directory */ + void mkdir(const KURL&url, int permissions); + /** rename a file */ + void rename(const KURL& src, const KURL& dest, bool overwrite); + /** create a symlink */ + void symlink(const QString& target, const KURL& dest, bool overwrite); + /** change file permissions */ + void chmod(const KURL& url, int permissions); + /** copies a file */ + void copy(const KURL &src, const KURL &dest, int permissions, bool overwrite); + /** report status */ + void slave_status(); + /** removes a file or directory */ + void del(const KURL &u, bool isfile); + /** special like background execute */ + void special( const QByteArray &data ); + +private: // Private attributes + /** the SSH process used to communicate with the remote end */ + pid_t childPid; + /** fd for reading and writing to the process */ + int childFd; + /** buffer for data to be written */ + const char *outBuf; + /** current write position in buffer */ + KIO::fileoffset_t outBufPos; + /** length of buffer */ + KIO::fileoffset_t outBufLen; + /** use su if true else use ssh */ + bool local; + /** // FIXME: just a workaround for konq deficiencies */ + bool isStat; + /** // FIXME: just a workaround for konq deficiencies */ + QString redirectUser, redirectPass; + +protected: // Protected attributes + /** for LIST/STAT */ + KIO::UDSEntry udsEntry; + /** for LIST/STAT */ + KIO::UDSEntry udsStatEntry; + /** for LIST/STAT */ + KIO::UDSAtom typeAtom; + /** for LIST/STAT */ + KIO::UDSAtom mimeAtom; + /** for LIST/STAT */ + QString thisFn; + /** for STAT */ + QString wantedFn; + QString statPath; + /** url of current request */ + KURL url; + /** true if connection is logged in successfully */ + bool isLoggedIn; + /** host name of current connection */ + QString connectionHost; + /** user name of current connection */ + QString connectionUser; + /** port of current connection */ + int connectionPort; + /** password of current connection */ + QString connectionPassword; + /** AuthInfo object used for logging in */ + KIO::AuthInfo connectionAuth; + /** number of lines received, == 0 -> everything went ok */ + int errorCount; + /** queue for lines to be sent */ + QStringList qlist; + /** queue for commands to be sent */ + QStringList commandList; + /** queue for commands to be sent */ + QValueList<int> commandCodes; + /** bytes still to be read in raw mode */ + KIO::fileoffset_t rawRead; + /** bytes still to be written in raw mode */ + KIO::fileoffset_t rawWrite; + /** data bytes to read in next read command */ + KIO::fileoffset_t recvLen; + /** data bytes to write in next write command */ + KIO::fileoffset_t sendLen; + /** true if the last write operation was finished */ + bool writeReady; + /** true if a command stack is currently executing */ + bool isRunning; + /** reason of LIST command */ + enum { CHECK, LIST } listReason; + /** true if FISH server understands APPEND command */ + bool hasAppend; + /** permission of created file */ + int putPerm; + /** true if file may be overwritten */ + bool checkOverwrite; + /** current position of write */ + KIO::fileoffset_t putPos; + /** true if file already existed */ + bool checkExist; + /** true if this is the first login attempt (== use cached password) */ + bool firstLogin; + /** write buffer */ + QByteArray rawData; + /** buffer for storing bytes used for MimeMagic */ + QByteArray mimeBuffer; + /** whther the mimetype has been sent already */ + bool mimeTypeSent; + /** number of bytes read so far */ + KIO::fileoffset_t dataRead; + /** details about each fishCommand */ + static const struct fish_info { + const char *command; + int params; + const char *alt; + int lines; + } fishInfo[]; + /** last FISH command sent to server */ + enum fish_command_type { FISH_FISH, FISH_VER, FISH_PWD, FISH_LIST, FISH_STAT, + FISH_RETR, FISH_STOR, + FISH_CWD, FISH_CHMOD, FISH_DELE, FISH_MKD, FISH_RMD, + FISH_RENAME, FISH_LINK, FISH_SYMLINK, FISH_CHOWN, + FISH_CHGRP, FISH_READ, FISH_WRITE, FISH_COPY, FISH_APPEND, FISH_EXEC } fishCommand; + int fishCodeLen; +protected: // Protected methods + /** manages initial communication setup including password queries */ + int establishConnection(char *buffer, KIO::fileoffset_t buflen); + int received(const char *buffer, KIO::fileoffset_t buflen); + void sent(); + /** builds each FISH request and sets the error counter */ + bool sendCommand(fish_command_type cmd, ...); + /** checks response string for result code, converting 000 and 001 appropriately */ + int handleResponse(const QString &str); + /** parses a ls -l time spec */ + int makeTimeFromLs(const QString &dayStr, const QString &monthStr, const QString &timeyearStr); + /** executes a chain of commands */ + void run(); + /** creates the subprocess */ + bool connectionStart(); + /** writes one chunk of data to stdin of child process */ + void writeChild(const char *buf, KIO::fileoffset_t len); + /** parses response from server and acts accordingly */ + void manageConnection(const QString &line); + /** writes to process */ + void writeStdin(const QString &line); +}; + + +#endif diff --git a/kioslave/fish/fish.pl b/kioslave/fish/fish.pl new file mode 100755 index 000000000..1ba539f9f --- /dev/null +++ b/kioslave/fish/fish.pl @@ -0,0 +1,369 @@ +#!/usr/bin/perl +# 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, version 2 of the License +=pod +This file was transferred by kio_fish, a network client part of the +KDE project. You may safely delete it, it will be transferred again +when needed. It's only purpose is to make kio_fish access faster and +more reliable. +=cut + +use Fcntl; + +$|++; +#open(DEBUG,">/tmp/kio_fish.debug.$$.log"); +# save code in initial directory if just transferred +if (defined $code) { + unlink('.fishsrv.pl'); + sysopen(FH,'.fishsrv.pl',O_WRONLY|O_CREAT|O_EXCL); + print FH $code; + close(FH); + chmod(0444,'.fishsrv.pl'); +# request new code if it changed (checksum mismatch) +# for automatic upgrades +} elsif ($ARGV[0] ne "{CHECKSUM}") { + $|=1; + print "### 100 transfer fish server\n"; + while(<STDIN>) { + last if /^__END__/; + $code.=$_; + } + exit(eval($code)); +} + +# we are up and running. +print "### 200\n"; +use strict; +use POSIX qw(getcwd dup2 strftime); +$SIG{'CHLD'} = 'IGNORE'; +$| = 1; +MAIN: while (<STDIN>) { + chomp; + chomp; + next if !length($_) || substr($_,0,1) ne '#'; +#print DEBUG "$_\n"; + s/^#//; + /^VER / && do { + # We do not advertise "append" capability anymore, as "write" is + # as fast in perl mode and more reliable (overlapping writes) + print "VER 0.0.3 copy lscount lslinks lsmime exec stat\n### 200\n"; + next; + }; + /^PWD$/ && do { + print getcwd(),"\n### 200\n"; + next; + }; + /^SYMLINK\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + print (symlink($ofn,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^COPY\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + my ($size) = (stat($ofn))[7]; + my $read = 1; + if (-l $ofn) { + my $dest = readlink($ofn); + unlink($fn); + symlink($dest,$fn) || ($read = 0); + } else { + sysopen(FH,$ofn,O_RDONLY) || do { print "### 500 $!\n"; next; }; + sysopen(OFH,$fn,O_WRONLY|O_CREAT|O_TRUNC) || do { close(FH); print "### 500 $!\n"; next; }; + local $/ = undef; + my $buffer = ''; + while ($size > 32768 && ($read = sysread(FH,$buffer,32768)) > 0) { + $size -= $read; + if (syswrite(OFH,$buffer,$read) != $read) { + close(FH); close(OFH); + print "### 500 $!\n"; + next MAIN; + } + + } + while ($size > 0 && ($read = sysread(FH,$buffer,$size)) > 0) { + $size -= $read; + if (syswrite(OFH,$buffer,$read) != $read) { + close(FH); close(OFH); + print "### 500 $!\n"; + next MAIN; + } + } + close(FH); + close(OFH); + } + if ($read > 0) { + print "### 200\n"; + } else { + print "### 500 $!\n"; + } + next; + }; + /^LINK\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + print (link($ofn,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^RENAME\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $ofn = unquote($1); + my $fn = unquote($2); + print (rename($ofn,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^CHGRP\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($2); + print (chown(-1,int($1),$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^CHOWN\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($2); + print (chown(int($1),-1,$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^CHMOD\s+([0-7]+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($2); + print (chmod(oct($1),$fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^DELE\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $fn = unquote($1); + print (unlink($fn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^RMD\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $dn = unquote($1); + print (rmdir($dn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^MKD\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $dn = unquote($1); + if (mkdir($dn,0777)) { + print "### 200\n"; + } else { + my $err = $!; + print (chdir($dn)?"### 501 $err\n":"### 500 $err\n"); + } + next; + }; + /^CWD\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $dn = unquote($1); + print (chdir($dn)?"### 200\n":"### 500 $!\n"); + next; + }; + /^LIST\s+((?:\\.|[^\\])*?)\s*$/ && do { + list($1, 1); + next; + }; + /^STAT\s+((?:\\.|[^\\])*?)\s*$/ && do { + list($1, 0); + next; + }; + /^WRITE\s+(\d+)\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + write_loop($2,$3,O_WRONLY|O_CREAT,$1); + next; + }; + /^APPEND\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + write_loop($1,$2,O_WRONLY|O_APPEND); + next; + }; + /^STOR\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + write_loop($1,$2,O_WRONLY|O_CREAT|O_TRUNC); + next; + }; + /^RETR\s+((?:\\.|[^\\])*?)\s*$/ && do { + read_loop($1); + next; + }; + /^READ\s+(\d+)\s+(\d+)\s+((?:\\.|[^\\])*?)\s*$/ && do { + read_loop($3,$2,$1); + next; + }; + /^EXEC\s+((?:\\.|[^\\])*?)\s+((?:\\.|[^\\])*?)\s*$/ && do { + my $tempfile = unquote($2); + my $command = unquote($1); + $command = $command . ";echo \"###RESULT: \$?\""; + print("### 500 $!\n"), next + if (!sysopen(FH,$tempfile,O_CREAT|O_EXCL|O_WRONLY,0600)); + my $pid = fork(); + print("### 500 $!\n"), next + if (!defined $pid); + if ($pid == 0) { + open(STDOUT,'>>&FH'); + open(STDERR,'>>&FH'); + open(STDIN,'</dev/null'); # not sure here, ms windows anyone? + exec('/bin/sh','-c',$command); + print STDERR "Couldn't exec /bin/sh: $!\n"; + exit(255); + } + waitpid($pid,0); + close(FH); + print "### 200\n"; + next; + }; +} +exit(0); + +sub list { + my $dn = unquote($_[0]); + my @entries; + if (!-e $dn) { + print "### 404 File does not exist\n"; + return; + } elsif ($_[1] && -d _) { + opendir(DIR,$dn) || do { print "### 500 $!\n"; return; }; + @entries = readdir(DIR); + closedir(DIR); + } else { + ($dn, @entries) = $dn =~ m{(.*)/(.*)}; + $dn = '/' if (!length($dn)); + } + print scalar(@entries),"\n### 100\n"; + my $cwd = getcwd(); + chdir($dn) || do { print "### 500 $!\n"; return; }; + foreach (@entries) { + my $link = readlink; + my ($mode,$uid,$gid,$size,$mtime) = (lstat)[2,4,5,7,9]; + print filetype($mode,$link,$uid,$gid); + print "S$size\n"; + print strftime("D%Y %m %d %H %M %S\n",localtime($mtime)); + print ":$_\n"; + print "L$link\n" if defined $link; + print mimetype($_); + print "\n"; + } + chdir($cwd); + print "### 200\n"; +} + +sub read_loop { + my $fn = unquote($_[0]); + my ($size) = ($_[1]?int($_[1]):(stat($fn))[7]); + my $error = ''; + print "### 501 Is directory\n" and return if -d $fn; + sysopen(FH,$fn,O_RDONLY) || ($error = $!); + if ($_[2]) { + sysseek(FH,int($_[2]),0) || do { close(FH); $error ||= $!; }; + } + print "### 500 $error\n" and return if $error; + if (@_ < 2) { + print "$size\n"; + } + print "### 100\n"; + my $buffer = ''; + my $read = 1; + while ($size > 32768 && ($read = sysread(FH,$buffer,32768)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + print $buffer; + } + while ($size > 0 && ($read = sysread(FH,$buffer,$size)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + print $buffer; + } + while ($size > 0) { + print ' '; + $size--; + } + $error ||= $! if $read <= 0; + close(FH); + if (!$error) { + print "### 200\n"; + } else { + print "### 500 $error\n"; + } +} + +sub write_loop { + my $size = int($_[0]); + my $fn = unquote($_[1]); +#print DEBUG "write_loop called $size size, $fn fn, $_[2]\n"; + my $error = ''; + sysopen(FH,$fn,$_[2]) || do { print "### 400 $!\n"; return; }; + eval { flock(FH,2); }; + if ($_[3]) { + sysseek(FH,int($_[3]),0) || do { close(FH);print "### 400 $!\n"; return; }; + } + <STDIN>; + print "### 100\n"; + my $buffer = ''; + my $read = 1; + while ($size > 32768 && ($read = read(STDIN,$buffer,32768)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + $error ||= $! if (syswrite(FH,$buffer,$read) != $read); + } + while ($size > 0 && ($read = read(STDIN,$buffer,$size)) > 0) { +#print DEBUG "$size left, $read read\n"; + $size -= $read; + $error ||= $! if (syswrite(FH,$buffer,$read) != $read); + } + close(FH); + if (!$error) { + print "### 200\n"; + } else { + print "### 500 $error\n"; + } +} + +sub unquote { $_ = shift; s/\\(.)/$1/g; return $_; } + +sub filetype { + my ($mode,$link,$uid,$gid) = @_; + my $result = 'P'; + while (1) { + -f _ && do { $result .= '-'; last; }; + -d _ && do { $result .= 'd'; last; }; + defined($link) && do { $result .= 'l'; last; }; + -c _ && do { $result .= 'c'; last; }; + -b _ && do { $result .= 'b'; last; }; + -S _ && do { $result .= 's'; last; }; + -p _ && do { $result .= 'p'; last; }; + $result .= '?'; last; + } + $result .= ($mode & 0400?'r':'-'); + $result .= ($mode & 0200?'w':'-'); + $result .= ($mode & 0100?($mode&04000?'s':'x'):($mode&04000?'S':'-')); + $result .= ($mode & 0040?'r':'-'); + $result .= ($mode & 0020?'w':'-'); + $result .= ($mode & 0010?($mode&02000?'s':'x'):($mode&02000?'S':'-')); + $result .= ($mode & 0004?'r':'-'); + $result .= ($mode & 0002?'w':'-'); + $result .= ($mode & 0001?($mode&01000?'t':'x'):($mode&01000?'T':'-')); + + $result .= ' '; + $result .= (getpwuid($uid)||$uid); + $result .= '.'; + $result .= (getgrgid($gid)||$gid); + $result .= "\n"; + return $result; +} + +sub mimetype { + my $fn = shift; + return "Minode/directory\n" if -d $fn; + pipe(IN,OUT); + my $pid = fork(); + return '' if (!defined $pid); + if ($pid) { + close(OUT); + my $type = <IN>; + close(IN); + chomp $type; + chomp $type; + $type =~ s/[,; ].*//; + return '' if ($type !~ m/\//); + return "M$type\n" + } + close(IN); + sysopen(NULL,'/dev/null',O_RDWR); + dup2(fileno(NULL),fileno(STDIN)); + dup2(fileno(OUT),fileno(STDOUT)); + dup2(fileno(NULL),fileno(STDERR)); + exec('/usr/bin/file','-i','-b','-L',$fn); + exit(0); +} +__END__ diff --git a/kioslave/fish/fish.protocol b/kioslave/fish/fish.protocol new file mode 100644 index 000000000..c14599d50 --- /dev/null +++ b/kioslave/fish/fish.protocol @@ -0,0 +1,81 @@ +[Protocol] +exec=kio_fish +protocol=fish +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link, +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +Icon=remote +Description=A kioslave for the FISH protocol +Description[af]='n Kioslave vir die FISH protokol +Description[be]=Kioslave Ð´Ð»Ñ Ð¿Ñ€Ð°Ñ‚Ð°ÐºÐ¾Ð»Ð° FISH +Description[bn]=ফিশ (FISH) পà§à¦°à§‹à¦Ÿà§‹à¦•à¦²-à¦à¦° জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ kioslave +Description[br]=Ur c'hioslave evit ar c'homenad FISH +Description[bs]=kioslave za FISH protokol +Description[ca]=Un kioslave pel protocol FISH +Description[cs]=Pomocný protokol pro FISH +Description[csb]=Plugins protokòłu FISH +Description[da]=En kioslave for FISH-protokollen +Description[de]=Ein-/Ausgabemodul für das FISH-Protokoll +Description[el]=Ένας kioslave για το Ï€Ïωτόκολλο FISH +Description[eo]=K-enel-sklavo por la FISH protokolo +Description[es]=Un kioslave para el protocolo FISH +Description[et]=FISH protokolli IO-moodul +Description[eu]=FISH protokolorako kioslavea +Description[fa]=یک kioslave برای قرارداد FISH +Description[fi]=Liitäntä FISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole FISH +Description[fy]=In kioslave foar it FISH protokol +Description[ga]=kioslave le haghaidh an phrótacail FISH +Description[gl]=Un kioslave para o protocolo FISH +Description[he]=ממשק kioslave עבור פרוטוקול FISH +Description[hi]=फिश पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¥‰à¤² हेतॠके-आई-ओ-सà¥à¤²à¥‡à¤µ +Description[hr]=Kioslave za FISH protokol +Description[hu]=KDE-protokoll a FISH protokollhoz +Description[is]=kioslave fyrir FISH samskiptaregluna +Description[it]=Un kioslave per il protocollo FISH +Description[ja]=FISH プãƒãƒˆã‚³ãƒ«ã®ãŸã‚ã® kioslave +Description[ka]=kioslave FISH პრáƒáƒ¢áƒáƒ™áƒáƒšáƒ˜áƒ¡áƒ—ვის +Description[kk]=FISH протоколға арналған файл жүйеÑінің модулі +Description[km]=kioslave សម្រាប់​ពិធីការ FISH +Description[ko]=FISH í”„ë¡œí† ì½œì„ ìœ„í•œ KIO ìŠ¬ë ˆì´ë¸Œ +Description[lt]=Kiovergas FISH protokolui +Description[lv]=KIO vergs FISH protokolam +Description[mk]=КИО-Ñлужител за протоколот FISH +Description[ms]=Hamba kio untuk protokol FISH +Description[nb]=En IU-slave for FISH-protokollen +Description[nds]=En In-/Utgaavdeenst för dat FISH-Protokoll +Description[ne]=FISH पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¤²à¤•à¤¾ लागि à¤à¤‰à¤Ÿà¤¾ किओसà¥à¤²à¥‡à¤ +Description[nl]=Een kioslave voor het protocol FISH +Description[nn]=Ein IU-slave for FISH-protokollen +Description[pa]=FISH ਪਰੋਟੋਕਾਲ ਲਈ kioslave +Description[pl]=Wtyczka protokoÅ‚u FISH +Description[pt]=Um 'kioslave' para o protocolo FISH +Description[pt_BR]=Uma implementação para o protocolo FISH +Description[ro]=Un dispozitiv de I/E pentru protocolul FISH +Description[ru]=Модуль файловой ÑиÑтемы Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ð° FISH +Description[rw]=kio-umugaragu ya Porotokole FISH +Description[se]=SO-Å¡láva FISH-protokolla várás +Description[sk]=kioslave pre protokol FISH +Description[sl]=kioslave za protokol FISH +Description[sr]=Kioslave за протокол FISH +Description[sr@Latn]=Kioslave za protokol FISH +Description[sv]=En I/O-slav för protokollet FISH +Description[ta]=FISH நெறிமà¯à®±à¯ˆà®•à¯à®•à®¾à®© ஒர௠கà¯à®¯à¯‹à®¸à¯à®²à¯‡à®µà¯ +Description[te]=à°«à°¿à°·à± à°ªà±à°°à±Šà°Ÿà±Šà°•à°¾à°²à± కొరకౠà°à°’ బానిస +Description[th]=ตัวนำข้à¸à¸¡à¸¹à¸¥à¹€à¸‚้า-à¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹‚ปรโตคà¸à¸¥ FISH +Description[tr]=FISH protokolü için kioslave +Description[tt]=FISH protokolı öçen birem sistemeneñ modulı +Description[uk]=Підлеглий B/Ð’ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ñƒ FISH +Description[uz]=FISH protokoli uchun KCH-sleyv +Description[uz@cyrillic]=FISH протоколи учун КЧ-Ñлейв +Description[vi]=A kioslave (Ä‘Ã y tá»› và o ra KDE) cho giao thức FISH +Description[wa]=On kioslave pol protocole FISH +Description[zh_CN]=FISH å议的 KIO 仆人 +Description[zh_TW]=用於 FISH 通訊å”定的 kioslave +DocPath=kioslave/fish.html diff --git a/kioslave/fish/nxfish.protocol b/kioslave/fish/nxfish.protocol new file mode 100644 index 000000000..f050282af --- /dev/null +++ b/kioslave/fish/nxfish.protocol @@ -0,0 +1,74 @@ +[Protocol] +exec=kio_fish +protocol=nxfish +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link, +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +Icon=remote +Description=A kioslave for the NXFISH protocol +Description[af]='n Kioslave vir die NXFISH protokol +Description[be]=Kioslave Ð´Ð»Ñ Ð¿Ñ€Ð°Ñ‚Ð°ÐºÐ¾Ð»Ð° NXFISH +Description[br]=Ur c'hioslave evit ar c'homenad NXFISH +Description[bs]=kioslave za NXFISH protokol +Description[ca]=Un kioslave pel protocol NXFISH +Description[cs]=Pomocný protokol pro NXFISH +Description[csb]=Plugins protokòłu NXFISH +Description[da]=En kioslave for NXFISH-protokollen +Description[de]=Ein-/Ausgabemodul für das NXFISH-Protokoll +Description[el]=Ένας kioslave για το Ï€Ïωτόκολλο NXFISH +Description[eo]=K-enel-sklavo por la NXFISH protokolo +Description[es]=Un kioslave para el protocolo NXFISH +Description[et]=NXFISH protokolli IO-moodul +Description[eu]=NXFISH protokolorako kioslavea +Description[fa]=یک kioslave برای قرارداد NXFISH +Description[fi]=Liitäntä NXFISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole NXFISH +Description[fy]=In kioslave foar it protokol NXFISH +Description[ga]=kioslave le haghaidh an phrótacail NXFISH +Description[gl]=Un kioslave para o protocolo NXFISH +Description[he]=ממשק kioslave עבור פרוטוקול NXFISH +Description[hr]=Kioslave za NXFISH protokol +Description[hu]=KDE-protokoll az NXFISH protokollhoz +Description[is]=kioslave fyrir NXFISH samskiptaregluna +Description[it]=Un kioslave per il protocollo NXFISH +Description[ja]=NXFISH プãƒãƒˆã‚³ãƒ«ã®ãŸã‚ã® kioslave +Description[ka]=kioslave NXFISH áƒáƒ¥áƒ›áƒ˜áƒ¡áƒ—ვის +Description[kk]=NXFISH протоколы үшін kioslave +Description[km]=kioslave សម្រាប់​ពិធីការ NXFISH +Description[ko]=FISH í”„ë¡œí† ì½œì„ ìœ„í•œ KIO ìŠ¬ë ˆì´ë¸Œ +Description[lt]=PagalbinÄ— kio programa NXFISH protokolui +Description[mk]=КИО-Ñлужител за протоколот NXFISH +Description[nb]=En kioskslave for NXFISH-protokollen +Description[nds]=En In-/Utgaavdeenst för dat NXFISH-Protokoll +Description[ne]=NXFISH पà¥à¤°à¥‹à¤Ÿà¥‹à¤•à¤²à¤•à¤¾ लागि किओसà¥à¤²à¥‡à¤ +Description[nl]=Een kioslave voor het protocol NXFISH +Description[nn]=Ein IU-slave for NXFISH-protokollen +Description[pa]=NXFISH ਪਰੋਟੋਕਾਲ ਲਈ kioslave +Description[pl]=Wtyczka protokoÅ‚u NXFISH +Description[pt]=Um 'kioslave' para o protocolo NXFISH +Description[pt_BR]=Uma implementação para o protocolo NXFISH +Description[ro]=Un kioslave pentru protocolul NXFISH +Description[ru]=Модуль файловой ÑиÑтемы Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ð° NXFISH +Description[se]=SO-Å¡láva NXFISH-protokolla várás +Description[sk]=kioslave pre protokol NXFISH +Description[sl]=kioslave za protokol NXFISH +Description[sr]=Kioslave за протокол NXFISH +Description[sr@Latn]=Kioslave za protokol NXFISH +Description[sv]=En I/O-slav för protokollet NXFISH +Description[te]=ఎనౠఎకà±à°¸à± à°«à°¿à°·à± à°ªà±à°°à±Šà°Ÿà±Šà°•à°¾à°²à± కొరకౠà°à°’ బానిస +Description[th]=ตัวนำข้à¸à¸¡à¸¹à¸¥à¹€à¸‚้า-à¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹‚ปรโตคà¸à¸¥ NXFISH +Description[tr]=NXFISH protokolü için kioslave +Description[uk]=Підлеглий B/Ð’ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ñƒ NXFISH +Description[uz]=NXFISH protokoli uchun KCH-sleyvi +Description[uz@cyrillic]=NXFISH протоколи учун КЧ-Ñлейви +Description[vi]=A kioslave (Ä‘Ã y tá»› và o ra KDE) cho giao thức NXFISH +Description[wa]=On kioslave pol protocole NXFISH +Description[zh_CN]=NXFISH å议的 KIO 仆人 +Description[zh_TW]=用於 NXFISH 通訊å”定的 kioslave +DocPath=kioslave/fish.html diff --git a/kioslave/floppy/AUTHORS b/kioslave/floppy/AUTHORS new file mode 100644 index 000000000..062f9d6cb --- /dev/null +++ b/kioslave/floppy/AUTHORS @@ -0,0 +1,2 @@ +Written and maintained by: +Alexander Neundorf, neundorf@kde.org diff --git a/kioslave/floppy/Makefile.am b/kioslave/floppy/Makefile.am new file mode 100644 index 000000000..5c2533fda --- /dev/null +++ b/kioslave/floppy/Makefile.am @@ -0,0 +1,20 @@ +## Makefile.am of kdebase/kioslave/floppy + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) + +####### Files + +kde_module_LTLIBRARIES = kio_floppy.la + +kio_floppy_la_SOURCES = kio_floppy.cpp program.cpp +kio_floppy_la_LIBADD = $(LIB_KIO) +kio_floppy_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +kdelnk_DATA = floppy.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_floppy.pot diff --git a/kioslave/floppy/README b/kioslave/floppy/README new file mode 100644 index 000000000..99d87bd71 --- /dev/null +++ b/kioslave/floppy/README @@ -0,0 +1,7 @@ +this is an ioslave for KDE 2/3 for accessing fat/vfat floppies without +mounting. +It is a wrapper around the mtools. + + +Alex +neundorf@kde.org diff --git a/kioslave/floppy/TODO b/kioslave/floppy/TODO new file mode 100644 index 000000000..f4f1e679e --- /dev/null +++ b/kioslave/floppy/TODO @@ -0,0 +1,3 @@ +-error handling (will be done until new year) + +Alex diff --git a/kioslave/floppy/floppy.protocol b/kioslave/floppy/floppy.protocol new file mode 100644 index 000000000..3dcae5c78 --- /dev/null +++ b/kioslave/floppy/floppy.protocol @@ -0,0 +1,14 @@ +[Protocol] +exec=kio_floppy +protocol=floppy +input=none +output=filesystem +listing=Name,Type,Size,Date +reading=true +writing=true +makedir=true +deleting=true +moving=true +Icon=3floppy_mount +DocPath=kioslave/floppy.html +Class=:local diff --git a/kioslave/floppy/kio_floppy.cpp b/kioslave/floppy/kio_floppy.cpp new file mode 100644 index 000000000..ef3d6e6f2 --- /dev/null +++ b/kioslave/floppy/kio_floppy.cpp @@ -0,0 +1,1169 @@ +/* This file is part of the KDE project + + Copyright (C) 2000 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif + +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <time.h> +#include <sys/time.h> +#include <sys/types.h> + +#include <qtextstream.h> +#include <qcstring.h> +#include <qfile.h> + +#include "kio_floppy.h" + +#include <kinstance.h> +#include <kdebug.h> +#include <kio/global.h> +#include <klocale.h> + +using namespace KIO; + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_floppy" ); + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_floppy protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + kdDebug(7101) << "Floppy: kdemain: starting" << endl; + + FloppyProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; +} + +void getDriveAndPath(const QString& path, QString& drive, QString& rest) +{ + drive=QString::null; + rest=QString::null; + QStringList list=QStringList::split("/",path); + for (QStringList::Iterator it=list.begin(); it!=list.end(); it++) + { + if (it==list.begin()) + drive=(*it)+":"; + else + rest=rest+"/"+(*it); + } +} + +FloppyProtocol::FloppyProtocol (const QCString &pool, const QCString &app ) +:SlaveBase( "floppy", pool, app ) +,m_mtool(0) +,m_stdoutBuffer(0) +,m_stderrBuffer(0) +,m_stdoutSize(0) +,m_stderrSize(0) +{ + kdDebug(7101)<<"Floppy::Floppy: -"<<pool<<"-"<<endl; +} + +FloppyProtocol::~FloppyProtocol() +{ + delete [] m_stdoutBuffer; + delete [] m_stderrBuffer; + delete m_mtool; + m_mtool=0; + m_stdoutBuffer=0; + m_stderrBuffer=0; +} + +int FloppyProtocol::readStdout() +{ + //kdDebug(7101)<<"Floppy::readStdout"<<endl; + if (m_mtool==0) return 0; + + char buffer[16*1024]; + int length=::read(m_mtool->stdoutFD(),buffer,16*1024); + if (length<=0) return 0; + + //+1 gives us room for a terminating 0 + char *newBuffer=new char[length+m_stdoutSize+1]; + kdDebug(7101)<<"Floppy::readStdout(): length: "<<length<<" m_tsdoutSize: "<<m_stdoutSize<<" +1="<<length+m_stdoutSize+1<<endl; + if (m_stdoutBuffer!=0) + { + memcpy(newBuffer, m_stdoutBuffer, m_stdoutSize); + } + memcpy(newBuffer+m_stdoutSize, buffer, length); + m_stdoutSize+=length; + newBuffer[m_stdoutSize]='\0'; + + delete [] m_stdoutBuffer; + m_stdoutBuffer=newBuffer; + //kdDebug(7101)<<"Floppy::readStdout(): -"<<m_stdoutBuffer<<"-"<<endl; + + //kdDebug(7101)<<"Floppy::readStdout ends"<<endl; + return length; +} + +int FloppyProtocol::readStderr() +{ + //kdDebug(7101)<<"Floppy::readStderr"<<endl; + if (m_mtool==0) return 0; + + /*struct timeval tv; + tv.tv_sec=0; + tv.tv_usec=1000*300; + ::select(0,0,0,0,&tv);*/ + + char buffer[16*1024]; + int length=::read(m_mtool->stderrFD(),buffer,16*1024); + kdDebug(7101)<<"Floppy::readStderr(): read "<<length<<" bytes"<<endl; + if (length<=0) return 0; + + //+1 gives us room for a terminating 0 + char *newBuffer=new char[length+m_stderrSize+1]; + memcpy(newBuffer, m_stderrBuffer, m_stderrSize); + memcpy(newBuffer+m_stderrSize, buffer, length); + m_stderrSize+=length; + newBuffer[m_stderrSize]='\0'; + delete [] m_stderrBuffer; + m_stderrBuffer=newBuffer; + kdDebug(7101)<<"Floppy::readStderr(): -"<<m_stderrBuffer<<"-"<<endl; + + return length; +} + +void FloppyProtocol::clearBuffers() +{ + kdDebug(7101)<<"Floppy::clearBuffers()"<<endl; + m_stdoutSize=0; + m_stderrSize=0; + delete [] m_stdoutBuffer; + m_stdoutBuffer=0; + delete [] m_stderrBuffer; + m_stderrBuffer=0; + //kdDebug(7101)<<"Floppy::clearBuffers() ends"<<endl; +} + +void FloppyProtocol::terminateBuffers() +{ + //kdDebug(7101)<<"Floppy::terminateBuffers()"<<endl; + //append a terminating 0 to be sure + if (m_stdoutBuffer!=0) + { + m_stdoutBuffer[m_stdoutSize]='\0'; + } + if (m_stderrBuffer!=0) + { + m_stderrBuffer[m_stderrSize]='\0'; + } + //kdDebug(7101)<<"Floppy::terminateBuffers() ends"<<endl; +} + +bool FloppyProtocol::stopAfterError(const KURL& url, const QString& drive) +{ + if (m_stderrSize==0) + return true; + //m_stderrBuffer[m_stderrSize]='\0'; + + QString outputString(m_stderrBuffer); + QTextIStream output(&outputString); + QString line=output.readLine(); + kdDebug(7101)<<"line: -"<<line<<"-"<<endl; + if (line.find("resource busy") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access drive %1.\nThe drive is still busy.\nWait until it is inactive and then try again.").arg(drive)); + } + else if ((line.find("Disk full") > -1) || (line.find("No free cluster") > -1)) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not write to file %1.\nThe disk in drive %2 is probably full.").arg(url.prettyURL(),drive)); + } + //file not found + else if (line.find("not found") > -1) + { + error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + //no disk + else if (line.find("not configured") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThere is probably no disk in the drive %2").arg(url.prettyURL(),drive)); + } + else if (line.find("No such device") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThere is probably no disk in the drive %2 or you do not have enough permissions to access the drive.").arg(url.prettyURL(),drive)); + } + else if (line.find("not supported") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThe drive %2 is not supported.").arg(url.prettyURL(),drive)); + } + //not supported or no such drive + else if (line.find("Permission denied") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nMake sure the floppy in drive %2 is a DOS-formatted floppy disk \nand that the permissions of the device file (e.g. /dev/fd0) are set correctly (e.g. rwxrwxrwx).").arg(url.prettyURL(),drive)); + } + else if (line.find("non DOS media") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not access %1.\nThe disk in drive %2 is probably not a DOS-formatted floppy disk.").arg(url.prettyURL(),drive)); + } + else if (line.find("Read-only") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Access denied.\nCould not write to %1.\nThe disk in drive %2 is probably write-protected.").arg(url.prettyURL(),drive)); + } + else if ((outputString.find("already exists") > -1) || (outputString.find("Skipping ") > -1)) + { + error( KIO::ERR_FILE_ALREADY_EXIST,url.prettyURL()); + //return false; + } + else if (outputString.find("could not read boot sector") > -1) + { + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not read boot sector for %1.\nThere is probably not any disk in drive %2.").arg(url.prettyURL(),drive)); + //return false; + } + else + { + error( KIO::ERR_UNKNOWN, outputString); + } + return true; +} + +void FloppyProtocol::listDir( const KURL& _url) +{ + kdDebug(7101)<<"Floppy::listDir() "<<_url.path()<<endl; + KURL url(_url); + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + url.setPath("/a/"); + redirection(url); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + QStringList args; + + args<<"mdir"<<"-a"<<(drive+floppyPath); + if (m_mtool!=0) + delete m_mtool; + m_mtool=new Program(args); + + clearBuffers(); + + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mdir"); + return; + } + + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + //now mdir has finished + //let's parse the output + terminateBuffers(); + + if (errorOccured) + return; + + QString outputString(m_stdoutBuffer); + QTextIStream output(&outputString); + QString line; + + int totalNumber(0); + int mode(0); + UDSEntry entry; + + while (!output.atEnd()) + { + line=output.readLine(); + kdDebug(7101)<<"Floppy::listDir(): line: -"<<line<<"- length: "<<line.length()<<endl; + + if (mode==0) + { + if (line.isEmpty()) + { + kdDebug(7101)<<"Floppy::listDir(): switching to mode 1"<<endl; + mode=1; + } + } + else if (mode==1) + { + if (line[0]==' ') + { + kdDebug(7101)<<"Floppy::listDir(): ende"<<endl; + totalSize(totalNumber); + break; + } + entry.clear(); + StatInfo info=createStatInfo(line); + if (info.isValid) + { + createUDSEntry(info,entry); + //kdDebug(7101)<<"Floppy::listDir(): creating UDSEntry"<<endl; + listEntry( entry, false); + totalNumber++; + } + } + } + listEntry( entry, true ); // ready + finished(); + //kdDebug(7101)<<"Floppy::listDir() ends"<<endl; +} + +void FloppyProtocol::errorMissingMToolsProgram(const QString& name) +{ + error(KIO::ERR_SLAVE_DEFINED,i18n("Could not start program \"%1\".\nEnsure that the mtools package is installed correctly on your system.").arg(name)); + } + +void FloppyProtocol::createUDSEntry(const StatInfo& info, UDSEntry& entry) +{ + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = info.name; + entry.append( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = info.size; + entry.append(atom); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = info.time; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long=info.mode; + entry.append( atom ); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long =(info.isDir?S_IFDIR:S_IFREG); + entry.append( atom ); +} + +StatInfo FloppyProtocol::createStatInfo(const QString line, bool makeStat, const QString& dirName) +{ + //kdDebug(7101)<<"Floppy::createUDSEntry()"<<endl; + QString name; + QString size; + bool isDir(false); + QString day,month, year; + QString hour, minute; + StatInfo info; + + if (line.length()==41) + { + int nameLength=line.find(' '); + kdDebug(7101)<<"Floppy::createStatInfo: line find: "<<nameLength <<"= -"<<line<<"-"<<endl; + if (nameLength>0) + { + name=line.mid(0,nameLength); + QString ext=line.mid(9,3); + ext=ext.stripWhiteSpace(); + if (!ext.isEmpty()) + name+="."+ext; + } + kdDebug(7101)<<"Floppy::createStatInfo() name 8.3= -"<<name<<"-"<<endl; + } + else if (line.length()>41) + { + name=line.mid(42); + kdDebug(7101)<<"Floppy::createStatInfo() name vfat: -"<<name<<"-"<<endl; + } + if ((name==".") || (name=="..")) + { + if (makeStat) + name=dirName; + else + { + info.isValid=false; + return info; + } + } + + if (line.mid(13,5)=="<DIR>") + { + //kdDebug(7101)<<"Floppy::createUDSEntry() isDir"<<endl; + size="1024"; + isDir=true; + } + else + { + size=line.mid(13,9); + //kdDebug(7101)<<"Floppy::createUDSEntry() size: -"<<size<<"-"<<endl; + } + + //TEEKANNE JPG 70796 01-02-2003 17:47 Teekanne.jpg + if (line[25]=='-') + { + month=line.mid(23,2); + day=line.mid(26,2); + year=line.mid(29,4); + } + else //SETUP PKG 1019 1997-09-25 10:31 setup.pkg + { + year=line.mid(23,4); + month=line.mid(28,2); + day=line.mid(31,2); + } + hour=line.mid(35,2); + minute=line.mid(38,2); + //kdDebug(7101)<<"Floppy::createUDSEntry() day: -"<<day<<"-"<<month<<"-"<<year<<"- -"<<hour<<"-"<<minute<<"-"<<endl; + + if (name.isEmpty()) + { + info.isValid=false; + return info; + } + + info.name=name; + info.size=size.toInt(); + + QDateTime date(QDate(year.toInt(),month.toInt(),day.toInt()),QTime(hour.toInt(),minute.toInt())); + info.time=date.toTime_t(); + + if (isDir) + info.mode = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH| S_IWOTH|S_IWGRP|S_IWUSR ; + else + info.mode = S_IRUSR | S_IRGRP | S_IROTH| S_IWOTH|S_IWGRP|S_IWUSR; + + info.isDir=isDir; + + info.isValid=true; + //kdDebug(7101)<<"Floppy::createUDSEntry() ends"<<endl; + return info; +} + +StatInfo FloppyProtocol::_stat(const KURL& url) +{ + StatInfo info; + + QString path(url.path()); + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + if (floppyPath.isEmpty()) + { + kdDebug(7101)<<"Floppy::_stat(): floppyPath.isEmpty()"<<endl; + info.name=path; + info.size=1024; + info.time=0; + info.mode=S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH| S_IWOTH|S_IWGRP|S_IWUSR; + info.isDir=true; + info.isValid=true; + + return info; + } + + //kdDebug(7101)<<"Floppy::_stat(): delete m_mtool"<<endl; + if (m_mtool!=0) + delete m_mtool; + + QStringList args; + args<<"mdir"<<"-a"<<(drive+floppyPath); + + //kdDebug(7101)<<"Floppy::_stat(): create m_mtool"<<endl; + m_mtool=new Program(args); + + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mdir"); + return info; + } + + + clearBuffers(); + + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + //kdDebug(7101)<<"Floppy::_stat(): delete m_mtool"<<endl; + delete m_mtool; + m_mtool=0; + //now mdir has finished + //let's parse the output + terminateBuffers(); + + if (errorOccured) + { + info.isValid=false; + return info; + } + + if (m_stdoutSize==0) + { + info.isValid=false; + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; + } + + kdDebug(7101)<<"Floppy::_stat(): parse stuff"<<endl; + QString outputString(m_stdoutBuffer); + QTextIStream output(&outputString); + QString line; + for (int lineNumber=0; !output.atEnd(); lineNumber++) + { + line=output.readLine(); + if ( (lineNumber<3) || (line.isEmpty()) ) + continue; + StatInfo info=createStatInfo(line,true,url.fileName()); + if (info.isValid==false) + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; + } + if (info.isValid==false) + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; +} + +int FloppyProtocol::freeSpace(const KURL& url) +{ + QString path(url.path()); + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + //kdDebug(7101)<<"Floppy::freeSpace(): delete m_mtool"<<endl; + if (m_mtool!=0) + delete m_mtool; + + QStringList args; + args<<"mdir"<<"-a"<<drive; + + //kdDebug(7101)<<"Floppy::freeSpace(): create m_mtool"<<endl; + m_mtool=new Program(args); + + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mdir"); + return -1; + } + + + clearBuffers(); + + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + //kdDebug(7101)<<"Floppy::freeSpace(): delete m_mtool"<<endl; + delete m_mtool; + m_mtool=0; + //now mdir has finished + //let's parse the output + terminateBuffers(); + + if (errorOccured) + { + return -1; + } + + if (m_stdoutSize==0) + { + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return -1; + } + + kdDebug(7101)<<"Floppy::freeSpace(): parse stuff"<<endl; + QString outputString(m_stdoutBuffer); + QTextIStream output(&outputString); + QString line; + int lineNumber(0); + while (!output.atEnd()) + { + line=output.readLine(); + if (line.find("bytes free")==36) + { + QString tmp=line.mid(24,3); + tmp=tmp.stripWhiteSpace(); + tmp+=line.mid(28,3); + tmp=tmp.stripWhiteSpace(); + tmp+=line.mid(32,3); + tmp=tmp.stripWhiteSpace(); + + return tmp.toInt(); + } + lineNumber++; + } + return -1; +} + +void FloppyProtocol::stat( const KURL & _url) +{ + kdDebug(7101)<<"Floppy::stat() "<<_url.path()<<endl; + KURL url(_url); + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + url.setPath("/a/"); + redirection(url); + finished(); + return; + } + StatInfo info=this->_stat(url); + if (info.isValid) + { + UDSEntry entry; + createUDSEntry(info,entry); + statEntry( entry ); + finished(); + //kdDebug(7101)<<"Floppy::stat(): ends"<<endl; + return; + } + //otherwise the error() was already reported in _stat() +} + +void FloppyProtocol::mkdir( const KURL& url, int) +{ + kdDebug(7101)<<"FloppyProtocol::mkdir()"<<endl; + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + + args<<"mmd"<<(drive+floppyPath); + kdDebug(7101)<<"Floppy::mkdir(): executing: mmd -"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mmd"); + return; + } + + + clearBuffers(); + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + terminateBuffers(); + if (errorOccured) + return; + finished(); +} + +void FloppyProtocol::del( const KURL& url, bool isfile) +{ + kdDebug(7101)<<"FloppyProtocol::del()"<<endl; + QString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + + bool usingmdel; + + if (isfile) + { + args<<"mdel"<<(drive+floppyPath); + usingmdel=true; + } + else + { + args<<"mrd"<<(drive+floppyPath); + usingmdel=false; + } + + kdDebug(7101)<<"Floppy::del(): executing: " << (usingmdel ? QString("mdel") : QString("mrd") ) << "-"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram(usingmdel ? QString("mdel") : QString("mrd")); + return; + } + + + clearBuffers(); + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + terminateBuffers(); + if (errorOccured) + return; + finished(); +} + +void FloppyProtocol::rename( const KURL &src, const KURL &dest, bool _overwrite ) +{ + QString srcPath(src.path()); + QString destPath(dest.path()); + + kdDebug(7101)<<"Floppy::rename() -"<<srcPath<<"- to -"<<destPath<<"-"<<endl; + + if ((srcPath.isEmpty()) || (srcPath=="/")) + srcPath="/a/"; + + if ((destPath.isEmpty()) || (destPath=="/")) + destPath="/a/"; + + QString srcDrive; + QString srcFloppyPath; + getDriveAndPath(srcPath,srcDrive,srcFloppyPath); + if (srcFloppyPath.isEmpty()) + { + finished(); + return; + } + + QString destDrive; + QString destFloppyPath; + getDriveAndPath(destPath,destDrive,destFloppyPath); + if (destFloppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + + if (_overwrite) + args<<"mren"<<"-o"<<(srcDrive+srcFloppyPath)<<(destDrive+destFloppyPath); + else + args<<"mren"<<"-D"<<"s"<<(srcDrive+srcFloppyPath)<<(destDrive+destFloppyPath); + + kdDebug(7101)<<"Floppy::move(): executing: mren -"<<(srcDrive+srcFloppyPath)<<" "<<(destDrive+destFloppyPath)<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mren"); + return; + } + + + clearBuffers(); + int result; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + if (readStdout()==0) + loopFinished=true; + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(src,srcDrive)) + { + loopFinished=true; + errorOccured=true; + } + } + } while (!loopFinished); + + delete m_mtool; + m_mtool=0; + terminateBuffers(); + if (errorOccured) + return; + finished(); +} + +void FloppyProtocol::get( const KURL& url ) +{ + QString path(url.path()); + kdDebug(7101)<<"Floppy::get() -"<<path<<"-"<<endl; + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + StatInfo info=this->_stat(url); + //the error was already reported in _stat() + if (info.isValid==false) + return; + + totalSize( info.size); + + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + args<<"mcopy"<<(drive+floppyPath)<<"-"; + + kdDebug(7101)<<"Floppy::get(): executing: mcopy -"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mcopy"); + return; + } + + clearBuffers(); + int result; + int bytesRead(0); + QByteArray array; + bool loopFinished(false); + bool errorOccured(false); + do + { + bool stdoutEvent; + bool stderrEvent; + result=m_mtool->select(1,0,stdoutEvent, stderrEvent); + if (stdoutEvent) + { + delete [] m_stdoutBuffer; + m_stdoutBuffer=0; + m_stdoutSize=0; + if (readStdout()>0) + { + kdDebug(7101)<<"Floppy::get(): m_stdoutSize:"<<m_stdoutSize<<endl; + bytesRead+=m_stdoutSize; + array.setRawData(m_stdoutBuffer, m_stdoutSize); + data( array ); + array.resetRawData(m_stdoutBuffer, m_stdoutSize); + + } + else + { + loopFinished=true; + } + } + if (stderrEvent) + { + if (readStderr()==0) + loopFinished=true; + else + if (stopAfterError(url,drive)) + { + errorOccured=true; + loopFinished=true; + } + } + } while (!loopFinished); + + //kdDebug(7101)<<"Floppy::get(): deleting m_mtool"<<endl; + delete m_mtool; + m_mtool=0; + if (errorOccured) + return; + + //kdDebug(7101)<<"Floppy::get(): finishing"<<endl; + data( QByteArray() ); + finished(); +} + +void FloppyProtocol::put( const KURL& url, int , bool overwrite, bool ) +{ + QString path(url.path()); + kdDebug(7101)<<"Floppy::put() -"<<path<<"-"<<endl; + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + QString drive; + QString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + int freeSpaceLeft=freeSpace(url); + if (freeSpaceLeft==-1) + return; + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + QStringList args; + if (overwrite) + args<<"mcopy"<<"-o"<<"-"<<(drive+floppyPath); + else + args<<"mcopy"<<"-s"<<"-"<<(drive+floppyPath); + + kdDebug(7101)<<"Floppy::put(): executing: mcopy -"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram("mcopy"); + return; + } + + + clearBuffers(); + int result(0); + int bytesRead(0); + QByteArray array; + + //from file.cc + // Loop until we got 0 (end of data) + do + { + bool stdoutEvent; + bool stderrEvent; + kdDebug(7101)<<"Floppy::put(): select()..."<<endl; + m_mtool->select(0,100,stdoutEvent, stderrEvent); + if (stdoutEvent) + { + if (readStdout()==0) + result=0; + } + if (stderrEvent) + { + if (readStderr()==0) + result=0; + else + if (stopAfterError(url,drive)) + result=-1; + kdDebug(7101)<<"Floppy::put(): error: result=="<<result<<endl; + } + else + { + QByteArray buffer; + dataReq(); // Request for data + //kdDebug(7101)<<"Floppy::put(): after dataReq()"<<endl; + result = readData( buffer ); + //kdDebug(7101)<<"Floppy::put(): after readData(), read "<<result<<" bytes"<<endl; + if (result > 0) + { + bytesRead+=result; + kdDebug(7101)<<"Floppy::put() bytesRead: "<<bytesRead<<" space: "<<freeSpaceLeft<<endl; + if (bytesRead>freeSpaceLeft) + { + result=0; + error( KIO::ERR_SLAVE_DEFINED, i18n("Could not write to file %1.\nThe disk in drive %2 is probably full.").arg(url.prettyURL(),drive)); + } + else + { + //kdDebug(7101)<<"Floppy::put(): writing..."<<endl; + result=::write(m_mtool->stdinFD(),buffer.data(), buffer.size()); + kdDebug(7101)<<"Floppy::put(): after write(), wrote "<<result<<" bytes"<<endl; + } + } + } + } + while ( result > 0 ); + + if (result<0) + { + perror("writing to stdin"); + error( KIO::ERR_CANNOT_OPEN_FOR_WRITING, url.prettyURL()); + return; + } + + delete m_mtool; + m_mtool=0; + + finished(); +} + diff --git a/kioslave/floppy/kio_floppy.h b/kioslave/floppy/kio_floppy.h new file mode 100644 index 000000000..bfc003b9f --- /dev/null +++ b/kioslave/floppy/kio_floppy.h @@ -0,0 +1,78 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef KIO_FLOPPY_H +#define KIO_FLOPPY_H + +#include <kio/slavebase.h> +#include <kio/global.h> + +#include "program.h" + +#include <qstring.h> + +struct StatInfo +{ + StatInfo():name(""),time(0),size(0),mode(0),freeSpace(0),isDir(false),isValid(false) {;} + QString name; + time_t time; + int size; + int mode; + int freeSpace; + bool isDir:1; + bool isValid:1; +}; + + +class FloppyProtocol : public KIO::SlaveBase +{ + public: + FloppyProtocol (const QCString &pool, const QCString &app ); + virtual ~FloppyProtocol(); + + virtual void listDir( const KURL& url); + virtual void stat( const KURL & url); + virtual void mkdir( const KURL& url, int); + virtual void del( const KURL& url, bool isfile); + virtual void rename(const KURL &src, const KURL &dest, bool overwrite); + virtual void get( const KURL& url ); + virtual void put( const KURL& url, int _mode,bool overwrite, bool _resume ); + //virtual void copy( const KURL& src, const KURL &dest, int, bool overwrite ); + protected: + Program *m_mtool; + int readStdout(); + int readStderr(); + + StatInfo createStatInfo(const QString line, bool makeStat=false, const QString& dirName=""); + void createUDSEntry(const StatInfo& info, KIO::UDSEntry& entry); + StatInfo _stat(const KURL& _url); + int freeSpace(const KURL& url); + + bool stopAfterError(const KURL& url, const QString& drive); + void errorMissingMToolsProgram(const QString& name); + + void clearBuffers(); + void terminateBuffers(); + char *m_stdoutBuffer; + char *m_stderrBuffer; + int m_stdoutSize; + int m_stderrSize; +}; + +#endif diff --git a/kioslave/floppy/program.cpp b/kioslave/floppy/program.cpp new file mode 100644 index 000000000..7cfd4989a --- /dev/null +++ b/kioslave/floppy/program.cpp @@ -0,0 +1,201 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <config.h> +#include "program.h" +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <signal.h> + +#include <kdebug.h> + +Program::Program(const QStringList &args) +:m_pid(0) +,mArgs(args) +,mStarted(false) +{ +} + +Program::~Program() +{ + if (m_pid!=0) + { + ::close(mStdin[0]); + ::close(mStdout[0]); + ::close(mStderr[0]); + + ::close(mStdin[1]); + ::close(mStdout[1]); + ::close(mStderr[1]); + + int s(0); + //::wait(&s); + ::waitpid(m_pid,&s,0); + this->kill(); + ::waitpid(m_pid,&s,WNOHANG); + }; +} + +bool Program::start() +{ + if (mStarted) return false; + if (pipe(mStdout)==-1) return false; + if (pipe(mStdin )==-1) return false; + if (pipe(mStderr )==-1) return false; + + int notificationPipe[2]; + if (pipe(notificationPipe )==-1) return false; + + m_pid=fork(); + + if (m_pid>0) + { + //parent + ::close(mStdin[0]); + ::close(mStdout[1]); + ::close(mStderr[1]); + ::close(notificationPipe[1]); + mStarted=true; + fd_set notifSet; + FD_ZERO(¬ifSet); + FD_SET(notificationPipe[0],¬ifSet); + struct timeval tv; + //wait up to five seconds + + kdDebug(7101)<<"**** waiting for notification"<<endl; + //0.2 sec + tv.tv_sec=0; + tv.tv_usec=1000*200; + int result=::select(notificationPipe[0]+1,¬ifSet,0,0,&tv); +/* if (result<1) + { + kdDebug(7101)<<"**** waiting for notification: failed "<<result<<endl; + return false; + } + else*/ + if(result==1) + { + char buf[256]; + result=::read(notificationPipe[0],buf,256); + //if execvp() failed the child sends us "failed" + if (result>0) + return false; + }; + kdDebug(7101)<<"**** waiting for notification: succeeded"<<result<<endl; + return true; + } + else if (m_pid==-1) + { + //failed + return false; + } + else if (m_pid==0) + { + ::close(notificationPipe[0]); + + //child + ::close(0); // close the stdios + ::close(1); + ::close(2); + + dup(mStdin[0]); + dup(mStdout[1]); + dup(mStderr[1]); + + ::close(mStdin[1]); + ::close(mStdout[0]); + ::close(mStderr[0]); + + fcntl(mStdin[0], F_SETFD, FD_CLOEXEC); + fcntl(mStdout[1], F_SETFD, FD_CLOEXEC); + fcntl(mStderr[1], F_SETFD, FD_CLOEXEC); + + char **arglist=(char**)malloc((mArgs.count()+1)*sizeof(char*)); + int c=0; + + for (QStringList::Iterator it=mArgs.begin(); it!=mArgs.end(); ++it) + { + arglist[c]=(char*)malloc((*it).length()+1); + strcpy(arglist[c], (*it).latin1()); + c++; + } + arglist[mArgs.count()]=0; + //make parsing easier + putenv(strdup("LANG=C")); + execvp(arglist[0], arglist); + //we only get here if execvp() failed + ::write(notificationPipe[1],"failed",strlen("failed")); + ::close(notificationPipe[1]); + _exit(-1); + }; + return false; +} + +bool Program::isRunning() +{ + return mStarted; +} + +int Program::select(int secs, int usecs, bool& stdoutReceived, bool& stderrReceived/*, bool& stdinWaiting*/) +{ + stdoutReceived=false; + stderrReceived=false; + + struct timeval tv; + tv.tv_sec=secs; + tv.tv_usec=usecs; + + fd_set readFDs; + FD_ZERO(&readFDs); + FD_SET(stdoutFD(),&readFDs); + FD_SET(stderrFD(),&readFDs); + + int maxFD=stdoutFD(); + if (stderrFD()>maxFD) maxFD=stderrFD(); + + /*fd_set writeFDs; + FD_ZERO(&writeFDs); + FD_SET(stdinFD(),&writeFDs); + if (stdinFD()>maxFD) maxFD=stdinFD();*/ + maxFD++; + + int result=::select(maxFD,&readFDs,/*&writeFDs*/0,0,&tv); + if (result>0) + { + stdoutReceived=FD_ISSET(stdoutFD(),&readFDs); + stderrReceived=FD_ISSET(stderrFD(),&readFDs); + //stdinWaiting=(FD_ISSET(stdinFD(),&writeFDs)); + }; + return result; +} + +int Program::kill() +{ + if (m_pid==0) + return -1; + return ::kill(m_pid, SIGTERM); + //::kill(m_pid, SIGKILL); +} + diff --git a/kioslave/floppy/program.h b/kioslave/floppy/program.h new file mode 100644 index 000000000..29ea634ad --- /dev/null +++ b/kioslave/floppy/program.h @@ -0,0 +1,53 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef PROGRAM_H +#define PROGRAM_H + +#include <qstringlist.h> + +/** + * start programs and write to thieir stdin, stderr, + * and read from stdout + **/ +class Program +{ +public: + Program(const QStringList &args); + ~Program(); + bool start(); + bool isRunning(); + + int stdinFD() {return mStdin[1];} + int stdoutFD() {return mStdout[0];} + int stderrFD() {return mStderr[0];} + int pid() {return m_pid;} + int kill(); + int select(int secs, int usecs, bool& stdoutReceived, bool& stderrReceived/*, bool& stdinWaiting*/); +protected: + int mStdout[2]; + int mStdin[2]; + int mStderr[2]; + int m_pid; + QStringList mArgs; + bool mStarted; +}; + +#endif + diff --git a/kioslave/home/Makefile.am b/kioslave/home/Makefile.am new file mode 100644 index 000000000..be10e5bcc --- /dev/null +++ b/kioslave/home/Makefile.am @@ -0,0 +1,32 @@ +SUBDIRS= . kdedmodule +# wizard + +INCLUDES = $(all_includes) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kio_home.la + +kio_home_la_SOURCES = dummy.cpp +kio_home_la_LIBADD = libkiohome.la $(LIB_KIO) +kio_home_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined + +dummy.cpp: + echo > dummy.cpp + +kde_services_DATA = home.protocol + +noinst_LTLIBRARIES = libkiohome.la +libkiohome_la_SOURCES = kio_home.cpp homeimpl.cpp + +check_PROGRAMS = testhome +testhome_SOURCES = testhome.cpp +testhome_LDADD = libkiohome.la $(LIB_KIO) +testhome_LDFLAGS = $(all_libraries) + +## TODO in unsermake: TESTS = testhome +check: testhome + ./testhome + +messages: + $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/kio_home.pot + diff --git a/kioslave/home/dummy.cpp b/kioslave/home/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/kioslave/home/dummy.cpp @@ -0,0 +1 @@ + diff --git a/kioslave/home/home.protocol b/kioslave/home/home.protocol new file mode 100644 index 000000000..59f6cba5d --- /dev/null +++ b/kioslave/home/home.protocol @@ -0,0 +1,19 @@ +[Protocol] +exec=kio_home +protocol=home +input=none +output=filesystem +listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +Icon=folder_home +maxInstances=4 +#TODO DocPath=kioslave/file.html +Class=:local +Parent=system:/ +deleteRecursive=true +fileNameUsedForCopying=Name diff --git a/kioslave/home/homeimpl.cpp b/kioslave/home/homeimpl.cpp new file mode 100644 index 000000000..7e86173ba --- /dev/null +++ b/kioslave/home/homeimpl.cpp @@ -0,0 +1,228 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "homeimpl.h" + +#include <kdebug.h> +#include <qapplication.h> +#include <qeventloop.h> + +#include <sys/stat.h> + +#define MINIMUM_UID 500 + +HomeImpl::HomeImpl() +{ + KUser user; + m_effectiveUid = user.uid(); +} + +bool HomeImpl::parseURL(const KURL &url, QString &name, QString &path) const +{ + QString url_path = url.path(); + + int i = url_path.find('/', 1); + if (i > 0) + { + name = url_path.mid(1, i-1); + path = url_path.mid(i+1); + } + else + { + name = url_path.mid(1); + path = QString::null; + } + + return name != QString::null; +} + +bool HomeImpl::realURL(const QString &name, const QString &path, KURL &url) +{ + KUser user(name); + + if ( user.isValid() ) + { + KURL res; + res.setPath( user.homeDir() ); + res.addPath(path); + url = res; + return true; + } + + return false; +} + + +bool HomeImpl::listHomes(QValueList<KIO::UDSEntry> &list) +{ + kdDebug() << "HomeImpl::listHomes" << endl; + + KUser current_user; + QValueList<KUserGroup> groups = current_user.groups(); + QValueList<int> uid_list; + + QValueList<KUserGroup>::iterator groups_it = groups.begin(); + QValueList<KUserGroup>::iterator groups_end = groups.end(); + + for(; groups_it!=groups_end; ++groups_it) + { + QValueList<KUser> users = (*groups_it).users(); + + QValueList<KUser>::iterator it = users.begin(); + QValueList<KUser>::iterator users_end = users.end(); + + for(; it!=users_end; ++it) + { + if ((*it).uid()>=MINIMUM_UID + && !uid_list.contains( (*it).uid() ) ) + { + uid_list.append( (*it).uid() ); + KIO::UDSEntry entry; + createHomeEntry(entry, *it); + list.append(entry); + } + } + } + + return true; +} + +static void addAtom(KIO::UDSEntry &entry, unsigned int ID, long l, + const QString &s = QString::null) +{ + KIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void HomeImpl::createTopLevelEntry(KIO::UDSEntry &entry) const +{ + entry.clear(); + addAtom(entry, KIO::UDS_NAME, 0, "."); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_ACCESS, 0555); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, KIO::UDS_ICON_NAME, 0, "kfm_home"); + addAtom(entry, KIO::UDS_USER, 0, "root"); + addAtom(entry, KIO::UDS_GROUP, 0, "root"); +} + +void HomeImpl::createHomeEntry(KIO::UDSEntry &entry, + const KUser &user) +{ + kdDebug() << "HomeImpl::createHomeEntry" << endl; + + entry.clear(); + + QString full_name = user.loginName(); + + if (!user.fullName().isEmpty()) + { + full_name = user.fullName()+" ("+user.loginName()+")"; + } + + full_name = KIO::encodeFileName( full_name ); + + addAtom(entry, KIO::UDS_NAME, 0, full_name); + addAtom(entry, KIO::UDS_URL, 0, "home:/"+user.loginName()); + + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + + QString icon_name = "folder_home2"; + + if (user.uid()==m_effectiveUid) + { + icon_name = "folder_home"; + } + + addAtom(entry, KIO::UDS_ICON_NAME, 0, icon_name); + + KURL url; + url.setPath(user.homeDir()); + entry += extractUrlInfos(url); +} + +bool HomeImpl::statHome(const QString &name, KIO::UDSEntry &entry) +{ + kdDebug() << "HomeImpl::statHome: " << name << endl; + + KUser user(name); + + if (user.isValid()) + { + createHomeEntry(entry, user); + return true; + } + + return false; +} + +void HomeImpl::slotStatResult(KIO::Job *job) +{ + if ( job->error() == 0) + { + KIO::StatJob *stat_job = static_cast<KIO::StatJob *>(job); + m_entryBuffer = stat_job->statResult(); + } + + qApp->eventLoop()->exitLoop(); +} + +KIO::UDSEntry HomeImpl::extractUrlInfos(const KURL &url) +{ + m_entryBuffer.clear(); + + KIO::StatJob *job = KIO::stat(url, false); + connect( job, SIGNAL( result(KIO::Job *) ), + this, SLOT( slotStatResult(KIO::Job *) ) ); + qApp->eventLoop()->enterLoop(); + + KIO::UDSEntry::iterator it = m_entryBuffer.begin(); + KIO::UDSEntry::iterator end = m_entryBuffer.end(); + + KIO::UDSEntry infos; + + for(; it!=end; ++it) + { + switch( (*it).m_uds ) + { + case KIO::UDS_ACCESS: + case KIO::UDS_USER: + case KIO::UDS_GROUP: + case KIO::UDS_CREATION_TIME: + case KIO::UDS_MODIFICATION_TIME: + case KIO::UDS_ACCESS_TIME: + infos.append(*it); + break; + default: + break; + } + } + + addAtom(infos, KIO::UDS_LOCAL_PATH, 0, url.path()); + + return infos; +} + +#include "homeimpl.moc" + diff --git a/kioslave/home/homeimpl.h b/kioslave/home/homeimpl.h new file mode 100644 index 000000000..8c4ace279 --- /dev/null +++ b/kioslave/home/homeimpl.h @@ -0,0 +1,57 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef HOMEIMPL_H +#define HOMEIMPL_H + +#include <kio/global.h> +#include <kio/job.h> +#include <kurl.h> +#include <kuser.h> + +#include <qstring.h> + +class HomeImpl : public QObject +{ +Q_OBJECT + +public: + HomeImpl(); + bool parseURL(const KURL &url, QString &name, QString &path) const; + bool realURL(const QString &name, const QString &path, KURL &url); + + bool statHome(const QString &name, KIO::UDSEntry &entry); + bool listHomes(QValueList<KIO::UDSEntry> &list); + + void createTopLevelEntry(KIO::UDSEntry &entry) const; + +private slots: + void slotStatResult(KIO::Job *job); + +private: + void createHomeEntry(KIO::UDSEntry& entry, const KUser &user); + + KIO::UDSEntry extractUrlInfos(const KURL &url); + KIO::UDSEntry m_entryBuffer; + + + long m_effectiveUid; +}; + +#endif diff --git a/kioslave/home/kdedmodule/Makefile.am b/kioslave/home/kdedmodule/Makefile.am new file mode 100644 index 000000000..3d7a54f9b --- /dev/null +++ b/kioslave/home/kdedmodule/Makefile.am @@ -0,0 +1,13 @@ +kde_module_LTLIBRARIES = kded_homedirnotify.la + +METASOURCES = AUTO +INCLUDES = $(all_includes) + +kded_homedirnotify_la_SOURCES = homedirnotify.cpp homedirnotify.skel homedirnotifymodule.cpp homedirnotifymodule.skel +kded_homedirnotify_la_LDFLAGS = $(all_libraries) -module -avoid-version +kded_homedirnotify_la_LIBADD = $(LIB_KSYCOCA) + + +servicesdir = $(kde_servicesdir)/kded +services_DATA = homedirnotify.desktop + diff --git a/kioslave/home/kdedmodule/homedirnotify.cpp b/kioslave/home/kdedmodule/homedirnotify.cpp new file mode 100644 index 000000000..e4eab44bb --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotify.cpp @@ -0,0 +1,185 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "homedirnotify.h" + +#include <kdebug.h> +#include <kuser.h> + +#include <kdirnotify_stub.h> + +#define MINIMUM_UID 500 + +HomeDirNotify::HomeDirNotify() +: mInited( false ) +{ +} + +void HomeDirNotify::init() +{ + if( mInited ) + return; + mInited = true; + + KUser current_user; + QValueList<KUserGroup> groups = current_user.groups(); + QValueList<int> uid_list; + + QValueList<KUserGroup>::iterator groups_it = groups.begin(); + QValueList<KUserGroup>::iterator groups_end = groups.end(); + + for(; groups_it!=groups_end; ++groups_it) + { + QValueList<KUser> users = (*groups_it).users(); + + QValueList<KUser>::iterator it = users.begin(); + QValueList<KUser>::iterator users_end = users.end(); + + for(; it!=users_end; ++it) + { + if ((*it).uid()>=MINIMUM_UID + && !uid_list.contains( (*it).uid() ) ) + { + uid_list.append( (*it).uid() ); + + QString name = (*it).loginName(); + KURL url; + url.setPath( (*it).homeDir() ); + + m_homeFoldersMap[name] = url; + } + } + } +} + +KURL HomeDirNotify::toHomeURL(const KURL &url) +{ + kdDebug() << "HomeDirNotify::toHomeURL(" << url << ")" << endl; + + init(); + QMap<QString,KURL>::iterator it = m_homeFoldersMap.begin(); + QMap<QString,KURL>::iterator end = m_homeFoldersMap.end(); + + for (; it!=end; ++it) + { + QString name = it.key(); + KURL base = it.data(); + + if ( base.isParentOf(url) ) + { + QString path = KURL::relativePath(base.path(), + url.path()); + KURL result("home:/"+name+"/"+path); + result.cleanPath(); + kdDebug() << "result => " << result << endl; + return result; + } + } + + kdDebug() << "result => KURL()" << endl; + return KURL(); +} + +KURL::List HomeDirNotify::toHomeURLList(const KURL::List &list) +{ + init(); + KURL::List new_list; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL url = toHomeURL(*it); + + if (url.isValid()) + { + new_list.append(url); + } + } + + return new_list; +} + +ASYNC HomeDirNotify::FilesAdded(const KURL &directory) +{ + kdDebug() << "HomeDirNotify::FilesAdded" << endl; + + KURL new_dir = toHomeURL(directory); + + if (new_dir.isValid()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesAdded( new_dir ); + } +} + +// This hack is required because of the way we manage .desktop files with +// Forwarding Slaves, their URL is out of the ioslave (some home:/ files +// have a file:/ based UDS_URL so that they are executed correctly. +// Hence, FilesRemoved and FilesChanged does nothing... We're forced to use +// FilesAdded to re-list the modified directory. +inline void evil_hack(const KURL::List &list) +{ + KDirNotify_stub notifier("*", "*"); + + KURL::List notified; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL url = (*it).upURL(); + + if (!notified.contains(url)) + { + notifier.FilesAdded(url); + notified.append(url); + } + } +} + + +ASYNC HomeDirNotify::FilesRemoved(const KURL::List &fileList) +{ + kdDebug() << "HomeDirNotify::FilesRemoved" << endl; + + KURL::List new_list = toHomeURLList(fileList); + + if (!new_list.isEmpty()) + { + //KDirNotify_stub notifier("*", "*"); + //notifier.FilesRemoved( new_list ); + evil_hack(new_list); + } +} + +ASYNC HomeDirNotify::FilesChanged(const KURL::List &fileList) +{ + kdDebug() << "HomeDirNotify::FilesChanged" << endl; + + KURL::List new_list = toHomeURLList(fileList); + + if (!new_list.isEmpty()) + { + //KDirNotify_stub notifier("*", "*"); + //notifier.FilesChanged( new_list ); + evil_hack(new_list); + } +} diff --git a/kioslave/home/kdedmodule/homedirnotify.desktop b/kioslave/home/kdedmodule/homedirnotify.desktop new file mode 100644 index 000000000..c931748d1 --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotify.desktop @@ -0,0 +1,60 @@ +[Desktop Entry] +Type=Service +Name=KDED Home Base URL Notifier +Name[af]=KDED tuis URL inkennissteller +Name[be]=Праверка зменаў мÑÑцовых файлаў KDED +Name[bs]=KDED lokalno obavjeÅ¡tenje o baznom URLu +Name[ca]=Notificador KDED de l'URL d'inici +Name[cs]=Démon upozorňovánà na domovské URL +Name[csb]=Dôwanié wiédzë o URL-ach domôcegò katalogù dlô KDED +Name[da]=KDED Hjemmebasis-URL pÃ¥mindelser +Name[de]=Ãœberwachung für persönliche Ordner +Name[el]=KDED ειδοποιητής URL αÏχικής βάσης +Name[eo]=KDED Hejmo Bazo URL Avertilo +Name[es]=Notificador de URL de KDED +Name[et]=KDED kodu baas-URLi teadustaja +Name[eu]=KDED hasierako URL oinarriaren iragarlea +Name[fa]=اخطاردهندۀ نشانی وب پایۀ آغازۀ KDED +Name[fi]=KDED:in etä-verkko-osoitteen ilmaisin +Name[fr]=Notification de l'URL de base KDED +Name[fy]=KDED Thúsbasis-URL-adres notifikaasje +Name[gl]=Notificador de URL base de KDED +Name[hr]=KDED URL obavjeÅ¡tavanje domaće baze +Name[hu]=ÉrtesÃtÅ‘ a KDE saját URL-hez +Name[is]=KDED grunnslóðar tilkynnari +Name[it]=Notifica KDED Home Base URL +Name[ja]=KDED ホームベース URL Nofitier +Name[ka]=KDED ძირითáƒáƒ“ი თáƒáƒ•áƒ¤áƒ£áƒ ცლის URL შემტყáƒáƒ‘ინებელი +Name[kk]=KDED Home Base URL құлақтандыру +Name[km]=KDED Remote Base URL Notifier +Name[ko]=KDED ì›ê²© 기반 URL 알리미 +Name[lt]=KDED pagrindinio namų URL priminiklis +Name[nb]=KDED-pÃ¥minner for eksterne nettadresser +Name[nds]=KDED-Narichten för Tohuusorner-URLs +Name[ne]=KDED गृह आधारित यूआरà¤à¤² सूचक +Name[nl]=KDED Thuisbasis-URL-adres notificatie +Name[nn]=KDED-varsel for heimebase +Name[pa]=KDED ਮà©à©±à¨– ਅਧਾਰ URL ਸੂਚਕ +Name[pl]=Powiadamianie o URL-ach katalogu domowego dla KDED +Name[pt]=Notificador de URLs de Base Remotos do KDED +Name[pt_BR]=Serviço de Notificação da URL do KDED +Name[ro]=Notificare KDED pentru URL acasă +Name[ru]=Уведомление о Ñмене базового адреÑа KDED +Name[sk]=KDED notifikátor domovskej URL +Name[sl]=Obvestilnik KDED domaÄega osnovnega URL-ja +Name[sr]=Обавештавач о домаћем базном URL-у, KDED +Name[sr@Latn]=ObaveÅ¡tavaÄ o domaćem baznom URL-u, KDED +Name[sv]=KDED-meddelande om hembaswebbadresser +Name[th]=ตัวà¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™ Home Base URL KDED +Name[tr]=KDED Yerel Tabanlı URL Hatırlatıcı +Name[uk]=Сповіщувач домашньої базової адреÑи URL KDED +Name[vi]=Trình thông báo URL trong máy KDED +Name[wa]=Notifieu di l' URL di bÃ¥ze del mÃ¥jhon KDED +Name[zh_CN]=KDED 主页基 URL 通知器 +Name[zh_TW]=KDED 家用基礎 URL é€šçŸ¥ç¨‹å¼ +ServiceTypes=KDEDModule +X-KDE-ModuleType=Library +X-KDE-Library=homedirnotify +X-KDE-FactoryName=homedirnotify +X-KDE-Kded-load-on-demand=true +X-KDE-Kded-autoload=true diff --git a/kioslave/home/kdedmodule/homedirnotify.h b/kioslave/home/kdedmodule/homedirnotify.h new file mode 100644 index 000000000..14655a1cd --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotify.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef HOMEDIRNOTIFY_H +#define HOMEDIRNOTIFY_H + +#include <kurl.h> +#include <kdirnotify.h> + +#include <qmap.h> + +class HomeDirNotify : public KDirNotify +{ +K_DCOP + +public: + HomeDirNotify(); + +k_dcop: + virtual ASYNC FilesAdded (const KURL &directory); + virtual ASYNC FilesRemoved (const KURL::List &fileList); + virtual ASYNC FilesChanged (const KURL::List &fileList); + +private: + void init(); + KURL toHomeURL(const KURL &url); + KURL::List toHomeURLList(const KURL::List &list); + + QMap<QString,KURL> m_homeFoldersMap; + bool mInited; +}; + +#endif diff --git a/kioslave/home/kdedmodule/homedirnotifymodule.cpp b/kioslave/home/kdedmodule/homedirnotifymodule.cpp new file mode 100644 index 000000000..1b91ccc31 --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotifymodule.cpp @@ -0,0 +1,37 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "homedirnotifymodule.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> + +HomeDirNotifyModule::HomeDirNotifyModule(const QCString &obj) + : KDEDModule(obj) +{ +} + +extern "C" { + KDE_EXPORT KDEDModule *create_homedirnotify(const QCString &obj) + { + KGlobal::locale()->insertCatalogue("kio_home"); + return new HomeDirNotifyModule(obj); + } +} + diff --git a/kioslave/home/kdedmodule/homedirnotifymodule.h b/kioslave/home/kdedmodule/homedirnotifymodule.h new file mode 100644 index 000000000..159670fb6 --- /dev/null +++ b/kioslave/home/kdedmodule/homedirnotifymodule.h @@ -0,0 +1,36 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef HOMEDIRNOTIFYMODULE_H +#define HOMEDIRNOTIFYMODULE_H + +#include <kdedmodule.h> + +#include "homedirnotify.h" + +class HomeDirNotifyModule : public KDEDModule +{ +K_DCOP + +public: + HomeDirNotifyModule(const QCString &obj); +private: + HomeDirNotify notifier; +}; + +#endif diff --git a/kioslave/home/kio_home.cpp b/kioslave/home/kio_home.cpp new file mode 100644 index 000000000..36a3161d8 --- /dev/null +++ b/kioslave/home/kio_home.cpp @@ -0,0 +1,186 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <stdlib.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <kcmdlineargs.h> +#include <kglobal.h> + + +#include "kio_home.h" + +static const KCmdLineOptions options[] = +{ + { "+protocol", I18N_NOOP( "Protocol name" ), 0 }, + { "+pool", I18N_NOOP( "Socket name" ), 0 }, + { "+app", I18N_NOOP( "Socket name" ), 0 }, + KCmdLineLastOption +}; + +extern "C" { + int KDE_EXPORT kdemain( int argc, char **argv ) + { + // KApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + KCmdLineArgs::init(argc, argv, "kio_home", 0, 0, 0, 0); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + HomeProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +HomeProtocol::HomeProtocol(const QCString &protocol, + const QCString &pool, const QCString &app) + : ForwardingSlaveBase(protocol, pool, app) +{ +} + +HomeProtocol::~HomeProtocol() +{ +} + +bool HomeProtocol::rewriteURL(const KURL &url, KURL &newUrl) +{ + QString name, path; + + if ( !m_impl.parseURL(url, name, path) ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return false; + } + + + if ( !m_impl.realURL(name, path, newUrl) ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return false; + } + + return true; +} + + +void HomeProtocol::listDir(const KURL &url) +{ + kdDebug() << "HomeProtocol::listDir: " << url << endl; + + if ( url.path().length() <= 1 ) + { + listRoot(); + return; + } + + QString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + ForwardingSlaveBase::listDir(url); +} + +void HomeProtocol::listRoot() +{ + KIO::UDSEntry entry; + + KIO::UDSEntryList home_entries; + bool ok = m_impl.listHomes(home_entries); + + if (!ok) // can't happen + { + error(KIO::ERR_UNKNOWN, ""); + return; + } + + totalSize(home_entries.count()+1); + + m_impl.createTopLevelEntry(entry); + listEntry(entry, false); + + KIO::UDSEntryListIterator it = home_entries.begin(); + KIO::UDSEntryListIterator end = home_entries.end(); + + for(; it!=end; ++it) + { + listEntry(*it, false); + } + + entry.clear(); + listEntry(entry, true); + + finished(); +} + +void HomeProtocol::stat(const KURL &url) +{ + kdDebug() << "HomeProtocol::stat: " << url << endl; + + QString path = url.path(); + if ( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + KIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + QString name; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if( path.isEmpty() ) + { + KIO::UDSEntry entry; + + if ( m_impl.statHome(name, entry) ) + { + statEntry(entry); + finished(); + } + else + { + error(KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + } + else + { + ForwardingSlaveBase::stat(url); + } +} diff --git a/kioslave/home/kio_home.h b/kioslave/home/kio_home.h new file mode 100644 index 000000000..1d5e237ea --- /dev/null +++ b/kioslave/home/kio_home.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef KIO_HOME_H +#define KIO_HOME_H + +#include <kio/forwardingslavebase.h> +#include "homeimpl.h" + +class HomeProtocol : public KIO::ForwardingSlaveBase +{ +public: + HomeProtocol(const QCString &protocol, const QCString &pool, + const QCString &app); + virtual ~HomeProtocol(); + + virtual bool rewriteURL(const KURL &url, KURL &newUrl); + + virtual void listDir(const KURL &url); + virtual void stat(const KURL &url); + +private: + void listRoot(); + + HomeImpl m_impl; +}; + +#endif diff --git a/kioslave/home/testhome.cpp b/kioslave/home/testhome.cpp new file mode 100644 index 000000000..e9d64ec68 --- /dev/null +++ b/kioslave/home/testhome.cpp @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kio_home.h" +#include "testhome.h" + +#include <config.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#include <stdlib.h> + +static bool check(const QString& txt, QString a, QString b) +{ + if (a.isEmpty()) + a = QString::null; + if (b.isEmpty()) + b = QString::null; + if (a == b) { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +int main(int argc, char *argv[]) +{ + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc,argv,"testhome", 0, 0, 0, 0); + KApplication app; + + TestHome test; + test.setup(); + test.runAll(); + kdDebug() << "All tests OK." << endl; + return 0; // success. The exit(1) in check() is what happens in case of failure. +} + +void TestHome::setup() +{ + +} + +void TestHome::runAll() +{ + +} + diff --git a/kioslave/home/testhome.h b/kioslave/home/testhome.h new file mode 100644 index 000000000..dd8b257e3 --- /dev/null +++ b/kioslave/home/testhome.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef TESTHOME_H +#define TESTHOME_H + +class TestHome +{ +public: + TestHome() {} + void setup(); + void runAll(); + + // tests + +}; + +#endif diff --git a/kioslave/info/LICENSE b/kioslave/info/LICENSE new file mode 100644 index 000000000..9cb70ac77 --- /dev/null +++ b/kioslave/info/LICENSE @@ -0,0 +1,22 @@ +The following license is applicable to all files in this directory, with the +exception of kde-info2html and kde-info2html.conf which are licensed under the GPL, +since they are based on GPL work. + +LICENSE: + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/info/Makefile.am b/kioslave/info/Makefile.am new file mode 100644 index 000000000..8db4a20ec --- /dev/null +++ b/kioslave/info/Makefile.am @@ -0,0 +1,21 @@ +## Makefile.am of kdebase/kioslave/info + +INCLUDES = $(all_includes) + +METASOURCES = AUTO + +####### Files + +kde_module_LTLIBRARIES = kio_info.la + +kio_info_la_SOURCES = info.cc +kio_info_la_LIBADD = $(LIB_KIO) +kio_info_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = info.h + +kdelnk_DATA = info.protocol +kdelnkdir = $(kde_servicesdir) + +kio_info_data_DATA = kde-info2html.conf +kio_info_data_SCRIPTS = kde-info2html +kio_info_datadir = $(kde_datadir)/kio_info diff --git a/kioslave/info/info.cc b/kioslave/info/info.cc new file mode 100644 index 000000000..6b829ec1c --- /dev/null +++ b/kioslave/info/info.cc @@ -0,0 +1,261 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <qdir.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kprocess.h> +#include <kstandarddirs.h> +#include <kiconloader.h> +#include <kinstance.h> +#include <klocale.h> + +#include "info.h" + +using namespace KIO; + +InfoProtocol::InfoProtocol( const QCString &pool, const QCString &app ) + : SlaveBase( "info", pool, app ) + , m_page( "" ) + , m_node( "" ) +{ + kdDebug( 7108 ) << "InfoProtocol::InfoProtocol" << endl; + + m_perl = KGlobal::dirs()->findExe( "perl" ); + m_infoScript = locate( "data", "kio_info/kde-info2html" ); + m_infoConf = locate("data", "kio_info/kde-info2html.conf"); + + if( m_perl.isNull() || m_infoScript.isNull() || m_infoConf.isNull() ) { + kdError( 7108 ) << "Critical error: Cannot locate files for HTML-conversion" << endl; + QString errorStr; + if ( m_perl.isNull() ) { + errorStr = "perl."; + } else { + QString missing =m_infoScript.isNull() ? "kio_info/kde-info2html" : "kio_info/kde-info2html.conf"; + errorStr = "kde-info2html" + i18n( "\nUnable to locate file %1 which is necessary to run this service. " + "Please check your software installation" ).arg( missing ); + } + error( KIO::ERR_CANNOT_LAUNCH_PROCESS, errorStr ); + exit(); + } + + kdDebug( 7108 ) << "InfoProtocol::InfoProtocol - done" << endl; +} + +InfoProtocol::~InfoProtocol() +{ + kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol" << endl; + + kdDebug( 7108 ) << "InfoProtocol::~InfoProtocol - done" << endl; +} + +void InfoProtocol::get( const KURL& url ) +{ + kdDebug( 7108 ) << "InfoProtocol::get" << endl; + kdDebug( 7108 ) << "URL: " << url.prettyURL() << " , Path :" << url.path() << endl; + + if (url.path()=="/") + { + KURL newUrl("info:/dir"); + redirection(newUrl); + finished(); + return; + }; + + // some people write info://autoconf instead of info:/autoconf + if (!url.host().isEmpty()) { + KURL newURl(url); + newURl.setPath(url.host()+url.path()); + newURl.setHost(QString::null); + redirection(newURl); + finished(); + return; + } + + if ( url.path().right(1) == "/" ) + { + // Trailing / are not supported, so we need to remove them. + KURL newUrl( url ); + QString newPath( url.path() ); + newPath.truncate( newPath.length()-1 ); + newUrl.setPath( newPath ); + redirection( newUrl ); + finished(); + return; + } + + mimeType("text/html"); + // extract the path and node from url + decodeURL( url ); + + QString path = KGlobal::iconLoader()->iconPath("up", KIcon::Toolbar, true); + int revindex = path.findRev('/'); + path = path.left(revindex); + + QString cmd = KProcess::quote(m_perl); + cmd += " "; + cmd += KProcess::quote(m_infoScript); + cmd += " "; + cmd += KProcess::quote(m_infoConf); + cmd += " "; + cmd += KProcess::quote(path); + cmd += " "; + cmd += KProcess::quote(m_page); + cmd += " "; + cmd += KProcess::quote(m_node); + + kdDebug( 7108 ) << "cmd: " << cmd << endl; + + FILE *file = popen( QFile::encodeName(cmd), "r" ); + if ( !file ) { + kdDebug( 7108 ) << "InfoProtocol::get popen failed" << endl; + error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); + return; + } + + char buffer[ 4096 ]; + QByteArray array; + + bool empty = true; + while ( !feof( file ) ) + { + int n = fread( buffer, 1, sizeof( buffer ), file ); + if ( !n && feof( file ) && empty ) { + error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); + return; + } + if ( n < 0 ) + { + // ERROR + kdDebug( 7108 ) << "InfoProtocol::get ERROR!" << endl; + pclose( file ); + return; + } + + empty = false; + array.setRawData( buffer, n ); + data( array ); + array.resetRawData( buffer, n ); + } + + pclose( file ); + + finished(); + + kdDebug( 7108 ) << "InfoProtocol::get - done" << endl; +} + +void InfoProtocol::mimetype( const KURL& /* url */ ) +{ + kdDebug( 7108 ) << "InfoProtocol::mimetype" << endl; + + // to get rid of those "Open with" dialogs... + mimeType( "text/html" ); + + // finish action + finished(); + + kdDebug( 7108 ) << "InfoProtocol::mimetype - done" << endl; +} + +void InfoProtocol::decodeURL( const KURL &url ) +{ + kdDebug( 7108 ) << "InfoProtocol::decodeURL" << endl; + + /* Notes: + * + * I cleaned up the URL decoding and chose not to support URLs in the + * form "info:/usr/local/share/info/libc.info.gz" or similar which the + * older code attempted (and failed, maybe it had worked once) to do. + * + * The reason is that an obvious use such as viewing a info file off your + * infopath would work for the first page, but then all the links would be + * wrong. Of course, one could change kde-info2html to make it work, but I don't + * think it worthy, others are free to disagree and write the necessary code ;) + * + * luis pedro + */ + + if ( url == KURL( "info:/browse_by_file?special=yes" ) ) { + m_page = "#special#"; + m_node = "browse_by_file"; + kdDebug( 7108 ) << "InfoProtocol::decodeURL - special - browse by file" << endl; + return; + } + + decodePath( url.path() ); + + kdDebug( 7108 ) << "InfoProtocol::decodeURL - done" << endl; +} + +void InfoProtocol::decodePath( QString path ) +{ + kdDebug( 7108 ) << "InfoProtocol::decodePath(-" <<path<<"-)"<< endl; + + m_page = "dir"; //default + m_node = ""; + + // remove leading slash + if ('/' == path[0]) { + path = path.mid( 1 ); + } + //kdDebug( 7108 ) << "Path: " << path << endl; + + int slashPos = path.find( "/" ); + + if( slashPos < 0 ) + { + m_page = path; + m_node = "Top"; + return; + } + + m_page = path.left( slashPos ); + + // remove leading+trailing whitespace + m_node = path.right( path.length() - slashPos - 1).stripWhiteSpace (); + + kdDebug( 7108 ) << "InfoProtocol::decodePath - done" << endl; +} + +// A minimalistic stat with only the file type +// This seems to be enough for konqueror +void InfoProtocol::stat( const KURL & ) +{ + UDSEntry uds_entry; + UDSAtom uds_atom; + + // Regular file with rwx permission for all + uds_atom.m_uds = KIO::UDS_FILE_TYPE; + uds_atom.m_long = S_IFREG | S_IRWXU | S_IRWXG | S_IRWXO; + + uds_entry.append( uds_atom ); + + statEntry( uds_entry ); + + finished(); +} + +extern "C" { int KDE_EXPORT kdemain( int argc, char **argv ); } + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_info" ); + + kdDebug() << "kio_info starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_info protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + InfoProtocol slave( argv[2], argv[3] ); + slave.dispatchLoop(); + + return 0; +} diff --git a/kioslave/info/info.h b/kioslave/info/info.h new file mode 100644 index 000000000..ccf41fc5e --- /dev/null +++ b/kioslave/info/info.h @@ -0,0 +1,36 @@ +#ifndef __info_h__ +#define __info_h__ + +#include <qobject.h> + +#include <kio/slavebase.h> + +class KProcess; + +class InfoProtocol : public KIO::SlaveBase +{ +public: + + InfoProtocol( const QCString &pool, const QCString &app ); + virtual ~InfoProtocol(); + + virtual void get( const KURL& url ); + virtual void stat( const KURL& url ); + virtual void mimetype( const KURL& url ); + +protected: + + void decodeURL( const KURL &url ); + void decodePath( QString path ); + +private: + + QString m_page; + QString m_node; + + QString m_perl; + QString m_infoScript; + QString m_infoConf; +}; + +#endif // __info_h__ diff --git a/kioslave/info/info.protocol b/kioslave/info/info.protocol new file mode 100644 index 000000000..3bb600aea --- /dev/null +++ b/kioslave/info/info.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_info +protocol=info +input=none +output=filesystem +reading=true +defaultMimetype=text/html +determineMimetypeFromExtension=false +DocPath=kioslave/info.html +Icon=help_index +Class=:local diff --git a/kioslave/info/kde-info2html b/kioslave/info/kde-info2html new file mode 100755 index 000000000..428d65dbb --- /dev/null +++ b/kioslave/info/kde-info2html @@ -0,0 +1,1031 @@ +#!/usr/bin/perl +#--------------------------------------------------------- +# info2html +#--------------------------------------------------------- +# +# PURPOSE +# This perl script converts info nodes to HTML format. +# The node is specified on the command line using the +# syntax +# (<infofile>)<tag> +# If <infofile> and/or <tag> are missing, (dir)Top is assumed. +# +# AUTHOR +# Karl Guggisberg <guggis@iam.unibe.ch> +# +# Changes for the KDE Help Center (c) 1999 Matthias ELter +# (me@kde.org) +# +# LICENSE +# GPL +# +# HISTORY +# 11.10.93 V 1.0 +# 14.10.93 V 1.0a some comments added +# 15.10.93 V 1.0b file for configuration settings +# 16.10.93 V 1.0c multiple info path possible +# some bugs in escaping references removed +# 28.6.94 V 1.0d some minor changes +# 8.4.95 V 1.1 bug fixes by Tim Witham +# <twitham@eng.fm.intel.com> +# March 1999 Changes for use in KDE Help Center +# February 2000 Changes for bzip2 format +# Sept. 4 2002 Updated to the KDE look +# by Hisham Muhammad <hisham@apple2.com> +# January 30 2003 Ported Hisham's work to HEAD +# by David Pashley <david@davidpashley.com> +# March 6 2003 Substitute use of absolute fixed file URLs to images with help:common URLs +# for the images and style sheet. By Luis Pedro Coelho +# March 9 2003 Add support for browsing by file. by Luis Pedro Coelho +# June 11 2003 Update the layout of the sides to the new infopageslayout. +# by Sven Leiber <s.leiber@web.de> +# +#------------------------------------------------------- + +use strict; + +# set here the full path of the info2html.conf +push @INC, $1 if $0 =~ m!(.*/)[^/]+$!; # full path of config file is passed in ARGV[1] by caller but let's clean this anyway +my $IMAGEDIR = "file:$ARGV[1]/"; # TV: broken, broken, not passed +my $config_file = $ARGV[0]; +delete $ENV{CDPATH}; +delete $ENV{ENV}; +require $config_file; #-- configuration settings + +my $STYLESHEET_KDE = "<link rel=\"stylesheet\" href=\"help:common/kde-default.css\" type=\"text/css\"/>"; +my $LOGO_KDE = "<img src=\"help:/common/kde_logo.png\" alt=\"KDE - The K Desktop Environment\" width=\"296\" height=\"79\" border=\"0\">"; + +# the use of a query should make sure it never conflicts with a "real" path +my $BROWSE_BY_FILE_PATH = '/browse_by_file?special=yes'; + + +my $DONTPRINTYET = 'DONTPRINTYET '; + +#-- patterns +my $NODEBORDER = '\037\014?'; #-- delimiter of an info node +my $REDIRSEP = '\177'; #-- delimiter in tag tables +my $WS = '[ \t]+'; #-- white space + +my $WSS = '[ \t]*'; #-- white space * +my $TE = '[\t\,\.]'; #-- end of a tag +my $TAG = '[^\t\,\.]+'; #-- pattern for a tag +my $FTAG = '[^\)]+'; #-- pattern for a file name in + #-- a cross reference + +#--------------------------------------------------------- +# DieFileNotFound +#--------------------------------------------------------- +# Replies and error message if the file '$FileName' is +# not accessible. +#--------------------------------------------------------- +sub DieFileNotFound { + my ($FileName) = @_; + $FileName =~ s/&/&/g; + $FileName =~ s/>/>/g; + $FileName =~ s/</</g; + + #-- TEXT : error message if a file could not be opened + print <<EOF; +<head> +<title>Info: (no page found)</title> +</head> +<body> +<h1>KDE Info Pages Viewer Error</h1> + No info page for topic <code>"$FileName"</code> found.<br> + You may find what you are looking for at the <a href="man:$FileName">$FileName manpage</a>. +</body> +EOF + die "\n"; +} + +#--------------------------------------------------------- +# Redirect +#--------------------------------------------------------- +# Since we can't do a kioslave redirection from here, we resort to an HTML +# redirection. +# +# It could be simpler to just output the correct page, but that would leave the +# the browser URL indication a bit wrong and more importantly we might mess up relative links. +# Therefore, I implemented it like this which is simpler if not as nice on the end user +# who sees a flicker. +#--------------------------------------------------------- + +sub Redirect { + my ($File,$Tag) = @_; + print <<EOF; + <html><head><title>Doing redirection</title> + <meta http-equiv="refresh" content="0; url=info:$File/$Tag"> + <body> + <h1>Redirecting .... </h1> + <p>If you are not automatically taken to a new page, <a href="info:$File/$Tag">click here</a> to continue. + </body> + </html> +EOF + + exit 0; +} + +#--------------------------------------------------------- +# FileNotFound +#--------------------------------------------------------- +# If the file is not found and the node is '', try to go through +# dir entries. +# This deals with cases like info:ls should open "coreutils/ls invocation" +#--------------------------------------------------------- +sub FileNotFound { + my ($FileName,$NodeName) = @_; + DieFileNotFound($FileName) if $NodeName ne 'Top' || $FileName eq 'dir'; + # Try to find it in dir + + my $DirFileName = &FindFile('dir'); + if ($DirFileName =~ m/.info.bz2$/ ) { + open DIR, "-|", "bzcat", $DirFileName; + } + elsif ($DirFileName =~ m/.info.gz$/ ) { + open DIR, "-|", "gzip", "-dc", $DirFileName; + } + else { + open DIR, $DirFileName; + } + my $looking = 1; + while (<DIR>) { + next if $looking && !/\* Menu/; + $looking = 0; + my @item = &ParseMenuItem($_,'dir'); + if (!defined(@item)) { next } + my ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText) = @item; + if ($MenuLinkRef eq $FileName) { + &Redirect($MenuLinkFile, $MenuLinkTag); + exit 0; + } + } + &DieFileNotFound($FileName); +} + +#--------------------------------------------------------- +# Escape +#--------------------------------------------------------- +# This procedures escapes some special characeters. The +# escape sequence follows the WWW guide for escaped +# characters in URLs +#--------------------------------------------------------- +sub Escape { + my ($Tag) = @_; + #-- escaping is not needed anymore KG/28.6.94 + #-- it is, for "?" %3f (info:/cvs/What is CVS?), kaper/23.7.02 + $Tag =~ s/ /%20/g; # space + $Tag =~ s/\?$/%3f/g; # space + $Tag =~ s/\"/%22/g; # space + $Tag =~ s/\#/%23/g; +# $Tag =~ s/\+/%AB/g; # + + $Tag; +} + +#---------------------------------------------------------- +# DirnameCheck +# TV: This is totally broken. +# I don't know what was the original attempt but that code +# cannot work ! we cannot match the info name (which has no full path) +# with the info path ... +# The only thing i can see (guessed from the || part of the caller) +# is that we try to reject files with "/" in their name, guessing +# we pass a man page full path instead of a info file name ... +# In *that* case, the flow logic is inverted and we should have used "&&" +# instead of "||" +# +# Thus the commented out call... +#---------------------------------------------------------- +#sub DirnameCheck { +# my ($Base) = @_; +# my $Dir = $Base; +# +# $Base =~ s!.*/!!g; +# $Dir =~ s!\Q$Base\E!!; +# +# foreach (@info2html::config::INFODIR) { +# return 1 if $Dir =~ /^$_/; +# } +# +# foreach my $i (split(/:/, $ENV{INFOPATH})) { +# return 1 if $Dir =~ /^$i/; +# } +# +# return 0; +#} + +#---------------------------------------------------------- +# DeEscape +#---------------------------------------------------------- +#sub DeEscape { +# my ($Tag) = @_; +# #-- deescaping is not needed anymore. KG/28.6.94 +# $Tag =~ s/%AB/+/g; +# $Tag =~ s/%20/ /g; +# $Tag =~ s/\.\.\///g; +# $Tag =~ s/\.\.//g; +# $Tag =~ s/\.\///g; +# $Tag; +#} + +sub infocat { +# Collect them all into an array that can be sorted + + my %InfoFile; + my %LinkText; + my @dirs; + + foreach my $dir (@info2html::config::INFODIR) { + push @dirs, $dir; + } + if ($ENV{'INFOPATH'}) { + foreach my $dir (split(/:/, $ENV{INFOPATH})) { + push @dirs, $dir; + } + } + + foreach my $dir (@dirs) { + opendir DIR, $dir; + my ($infofile,$filedesc); + while ($infofile = readdir(DIR)) { + if ($infofile =~ m/.info.bz2$/ ) { + open INFOFILE, "-|", "bzcat", "$dir/$infofile"; + } + elsif ($infofile =~ m/.info.gz$/ ) { + open INFOFILE, "-|", "gzip", "-dc", "$dir/$infofile"; + } + elsif ($infofile =~ m/.info$/) { + open INFOFILE, "-|", "$dir/$infofile"; + } + else { + next; + } + $filedesc = ''; + my $collect = 0; + my $empty = 1; + while (<INFOFILE>) { + last if (m/END-INFO-DIR-ENTRY/); + s/^\* //; + chomp; + next if /^\s*$/; + if ($collect) { + $filedesc .= "\n<br>" if ($collect < 16); + $filedesc .= $_; + --$collect; + $empty = 0; + } elsif (!$empty && !$collect) { + $filedesc .= "<br><b>...</b>\n"; + last; + } + $collect=16 if (m/START-INFO-DIR-ENTRY/); + } + if ($empty) { $filedesc .= 'no description available'; } + close INFOFILE; + $filedesc .= $infofile if ($filedesc eq ""); +# Add to the hash + $LinkText{$filedesc} = "$dir/$infofile"; + $InfoFile{$filedesc} = "$infofile"; + } + } + +# Now output the list + my @sorted = sort { lc($a) cmp lc($b) } keys %InfoFile; + + print '<dl>'; + foreach my $description ( @sorted ) { + print <<EOF; + <dt> <a href="info:$InfoFile{$description}/Top">$LinkText{$description}</a> + <dd>$description + +EOF + } + print '</dl>'; +} + +#---------------------------------------------------------- +# ParsHeaderToken +#---------------------------------------------------------- +# Parses the header line of an info node for a specific +# link directive (e.g. Up, Prev) +# +# Returns a link as (InfoFile,Tag). +#---------------------------------------------------------- +sub ParsHeaderToken { + my ($HeaderLine, $Token) = @_; + return ("", "") if $HeaderLine !~ /$Token:/; #-- token not available + my ($InfoFile, $node, $Temp); + if ($HeaderLine =~ m!$Token:$WS(\(($FTAG)\))!) { + $InfoFile = $2; + $Temp = $2 ne "" ? '\(' . $2 . '\)' : ""; + } + $node = $1 if $HeaderLine =~ m!$Token:$WS$Temp$WSS([^\t,\n]+)?([\t,\.\n])!; + $node ||= "Top"; + return $InfoFile, $node; +} + +#--------------------------------------------------------- +# ParsHeaderLine +#-------------------------------------------------------- +# Parses the header line on an info node for all link +# directives allowed in a header line. +# Sometimes the keyword 'Previous' is found in stead of +# 'Prev'. Thats why the redirection line is checked +# against both of these keywords. +#------------------------------------------------------- +sub ParsHeaderLine { + my ($HL) = @_; + my @LinkList; + #-- Node + push(@LinkList, &ParsHeaderToken($HL, "Node")); + #-- Next + push(@LinkList, &ParsHeaderToken($HL, "Next")); + #-- Up + push(@LinkList, &ParsHeaderToken($HL, "Up")); + #-- Prev or Previous + my @LinkInfo = &ParsHeaderToken($HL, "Prev"); + &ParsHeaderToken($HL, "Previous") if $LinkInfo[0] eq "" && $LinkInfo[1] eq ""; + push(@LinkList, @LinkInfo); + return @LinkList; +} + +############################################################ +# turn tabs into correct number of spaces +# +sub Tab2Space { + my ($line) = @_; + $line =~ s/^\t/ /; # 8 leading spaces if initial tab + while ($line =~ s/^([^\t]+)(\t)/$1 . ' ' x (8 - length($1) % 8)/e) { + } # replace each tab with right num of spaces + return $line; +} + +#-------------------------------------------------------- +# ParseMenuItem +#-------------------------------------------------------- +# Takes a line containing a Menu item and returns a list of +# ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText) +# or undef if the parsing fails +#------------------------------------------------------- + +sub ParseMenuItem { + my ($Line,$BaseInfoFile) = @_; + my ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText); + $Line = &Tab2Space($Line); # make sure columns line up well + + if ($Line =~ /\* ([^:]+)::/) { # -- is a simple entry ending with :: ? + $MenuLinkTag = $1; + $MenuLinkRef = $1; + $MenuLinkText = $'; #' --just to help emacs perl-mode + $MenuLinkFile = &Escape($BaseInfoFile); + } elsif ($Line =~ /\* ([^:]+):(\s*\(($FTAG)\)($TAG)?$TE\.?)?(.*)$/) { + $MenuLinkFile = $BaseInfoFile; + $MenuLinkRef = $1; + $MenuLinkText = $5; + if ($2) { + $MenuLinkFile = $3; + $MenuLinkTag = $4 || 'Top'; + $MenuLinkText = ($2 ? ' ' x (length($2)+1) : '') . "$5\n"; + } else { + $Line = "$5\n"; + if ($Line =~ /( *($TAG)?$TE(.*))$/) { + $MenuLinkTag = $2; + $MenuLinkText = $Line; + } + } + } else { + return undef; + } + $MenuLinkTag = &Escape($MenuLinkTag); # -- escape special chars + $MenuLinkText =~ s/^ *//; + return ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText); +} + +#-------------------------------------------------------- +# MenuItem2HTML +#-------------------------------------------------------- +# Transform an info menu item in HTML with references +#------------------------------------------------------- +sub MenuItem2HTML { + my ($Line, $BaseInfoFile) = @_; + my @parse_results = &ParseMenuItem($Line, $BaseInfoFile); + if (!defined (@parse_results)) { return $Line; } + my ($MenuLinkTag, $MenuLinkFile, $MenuLinkRef, $MenuLinkText) = @parse_results; + #-- produce a HTML line + return "<tr class=\"infomenutr\"><td class=\"infomenutd\" width=\"30%\"><ul><li><a href=\"info:/$MenuLinkFile/$MenuLinkTag\">$MenuLinkRef</a></ul></td><td class=\"infomenutd\">$MenuLinkText"; +} + +#------------------------------------------------------------- +# ReadIndirectTable +#------------------------------------------------------------ +# Scans an info file for the occurence of an 'Indirect:' +# table. Scans the entrys and returns two lists with the +# filenames and the global offsets. +#--------------------------------------------------------- +sub ReadIndirectTable { + my ($FileName, $FileNames, $Offsets) = @_; + + local *FH1; + if ($FileName =~ /\.gz$/) { + open FH1, "-|", "gunzip", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } elsif ($FileName =~ /\.bz2$/) { + open FH1, "-|", "bunzip2", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } else { + open(FH1, $FileName) || &DieFileNotFound($FileName); + } + #-- scan for start of Indirect: Table + local $_; + while (<FH1>) { + my $Next = <FH1> if /$NODEBORDER/; + last if $Next =~ /^Indirect:/i; + } + #-- scan the entrys and setup the arrays + local $_; + while (<FH1>) { + last if /$NODEBORDER/; + if (/([^:]+):[ \t]+(\d+)/) { + push(@$FileNames, $1); + push(@$Offsets, $2); + } + } + close(FH1); +} + +#--------------------------------------------------------- +# ReadTagTable +#-------------------------------------------------------- +# Reads in a tag table from an info file. +# Returns an assoziative array with the tags found. +# Tags are transformed to lower case (info is not +# case sensitive for tags). +# The entrys in the assoziative Array are of the +# form +# <file>#<offset> +# <file> may be empty if an indirect table is +# present or if the node is located in the +# main file. +# 'Exists' indicates if a tag table has been found. +# 'IsIndirect' indicates if the tag table is based +# on a indirect table. +#-------------------------------------------------------- +sub ReadTagTable { + my ($FileName, $TagList, $Exists, $IsIndirect) = @_; + + local *FH; + if ($FileName =~ /\.gz$/) { + open FH, "-|", "gunzip", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } elsif ($FileName =~ /\.bz2$/) { + open FH, "-|", "bunzip2", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } else { + open FH, $FileName || &DieFileNotFound($FileName); + } + ($$Exists, $$IsIndirect) = (0, 0); + #-- scan for start of tag table + local $_; + while (<FH>) { + if (/$NODEBORDER/) { + if (<FH> =~ /^Tag table:/i) { + $$Exists = 1; + last; + } + } + } + #-- scan the entrys + local $_; + while (<FH>) { + $$IsIndirect = 1 if /^\(Indirect\)/i; + last if /$NODEBORDER/; + if (/Node:[ \t]+([^$REDIRSEP]+)$REDIRSEP(\d+)/) { + my ($Tag, $Offset) = (lc($1), $2); + my $File = $1 if /File:[ \t]+([^\t,]+)/; + $TagList->{$Tag} = $File."#".$Offset; + } + } + close(FH); +} + +#---------------------------------------------------------- +# ParsCrossRefs +#---------------------------------------------------------- +# scans a line for the existence of cross references and +# transforms them to HTML using a little icon +#---------------------------------------------------------- +sub ParsCrossRefs { + my ($prev, $Line, $BaseInfoFile) = @_; + my ($NewLine, $Token); + my ($CrossRef, $CrossRefFile, $CrossRefTag, $CrossRefRef, $CrossRefText); + $Line = " " . $Line; + if ($prev =~ /\*Note([^\t\,\.]*)$/mi) { + $Line = "$prev-NEWLINE-$Line" if $Line =~ /^$TAG$TE/m; + } + my @Tokens = split(/(\*Note)/i, $Line); # -- split the line + while ($Token = shift @Tokens) { + $CrossRefTag = $CrossRefRef = $CrossRefFile = $CrossRefText = ''; + if ($Token !~ /^\*Note/i) { #-- this part is pure text + $NewLine .= $Token; + next; #-- ... take the next part + } + $CrossRef = shift(@Tokens); + if ($CrossRef !~ /:/) { #-- seems not to be a valid cross ref. + $NewLine .= $Token.$CrossRef; + next; # -- ... take the next one + } + if ($CrossRef =~ /^([^:]+)::/) { # -- a simple cross ref.. + $CrossRefTag = $1; + $CrossRefText = $'; + $CrossRefRef = $CrossRefTag; + $CrossRefTag =~ s/-NEWLINE-/ /g; + $CrossRefTag =~ s/^\s+//; + $CrossRefTag =~ s/\s+/ /g; + $CrossRefRef =~ s/-NEWLINE-/\n/g; + $CrossRefTag = &Escape($CrossRefTag); # -- escape specials + $BaseInfoFile = &Escape($BaseInfoFile); + $NewLine .= "<a href=\"info:/$BaseInfoFile/$CrossRefTag\">"; + $NewLine .= "$CrossRefRef</a>$CrossRefText"; + next; # -- .. take the next one + } + if ($CrossRef !~ /$TE/) { # never mind if tag doesn't end on this line + $NewLine .= $Token.$CrossRef; + next; + } +#print "--- Com. CR : $CrossRef --- \n"; + if ($CrossRef =~ /([^:]+):/) { #-- A more complicated one .. + $CrossRefRef = $1; + $CrossRef = $'; + $CrossRefText = $CrossRef; + } + if ($CrossRef =~ /^(\s|\n|-NEWLINE-)*\(($FTAG)\)/) { #-- .. with another file ? + $CrossRefFile = $2; + $CrossRef = $'; + } + $CrossRefTag = $2 if $CrossRef =~ /^(\s|\n|-NEWLINE-)*($TAG)?($TE)/; #-- ... and a tag ? + if ($CrossRefTag eq "" && $CrossRefFile eq "") { + $NewLine .= "*Note : $CrossRefText$3"; + next; + } + + $CrossRefTag =~ s/-NEWLINE-/ /g; + $CrossRefTag =~ s/^\s+//; + $CrossRefTag =~ s/\s+/ /g; + $CrossRefRef =~ s/-NEWLINE-/\n/g; + $CrossRefText =~ s/-NEWLINE-/\n/g; + $CrossRefFile = $BaseInfoFile if $CrossRefFile eq ""; + $CrossRefTag = "Top" if $CrossRefTag eq ""; + $CrossRefRef = "($CrossRefFile)$CrossRefTag" if $CrossRefRef eq ''; + $CrossRefTag = &Escape($CrossRefTag); #-- escape specials + $CrossRefFile = &Escape($CrossRefFile); + #-- append the HTML text + $NewLine .= "<a href=\"info:/$CrossRefFile/$CrossRefTag\">"; + $NewLine .= "$CrossRefRef</a>$CrossRefText"; + } + if ($NewLine =~ /\*Note([^\t\,\.]*)$/i) { + return "$DONTPRINTYET$NewLine"; + } else { + $NewLine; #-- return the new line + } +} + + +#------------------------------------------------------------- +# PrintLinkInfo +#------------------------------------------------------------- +# prints the HTML text for a link information in the +# header of an info node. Uses some icons URLs of icons +# are specified in 'info2html.conf'. +#------------------------------------------------------------ +sub PrintLinkInfo { + my ($LinkType, $LinkFile, $LinkTag, $BaseInfoFile) = @_; + my ($LinkFileEsc, $LinkTypeText); + return if $LinkFile eq "" && $LinkTag eq ""; + + #-- If no auxiliary file specified use the current info file + $LinkFile ||= $BaseInfoFile; + my $LinkRef = $LinkTag; + $LinkTag = &Escape($LinkTag); + $LinkFileEsc = &Escape($LinkFile); + #-- print the HTML Text + print <<EOF; +<a href="info:/$LinkFileEsc/$LinkTag"> + $LinkTypeText + <strong>$LinkRef</strong> +</a> +EOF +} + +#------------------------------------------------------------- +# PrintHeader +#------------------------------------------------------------- +# Prints the header for an info node in HTML format +#------------------------------------------------------------ +sub PrintHeader { + my ($LinkList, $BaseInfoFile) = @_; + my @LinkList = @{$LinkList}; + + my $UpcaseInfoFile = $BaseInfoFile; + $UpcaseInfoFile =~ tr/a-z/A-Z/; + #-- TEXT for the header of an info node + print <<EOF; +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> +<html> + <head> + <title>Info: ($BaseInfoFile) $LinkList[1]</title> + $STYLESHEET_KDE + </head> + <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> +<!--header start--> +<div style="background-image: url(help:/common/top-middle.png); width: 100%; height: 131px;"> +<div style="position: absolute; right: 0px;"> +<img src="help:/common/top-right-konqueror.png" style="margin: 0px" alt="" /> +</div> +<div style="position: absolute; left: 0px;"> +<img src="help:/common/top-left.png" style="margin: 0px" alt="" /> +</div> +<div style="position: absolute; top: 25px; right: 100px; text-align: right; font-size: xx-large; font-weight: bold; text-shadow: #fff 0px 0px 5px; color: #444"> +$UpcaseInfoFile: $LinkList[1]</div> +</div> +<div class="header" style="border: none"> +EOF + common_headers($LinkList, $BaseInfoFile); +print <<EOF; +</div> + <div id="contents"> + <div class="chapter"> +EOF +} + +sub common_headers { + my ($LinkList, $BaseInfoFile) = @_; + my @LinkList = @{$LinkList}; + print <<EOF; + <tr><td width="33%" align="left" valign="top" class="navLeft"> +EOF + &PrintLinkInfo("Prev", $LinkList[6], $LinkList[7], $BaseInfoFile); + print <<EOF; + </td><td width="34%" align="center" valign="top" class="navCenter"> +EOF + &PrintLinkInfo("Up", $LinkList[4], $LinkList[5], $BaseInfoFile); + print <<EOF; + </td><td width="33%" align="right" valign="top" class="navRight"> +EOF + &PrintLinkInfo("Next", $LinkList[2], $LinkList[3], $BaseInfoFile); +} + +#--------------------------------------------------------- +# PrintFooter +#--------------------------------------------------------- +# prints the footer for an info node in HTML format +#--------------------------------------------------------- +sub PrintFooter { + my ($LinkList, $BaseInfoFile, $LinkFile) = @_; + + $LinkFile ||= $BaseInfoFile; + + #-- TEXT for the footer of an info node + print <<EOF; + </div> + <em>Automatically generated by a version of + <a href="$info2html::config::DOC_URL"> + <b>info2html</b> + </a> modified for <a href="http://www.kde.org/">KDE</a></em>. + <div class="bottom-nav"> +EOF + common_headers($LinkList, $BaseInfoFile); + print <<EOF; + <!--<br />--><em>$LinkFile</em> + </div> + </body> +</html> +EOF +} + +#---------------------------------------------------------- +# ReplyNotFoundMessage +#---------------------------------------------------------- +sub ReplyNotFoundMessage { + my ($FileName, $Tag) = @_; + print <<EOF; +<head> +<title>Info Files - Error Message</title> +</head> +<h1>Error</h1> +<body> +The Info node <em>$Tag</em> in Info file <em>$FileName</em> +does not exist. +</body> +EOF +} + +sub PrintByFileLink { + print <<EOF + + <hr width="80%"/> + <p>If you did not find what you were looking for try <a href="info:$BROWSE_BY_FILE_PATH">browsing by file</a> to + see files from packages which did not update the directory. +EOF +} + +#----------------------------------------------------------- +# BrowseByFile +#----------------------------------------------------------- +# Shows a list of available files in the system with a short +# description of them. +#------------------------------------------------------------ + +sub BrowseByFile { + my @LinkList = ('', '', '', '', + 'dir', 'Top', '','',''); # set LinkList[4] & LinkList[5], of course ;) + my $BaseInfoFile = 'Available Files'; + &PrintHeader(\@LinkList, $BaseInfoFile); + print <<EOF; +<h2>Available Files</h2> +EOF + &infocat; + &PrintFooter(\@LinkList, $BaseInfoFile); +} + +#----------------------------------------------------------- +# InfoNode2HTML +#----------------------------------------------------------- +# scans an info file for the node with the name '$Tag' +# starting at the postion '$Offset'. +# If found the node is tranlated to HTML and printed. +#------------------------------------------------------------ +sub InfoNode2HTML { + my ($FileName, $Offset, $Tag, $BaseInfoFile) = @_; + + local *FH2; + if ($FileName =~ /\.gz$/) { + open FH2, "-|", "gunzip", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } elsif ($FileName =~ /\.bz2$/) { + open FH2, "-|", "bunzip2", "-q", "-d", "-c", $FileName || &DieFileNotFound($FileName); + } else { + open FH2, $FileName || &DieFileNotFound($FileName); + } + seek(FH2, $Offset, 0); + $Tag =~ tr/A-Z/a-z/; # -- to lowercase + #-- scan for the node start + my ($Found, @LinkList); + local $_; + while (<FH2>) { + if (/$NODEBORDER/) { + my $line = <FH2>; + @LinkList = &ParsHeaderLine($line); + my $CompareTag = $Tag; + $CompareTag =~ s/([^0-9A-Za-z])/\\$1/g; #-- escape special chars ! + my $Temp = $LinkList[1]; + $Temp =~ tr/A-Z/a-z/; #-- to lower case + if ($Temp =~ /^\s*$CompareTag\s*$/) { #-- node start found ? + $Found = 1; + last; + } + } + } + + return &ReplyNotFoundMessage($FileName, $Tag) unless $Found; # -- break if not found; + + &PrintHeader(\@LinkList, $BaseInfoFile); + my $InMenu = 0; + my $prev; + my $LineCount = 0; + my $Entries = 0; + my $Par = 0; + my @ParLines = (); + my $ParLine=0; + my $MayBeText=0; + my $MayBeTitle=0; + my $Line; + my $PrevMenu; + local $_; + while (<FH2>) { + $LineCount++; + last if /$NODEBORDER/; + #-- replace meta characters + #s/"`"([^"'"]*)"'"/"<span class=\"option\">"$1"</span>"/g; + s/&/&/g; + s/>/>/g; + s/</</g; + + my $Length = length($_); + if ($LineCount == 3 && $InMenu == 0 && length($_) == $Length && $Length > 1){ #-- an underline ? + if (/^\**$/) { + print "<h2>$prev</h2>\n"; + $prev = ""; + next; + } + elsif (/^=*$/) { + print "<h3>$prev</h3>\n"; + $prev = ""; + next; + } + else { + print "<h4>$prev</h4>\n"; + $prev = ""; + next; + } + } + + if (/^\* Menu/ && $InMenu == 0) { # -- start of menu section ? + $InMenu = 1; + print "<h3>Menu</h3>\n"; + } + elsif ($InMenu == 1) { + # This is pretty crappy code. + # A lot of logic (like the ParsCrossRefs and tranforming Variable: etc) is repeated below. + # There have been a few bugs which were fixed in one branch of the code and left in the other. + # This should be refactored. + # LPC (16 March 2003) + if (/^\* /) { #-- a menu entry ? + if ($Entries == 0) { + $Entries = 1; + print "<table class=\"infomenutable\">"; + } + print &MenuItem2HTML($_,$BaseInfoFile); + } + elsif (/^$/) { #-- empty line + if ($Entries == 1) { + print "</td></tr></table>"; + $Entries = 0; + } + print "<br>"; + } + else { + $Line = &ParsCrossRefs($prev,$_,$BaseInfoFile); + if ($Line =~ /^$DONTPRINTYET/) { + $prev = $Line; + $prev =~ s/^$DONTPRINTYET//; + chomp $prev; + } + elsif ($LineCount == 2) { + $prev = $Line; + } else { + $prev = $Line; + $Line =~ s#- (Variable|Function|Macro|Command|Special Form|User Option|Data Type):.*$#<em><strong>$&</strong></em>#; + $Line =~ s/^[ \t]*//; + print $Line; + } + } + } + else { + if (/^ *$/) { + if ($MayBeText == 1) { + print "<p>$Par</p>" + } else { + print "<pre>"; + foreach (@ParLines) { + print $_; + } + print "\n"; + print "</pre>"; + } + @ParLines = (); + $ParLine = 1; + $MayBeText = 1; + $MayBeTitle = 1; + $Par = ""; + } else { + if ($ParLine == 1) { + if (!/^ {1,4}[^ ]/ || /[^ ] [^ ]/) { + $MayBeText = 0; + } + } else { + if (!/^ ?[^ ]/ || /[^ ] [^ ]/) { + $MayBeText = 0; + } + } + $Line = &ParsCrossRefs($prev,$_,$BaseInfoFile); + if ($Line =~ /^$DONTPRINTYET/) { + $prev = $Line; + $prev =~ s/^$DONTPRINTYET//; + chomp $prev; + } elsif ($LineCount == 2) { + $prev = $Line; + } else { + $prev = $Line; + $Line =~ s#- (Variable|Function|Macro|Command|Special Form|User Option):.*$#<strong>$&</strong>#; + $Line =~ s/`([^']*)'/`<span class="option">$1<\/span>'/g; #' + $Line =~ s/((news|ftp|http):\/\/[A-Za-z0-9\.\/\#\-_\~]*)/<a href="$1">$1<\/a>/g; + $Line =~ s/([A-Za-z0-9\.\/\#\-_\~]*\@[A-Za-z0-9\.\/\#\-_\~]*\.[A-Za-z]{2,3})/<a href="mailto:$1">$1<\/a>/g; + $Par = $Par . $Line; + $ParLines[$ParLine] = $Line; + $ParLine++; + } + } + } + } + if ($Entries == 1) { + print "</table>" + } + if ($PrevMenu =~ "") { + print &MenuItem2HTML($PrevMenu,$BaseInfoFile); + } + + close(FH2); + + if ($BaseInfoFile =~ m/dir/i + && $Tag =~ m/Top/i) { + &PrintByFileLink; + } + + &PrintFooter(\@LinkList, $BaseInfoFile); +} + +#------------------------------------------------------------- +# max +#------------------------------------------------------------ +sub max { + my ($a, $b) = @_; + return $a >= $b ? $a : $b; +} + +#----------------------------------------------------------- +# GetFileAndOffset +#------------------------------------------------------------ +# This procedure locates a specific node in a info file +# The location is based on the tag and indirect table in +# basic info file if such tables are available. +# Because the offsets specified in the tag and in the +# indirect table are more or less inacurate the computet +# offset is set back 100 bytes. From this position +# the specified node will looked for sequentially +#------------------------------------------------------------ +sub GetFileAndOffset { + my ($BaseInfoFile, $NodeName) = @_; + my ($Exists, $IsIndirect, $File, $Offset, $FileOffset, %TagList, @FileNames, @Offsets); + $NodeName =~ tr/A-Z/a-z/; + &ReadIndirectTable($BaseInfoFile, \@FileNames, \@Offsets); + + +# This looks wastefull: +# We build a whole TagList hash and then use it to lookup the tag info. +# Why not pass $NodeName to ReadTagTable and let it return just the desired info? +# lpc (16 March 2003) + &ReadTagTable($BaseInfoFile, \%TagList, \$Exists, \$IsIndirect); + return "", 0 unless $Exists; #-- no tag table available + return "", 0 unless defined $TagList{$NodeName}; #-- tag is not in the tag table + ($File, $Offset) = split(/#/, $TagList{$NodeName}); + return $File, &max($Offset - 100, 0) if $File; #-- there is an explicite + #-- not in the tag table + + if ($IsIndirect == 1) { + foreach my $i (0..$#Offsets) { + $FileOffset = $Offsets[$i] if $Offsets[$i] <= $Offset; + $File = $FileNames[$i] if $Offsets[$i] <= $Offset; + } + return $File, &max($Offset - $FileOffset - 100,0); #-- be safe (-100!) + } else { + return "", &max($Offset - 100, 0); + } +} + +# FindFile: find the given file on the infopath, return full name or "". +# Let filenames optionally have .info suffix. Try named version first. +# Handle gzipped file too. +sub FindFile { + my ($File) = @_; + return "" if ($File =~ /\.\./); + my $Alt = $File =~ /^(.+)\.info$/ ? $1 : $File . '.info'; + foreach my $Name ($File, $Alt) { + my $gzName = $Name . '.gz'; + my $bz2Name = $Name . '.bz2'; + + foreach (@info2html::config::INFODIR) { + return "$_/$Name" if -e "$_/$Name"; + return "$_/$gzName" if -e "$_/$gzName"; + return "$_/$bz2Name" if -e "$_/$bz2Name"; + } + next unless $ENV{INFOPATH}; + foreach my $i (split(/:/, $ENV{INFOPATH})) { + return "$i/$Name" if -e "$i/$Name"; + return "$i/$gzName" if -e "$i/$gzName"; + return "$i/$bz2Name" if -e "$i/$bz2Name"; + } + } + return ""; +} + +#------------------------------------------------------- +# +#------------------- MAIN ----------------------------- +# +# called as +# perl /path/kde-info2html config_file image_base_path BaseInfoFile NodeName +# +# BaseInfoFile eq '#special#' to pass special args through NodeName (yes, it is a hack). +# + +my $PROGRAM = $0; # determine our basename and version +$PROGRAM =~ s!.*/!!; +my ($BaseInfoFile, $NodeName) = ($ARGV[2], $ARGV[3]); +#&DirnameCheck($BaseInfoFile) || &DieFileNotFound($BaseInfoFile); + +if ($BaseInfoFile eq '#special#' && $NodeName eq 'browse_by_file') { + &BrowseByFile; + exit 0; +} + +$BaseInfoFile = "dir" if $BaseInfoFile =~ /^dir$/i; +my $FileNameFull = &FindFile($BaseInfoFile) || &FileNotFound($BaseInfoFile,$NodeName); +my ($File, $Offset) = &GetFileAndOffset($FileNameFull, $NodeName); +$File ||= $BaseInfoFile; +$FileNameFull = &FindFile($File); +&InfoNode2HTML($FileNameFull, $Offset, $NodeName, $BaseInfoFile); + +exit 0; diff --git a/kioslave/info/kde-info2html.conf b/kioslave/info/kde-info2html.conf new file mode 100644 index 000000000..260d2b336 --- /dev/null +++ b/kioslave/info/kde-info2html.conf @@ -0,0 +1,43 @@ +# -*- perl -*- +package info2html::config; +#----------------------------------------------------------------- +# info2html.conf +#----------------------------------------------------------------- +# PURPOSE +# configuration settings for the 'info2html' script. +# +# AUTHOR +# Karl Guggisberg <guggis@iam.unibe.ch> +# +# HISTORY +# 15.10.93 V 1.0b +# 16.10.93 V 1.0c multple info files possible +# 28.6.94 V 1.0d some minor changes +# 8.4.95 V 1.1 some changements +#---------------------------------------------------------------- + +use strict; +#use vars qw(@ISA @EXPORT); +# +#@ISA = qw(Exporter); +#@EXPORT = qw(@INFODIR $DOC_URL); + + +#-- location of info files. +our @INFODIR = ( + "/usr/share/info", + "/usr/info", + "/usr/lib/info", +# "/usr/lib/teTeX/info", + "/usr/local/info", + "/usr/local/lib/info", + "/usr/X11R6/info", + "/usr/X11R6/lib/info", + "/usr/X11R6/lib/xemacs/info" + ); + + +#-- URL for documentation of info2html +our $DOC_URL = 'http://info2html.sourceforge.net/'; + +1; diff --git a/kioslave/ldap/LICENSE b/kioslave/ldap/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/kioslave/ldap/LICENSE @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/ldap/Makefile.am b/kioslave/ldap/Makefile.am new file mode 100644 index 000000000..49faa1e98 --- /dev/null +++ b/kioslave/ldap/Makefile.am @@ -0,0 +1,22 @@ +## Makefile.am of kdebase/kioslave/ldap + +INCLUDES = $(all_includes) $(LDAP_INCS) +AM_CXXFLAGS = -DLDAP_DEPRECATED +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LDAP_RPATH) +LDADD = $(LIB_KIO) $(LDAP_LIBS) + +####### Files + +kde_module_LTLIBRARIES = kio_ldap.la + +kio_ldap_la_SOURCES = kio_ldap.cpp +kio_ldap_la_LIBADD = $(LIB_KIO) $(LDAP_LIBS) $(LIB_KABC) +kio_ldap_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LDAP_RPATH) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_ldap.h + +kdelnk_DATA = ldap.protocol ldaps.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_ldap.pot diff --git a/kioslave/ldap/configure.in.in b/kioslave/ldap/configure.in.in new file mode 100644 index 000000000..b23c12387 --- /dev/null +++ b/kioslave/ldap/configure.in.in @@ -0,0 +1,111 @@ +AC_MSG_CHECKING(for LDAP support) +AC_ARG_WITH(ldap, +AC_HELP_STRING([--with-ldap=PATH],[Set path for LDAP files [default=check]]), +[ case "$withval" in + yes) + with_ldap=CHECK + ;; + esac ], +[ with_ldap=CHECK ] +)dnl + +if test "x$with_ldap" = "xCHECK" ; then + with_ldap=NOTFOUND + search_incs="$kde_includes /usr/include /usr/local/include" + AC_FIND_FILE(ldap.h, $search_incs, ldap_incdir) + if test -r $ldap_incdir/ldap.h ; then + test "x$ldap_incdir" != "x/usr/include" && LDAP_INCS="-I$ldap_incdir" + with_ldap=FOUND + fi + if test $with_ldap = FOUND ; then + with_ldap=NOTFOUND + for ext in la so sl a dylib ; do + AC_FIND_FILE(libldap.$ext, $kde_libraries /usr/lib /usr/local/lib /usr/lib64, + ldap_libdir) + if test -r $ldap_libdir/libldap.$ext ; then + if test "x$ldap_libdir" != "x/usr/lib" ; then + LDAP_LIBS="-L$ldap_libdir " + test "$USE_RPATH" = yes && LDAP_RPATH="-R $ldap_libdir" + fi + LDAP_LIBS="${LDAP_LIBS}-lldap" + with_ldap=FOUND + break + fi + done + fi +fi + +case "$with_ldap" in +no) AC_MSG_RESULT(no) ;; +framework) + LDAP_LIBS="-Xlinker -framework -Xlinker LDAP" + AC_DEFINE_UNQUOTED(HAVE_LIBLDAP, 1, [Define if you have LDAP libraries]) + LDAP_SUBDIR="ldap" + AC_MSG_RESULT(Apple framework) + ;; +FOUND) + AC_MSG_RESULT(incs=$ldap_incdir libs=$ldap_libdir) + ;; +NOTFOUND) AC_MSG_RESULT(searched but not found) ;; +*) + AC_MSG_RESULT($with_ldap) + ;; +esac + +LIB_LBER= +KDE_CHECK_LIB(lber, ber_alloc, [LIB_LBER=-llber], [], -L$ldap_libdir) +AC_SUBST(LIB_LBER) + +AC_MSG_CHECKING(whether LDAP support can be compiled) + + if test "x$with_ldap" != "xFOUND" ; then + LDAP_ROOT="$with_ldap" + if test "x$LDAP_ROOT" != "x/usr" ; then + LDAP_INCS="-I${LDAP_ROOT}/include" + LDAP_LIBS="-L${LDAP_ROOT}/lib " + if test "$USE_RPATH" = "yes" ; then + LDAP_RPATH="-R ${LDAP_ROOT}/lib" + fi + fi + LDAP_LIBS="${LDAP_LIBS}-lldap" + fi + LDAP_LIBS="${LDAP_LIBS} ${LIB_LBER} ${LIBRESOLV}" + + kde_safe_LIBS="$LIBS" + kde_safe_CFLAGS="$CFLAGS" + LIBS="$LIBS $all_libraries $LDAP_LIBS $KRB4_LIBS $X_EXTRA_LIBS" + CFLAGS="$CFLAGS $all_includes $LDAP_INCS $KRB4_INCS" + AC_LANG_SAVE + AC_LANG_C + AC_TRY_LINK(dnl + [ + #include <ldap.h> + #if LDAP_API_VERSION < 2004 + #error LDAP version too old, please upgrade to a library supporting API 2004 or higher + #endif + ], + [ + LDAP *ldap; + ], + , with_ldap=no + ) + AC_LANG_RESTORE + CFLAGS=$kde_safe_CFLAGS + LIBS=$kde_safe_LIBS + if test "$with_ldap" = "no" ; then + LDAP_INCS= + LDAP_LIBS= + LDAP_RPATH= + LDAP_SUBDIR= + AC_MSG_RESULT(no (but first try gave $msg)) + else + AC_DEFINE_UNQUOTED(HAVE_LIBLDAP, 1, [Define if you have LDAP libraries]) + LDAP_SUBDIR="ldap" + AC_MSG_RESULT(yes) + fi + +AC_SUBST(LDAP_INCS) +AC_SUBST(LDAP_LIBS) +AC_SUBST(LDAP_RPATH) + +AM_CONDITIONAL(include_kioslave_ldap, test -n "$LDAP_SUBDIR") diff --git a/kioslave/ldap/kio_ldap.cpp b/kioslave/ldap/kio_ldap.cpp new file mode 100644 index 000000000..749ab6121 --- /dev/null +++ b/kioslave/ldap/kio_ldap.cpp @@ -0,0 +1,1154 @@ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <sys/stat.h> + +#include <unistd.h> +#include <stdlib.h> +#include <netdb.h> +#include <netinet/in.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> + +#ifdef HAVE_SASL_SASL_H //prefer libsasl2 +#include <sasl/sasl.h> +#else +#ifdef HAVE_SASL_H +#include <sasl.h> +#endif +#endif +#include <kabc/ldif.h> + +#include "kio_ldap.h" + +using namespace KIO; +using namespace KABC; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +/** + * The main program. + */ +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_ldap" ); + + kdDebug(7125) << "Starting " << getpid() << endl; + + if ( argc != 4 ) { + kdError() << "Usage kio_ldap protocol pool app" << endl; + return -1; + } + + // let the protocol class do its work + LDAPProtocol slave( argv[1], argv[ 2 ], argv[ 3 ] ); + slave.dispatchLoop(); + + kdDebug( 7125 ) << "Done" << endl; + return 0; +} + +/** + * Initialize the ldap slave + */ +LDAPProtocol::LDAPProtocol( const QCString &protocol, const QCString &pool, + const QCString &app ) : SlaveBase( protocol, pool, app ) +{ + mLDAP = 0; mTLS = 0; mVer = 3; mAuthSASL = false; + mRealm = ""; mBindName = ""; + mTimeLimit = mSizeLimit = 0; + kdDebug(7125) << "LDAPProtocol::LDAPProtocol (" << protocol << ")" << endl; +} + +LDAPProtocol::~LDAPProtocol() +{ + closeConnection(); +} + +void LDAPProtocol::LDAPErr( const KURL &url, int err ) +{ + + char *errmsg = 0; + if ( mLDAP ) { + if ( err == LDAP_SUCCESS ) ldap_get_option( mLDAP, LDAP_OPT_ERROR_NUMBER, &err ); + if ( err != LDAP_SUCCESS ) ldap_get_option( mLDAP, LDAP_OPT_ERROR_STRING, &errmsg ); + } + if ( err == LDAP_SUCCESS ) return; + kdDebug(7125) << "error code: " << err << " msg: " << ldap_err2string(err) << + " Additonal error message: '" << errmsg << "'" << endl; + QString msg; + QString extraMsg; + if ( errmsg ) { + if ( errmsg[0] ) + extraMsg = i18n("\nAdditional info: ") + QString::fromUtf8( errmsg ); + free( errmsg ); + } + msg = url.prettyURL(); + if ( !extraMsg.isEmpty() ) msg += extraMsg; + + /* FIXME: No need to close on all errors */ + closeConnection(); + + switch (err) { +/* FIXME: is it worth mapping the following error codes to kio errors? + + LDAP_OPERATIONS_ERROR + LDAP_STRONG_AUTH_REQUIRED + LDAP_PROTOCOL_ERROR + LDAP_TIMELIMIT_EXCEEDED + LDAP_SIZELIMIT_EXCEEDED + LDAP_COMPARE_FALSE + LDAP_COMPARE_TRUE + LDAP_PARTIAL_RESULTS + LDAP_NO_SUCH_ATTRIBUTE + LDAP_UNDEFINED_TYPE + LDAP_INAPPROPRIATE_MATCHING + LDAP_CONSTRAINT_VIOLATION + LDAP_INVALID_SYNTAX + LDAP_NO_SUCH_OBJECT + LDAP_ALIAS_PROBLEM + LDAP_INVALID_DN_SYNTAX + LDAP_IS_LEAF + LDAP_ALIAS_DEREF_PROBLEM + LDAP_INAPPROPRIATE_AUTH + LDAP_BUSY + LDAP_UNAVAILABLE + LDAP_UNWILLING_TO_PERFORM + LDAP_LOOP_DETECT + LDAP_NAMING_VIOLATION + LDAP_OBJECT_CLASS_VIOLATION + LDAP_NOT_ALLOWED_ON_NONLEAF + LDAP_NOT_ALLOWED_ON_RDN + LDAP_NO_OBJECT_CLASS_MODS + LDAP_OTHER + LDAP_LOCAL_ERROR + LDAP_ENCODING_ERROR + LDAP_DECODING_ERROR + LDAP_FILTER_ERROR +*/ + case LDAP_AUTH_UNKNOWN: + case LDAP_INVALID_CREDENTIALS: + case LDAP_STRONG_AUTH_NOT_SUPPORTED: + error(ERR_COULD_NOT_AUTHENTICATE, msg); + break; + case LDAP_ALREADY_EXISTS: + error(ERR_FILE_ALREADY_EXIST, msg); + break; + case LDAP_INSUFFICIENT_ACCESS: + error(ERR_ACCESS_DENIED, msg); + break; + case LDAP_CONNECT_ERROR: + case LDAP_SERVER_DOWN: + error(ERR_COULD_NOT_CONNECT,msg); + break; + case LDAP_TIMEOUT: + error(ERR_SERVER_TIMEOUT,msg); + break; + case LDAP_PARAM_ERROR: + error(ERR_INTERNAL,msg); + break; + case LDAP_NO_MEMORY: + error(ERR_OUT_OF_MEMORY,msg); + break; + + default: + error( ERR_SLAVE_DEFINED, + i18n( "LDAP server returned the error: %1 %2\nThe LDAP URL was: %3" ). + arg( ldap_err2string(err)).arg( extraMsg ).arg( url.prettyURL() ) ); + } +} + +void LDAPProtocol::controlsFromMetaData( LDAPControl ***serverctrls, + LDAPControl ***clientctrls ) +{ + QString oid; bool critical; QByteArray value; + int i = 0; + while ( hasMetaData( QString::fromLatin1("SERVER_CTRL%1").arg(i) ) ) { + QCString val = metaData( QString::fromLatin1("SERVER_CTRL%1").arg(i) ).utf8(); + LDIF::splitControl( val, oid, critical, value ); + kdDebug(7125) << "server ctrl #" << i << " value: " << val << + " oid: " << oid << " critical: " << critical << " value: " << + QString::fromUtf8( value, value.size() ) << endl; + addControlOp( serverctrls, oid, value, critical ); + i++; + } + i = 0; + while ( hasMetaData( QString::fromLatin1("CLIENT_CTRL%1").arg(i) ) ) { + QCString val = metaData( QString::fromLatin1("CLIENT_CTRL%1").arg(i) ).utf8(); + LDIF::splitControl( val, oid, critical, value ); + kdDebug(7125) << "client ctrl #" << i << " value: " << val << + " oid: " << oid << " critical: " << critical << " value: " << + QString::fromUtf8( value, value.size() ) << endl; + addControlOp( clientctrls, oid, value, critical ); + i++; + } +} + +int LDAPProtocol::asyncSearch( LDAPUrl &usrc ) +{ + char **attrs = 0; + int msgid; + LDAPControl **serverctrls = 0, **clientctrls = 0; + + int count = usrc.attributes().count(); + if ( count > 0 ) { + attrs = static_cast<char**>( malloc((count+1) * sizeof(char*)) ); + for (int i=0; i<count; i++) + attrs[i] = strdup( (*usrc.attributes().at(i)).utf8() ); + attrs[count] = 0; + } + + int retval, scope = LDAP_SCOPE_BASE; + switch ( usrc.scope() ) { + case LDAPUrl::Base: + scope = LDAP_SCOPE_BASE; + break; + case LDAPUrl::One: + scope = LDAP_SCOPE_ONELEVEL; + break; + case LDAPUrl::Sub: + scope = LDAP_SCOPE_SUBTREE; + break; + } + + controlsFromMetaData( &serverctrls, &clientctrls ); + + kdDebug(7125) << "asyncSearch() dn=\"" << usrc.dn() << "\" scope=" << + usrc.scope() << " filter=\"" << usrc.filter() << "\" attrs=" << usrc.attributes() << + endl; + retval = ldap_search_ext( mLDAP, usrc.dn().utf8(), scope, + usrc.filter().isEmpty() ? QCString() : usrc.filter().utf8(), attrs, 0, + serverctrls, clientctrls, + 0, mSizeLimit, &msgid ); + + ldap_controls_free( serverctrls ); + ldap_controls_free( clientctrls ); + + // free the attributes list again + if ( count > 0 ) { + for ( int i=0; i<count; i++ ) free( attrs[i] ); + free(attrs); + } + + if ( retval == 0 ) retval = msgid; + return retval; +} + +QCString LDAPProtocol::LDAPEntryAsLDIF( LDAPMessage *message ) +{ + QCString result; + char *name; + struct berval **bvals; + BerElement *entry; + QByteArray tmp; + + char *dn = ldap_get_dn( mLDAP, message ); + if ( dn == NULL ) return QCString( "" ); + tmp.setRawData( dn, strlen( dn ) ); + result += LDIF::assembleLine( "dn", tmp ) + '\n'; + tmp.resetRawData( dn, strlen( dn ) ); + ldap_memfree( dn ); + + // iterate over the attributes + name = ldap_first_attribute(mLDAP, message, &entry); + while ( name != 0 ) + { + // print the values + bvals = ldap_get_values_len(mLDAP, message, name); + if ( bvals ) { + + for ( int i = 0; bvals[i] != 0; i++ ) { + char* val = bvals[i]->bv_val; + unsigned long len = bvals[i]->bv_len; + tmp.setRawData( val, len ); + result += LDIF::assembleLine( QString::fromUtf8( name ), tmp, 76 ) + '\n'; + tmp.resetRawData( val, len ); + } + ldap_value_free_len(bvals); + } + ldap_memfree( name ); + // next attribute + name = ldap_next_attribute(mLDAP, message, entry); + } + return result; +} + +void LDAPProtocol::addControlOp( LDAPControl ***pctrls, const QString &oid, + const QByteArray &value, bool critical ) +{ + LDAPControl **ctrls; + LDAPControl *ctrl = (LDAPControl *) malloc( sizeof( LDAPControl ) ); + + ctrls = *pctrls; + + kdDebug(7125) << "addControlOp: oid:'" << oid << "' val: '" << + QString::fromUtf8(value, value.size()) << "'" << endl; + int vallen = value.size(); + ctrl->ldctl_value.bv_len = vallen; + if ( vallen ) { + ctrl->ldctl_value.bv_val = (char*) malloc( vallen ); + memcpy( ctrl->ldctl_value.bv_val, value.data(), vallen ); + } else { + ctrl->ldctl_value.bv_val = 0; + } + ctrl->ldctl_iscritical = critical; + ctrl->ldctl_oid = strdup( oid.utf8() ); + + uint i = 0; + + if ( ctrls == 0 ) { + ctrls = (LDAPControl **) malloc ( 2 * sizeof( LDAPControl* ) ); + ctrls[ 0 ] = 0; + ctrls[ 1 ] = 0; + } else { + while ( ctrls[ i ] != 0 ) i++; + ctrls[ i + 1 ] = 0; + ctrls = (LDAPControl **) realloc( ctrls, (i + 2) * sizeof( LDAPControl * ) ); + } + ctrls[ i ] = ctrl; + + *pctrls = ctrls; +} + +void LDAPProtocol::addModOp( LDAPMod ***pmods, int mod_type, const QString &attr, + const QByteArray &value ) +{ +// kdDebug(7125) << "type: " << mod_type << " attr: " << attr << +// " value: " << QString::fromUtf8(value,value.size()) << +// " size: " << value.size() << endl; + LDAPMod **mods; + + mods = *pmods; + + uint i = 0; + + if ( mods == 0 ) { + mods = (LDAPMod **) malloc ( 2 * sizeof( LDAPMod* ) ); + mods[ 0 ] = (LDAPMod*) malloc( sizeof( LDAPMod ) ); + mods[ 1 ] = 0; + memset( mods[ 0 ], 0, sizeof( LDAPMod ) ); + } else { + while( mods[ i ] != 0 && + ( strcmp( attr.utf8(),mods[i]->mod_type ) != 0 || + ( mods[ i ]->mod_op & ~LDAP_MOD_BVALUES ) != mod_type ) ) i++; + + if ( mods[ i ] == 0 ) { + mods = ( LDAPMod ** )realloc( mods, (i + 2) * sizeof( LDAPMod * ) ); + if ( mods == 0 ) { + kdError() << "addModOp: realloc" << endl; + return; + } + mods[ i + 1 ] = 0; + mods[ i ] = ( LDAPMod* ) malloc( sizeof( LDAPMod ) ); + memset( mods[ i ], 0, sizeof( LDAPMod ) ); + } + } + + mods[ i ]->mod_op = mod_type | LDAP_MOD_BVALUES; + if ( mods[ i ]->mod_type == 0 ) mods[ i ]->mod_type = strdup( attr.utf8() ); + + *pmods = mods; + + int vallen = value.size(); + if ( vallen == 0 ) return; + BerValue *berval; + berval = ( BerValue* ) malloc( sizeof( BerValue ) ); + berval -> bv_val = (char*) malloc( vallen ); + berval -> bv_len = vallen; + memcpy( berval -> bv_val, value.data(), vallen ); + + if ( mods[ i ] -> mod_vals.modv_bvals == 0 ) { + mods[ i ]->mod_vals.modv_bvals = ( BerValue** ) malloc( sizeof( BerValue* ) * 2 ); + mods[ i ]->mod_vals.modv_bvals[ 0 ] = berval; + mods[ i ]->mod_vals.modv_bvals[ 1 ] = 0; + kdDebug(7125) << "addModOp: new bervalue struct " << endl; + } else { + uint j = 0; + while ( mods[ i ]->mod_vals.modv_bvals[ j ] != 0 ) j++; + mods[ i ]->mod_vals.modv_bvals = ( BerValue ** ) + realloc( mods[ i ]->mod_vals.modv_bvals, (j + 2) * sizeof( BerValue* ) ); + if ( mods[ i ]->mod_vals.modv_bvals == 0 ) { + kdError() << "addModOp: realloc" << endl; + return; + } + mods[ i ]->mod_vals.modv_bvals[ j ] = berval; + mods[ i ]->mod_vals.modv_bvals[ j+1 ] = 0; + kdDebug(7125) << j << ". new bervalue " << endl; + } +} + +void LDAPProtocol::LDAPEntry2UDSEntry( const QString &dn, UDSEntry &entry, + const LDAPUrl &usrc, bool dir ) +{ + UDSAtom atom; + + int pos; + entry.clear(); + atom.m_uds = UDS_NAME; + atom.m_long = 0; + QString name = dn; + if ( (pos = name.find(",")) > 0 ) + name = name.left( pos ); + if ( (pos = name.find("=")) > 0 ) + name.remove( 0, pos+1 ); + name.replace(' ', "_"); + if ( !dir ) name += ".ldif"; + atom.m_str = name; + entry.append( atom ); + + // the file type + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = dir ? S_IFDIR : S_IFREG; + entry.append( atom ); + + // the mimetype + if (!dir) { + atom.m_uds = UDS_MIME_TYPE; + atom.m_long = 0; + atom.m_str = "text/plain"; + entry.append( atom ); + } + + atom.m_uds = UDS_ACCESS; + atom.m_long = dir ? 0500 : 0400; + entry.append( atom ); + + // the url + atom.m_uds = UDS_URL; + atom.m_long = 0; + LDAPUrl url; + url=usrc; + + url.setPath("/"+dn); + url.setScope( dir ? LDAPUrl::One : LDAPUrl::Base ); + atom.m_str = url.prettyURL(); + entry.append( atom ); +} + +void LDAPProtocol::changeCheck( LDAPUrl &url ) +{ + bool critical; + bool tls = ( url.hasExtension( "x-tls" ) ); + int ver = 3; + if ( url.hasExtension( "x-ver" ) ) + ver = url.extension( "x-ver", critical).toInt(); + bool authSASL = url.hasExtension( "x-sasl" ); + QString mech; + if ( url.hasExtension( "x-mech" ) ) + mech = url.extension( "x-mech", critical).upper(); + QString realm; + if ( url.hasExtension( "x-realm" ) ) + mech = url.extension( "x-realm", critical).upper(); + QString bindname; + if ( url.hasExtension( "bindname" ) ) + bindname = url.extension( "bindname", critical).upper(); + int timelimit = 0; + if ( url.hasExtension( "x-timelimit" ) ) + timelimit = url.extension( "x-timelimit", critical).toInt(); + int sizelimit = 0; + if ( url.hasExtension( "x-sizelimit" ) ) + sizelimit = url.extension( "x-sizelimit", critical).toInt(); + + if ( !authSASL && bindname.isEmpty() ) bindname = mUser; + + if ( tls != mTLS || ver != mVer || authSASL != mAuthSASL || mech != mMech || + mRealm != realm || mBindName != bindname || mTimeLimit != timelimit || + mSizeLimit != sizelimit ) { + closeConnection(); + mTLS = tls; + mVer = ver; + mAuthSASL = authSASL; + mMech = mech; + mRealm = realm; + mBindName = bindname; + mTimeLimit = timelimit; + mSizeLimit = sizelimit; + kdDebug(7125) << "parameters changed: tls = " << mTLS << + " version: " << mVer << "SASLauth: " << mAuthSASL << endl; + openConnection(); + if ( mAuthSASL ) { + url.setUser( mUser ); + } else { + url.setUser( mBindName ); + } + } else { + if ( !mLDAP ) openConnection(); + } +} + +void LDAPProtocol::setHost( const QString& host, int port, + const QString& user, const QString& password ) +{ + + if( mHost != host || mPort != port || mUser != user || mPassword != password ) + closeConnection(); + + mHost = host; + if( port > 0 ) + mPort = port; + else { + struct servent *pse; + if ( (pse = getservbyname(mProtocol, "tcp")) == NULL ) + if ( mProtocol == "ldaps" ) + mPort = 636; + else + mPort = 389; + else + mPort = ntohs( pse->s_port ); + } + mUser = user; + mPassword = password; + + kdDebug(7125) << "setHost: " << host << " port: " << port << " user: " << + mUser << " pass: [protected]" << endl; +} + +static int kldap_sasl_interact( LDAP *, unsigned, void *slave, void *in ) +{ + return ((LDAPProtocol*) slave)->saslInteract( in ); +} + +void LDAPProtocol::fillAuthInfo( AuthInfo &info ) +{ + info.url.setProtocol( mProtocol ); + info.url.setHost( mHost ); + info.url.setPort( mPort ); + info.url.setUser( mUser ); + info.caption = i18n("LDAP Login"); + info.comment = QString::fromLatin1( mProtocol ) + "://" + mHost + ":" + + QString::number( mPort ); + info.commentLabel = i18n("site:"); + info.username = mAuthSASL ? mUser : mBindName; + info.password = mPassword; + info.keepPassword = true; +} + +int LDAPProtocol::saslInteract( void *in ) +{ +#if defined HAVE_SASL_H || defined HAVE_SASL_SASL_H + AuthInfo info; + fillAuthInfo( info ); + + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so it doesn't need a popup + //window for getting this info + for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { + if ( interact->id == SASL_CB_AUTHNAME || + interact->id == SASL_CB_PASS ) { + + if ( info.username.isEmpty() || info.password.isEmpty() ) { + + const bool cached = checkCachedAuthentication( info ); + + if ( ! ( ( mFirstAuth && cached ) || + ( mFirstAuth ? + openPassDlg( info ) : + openPassDlg( info, i18n("Invalid authorization information.") ) ) ) ) { + kdDebug(7125) << "Dialog cancelled!" << endl; + mCancel = true; + return LDAP_USER_CANCELLED; + } + mUser = info.username; + mPassword = info.password; + } + break; + } + } + + interact = ( sasl_interact_t * ) in; + QString value; + + while( interact->id != SASL_CB_LIST_END ) { + value = ""; + switch( interact->id ) { + case SASL_CB_GETREALM: + value = mRealm; + kdDebug(7125) << "SASL_REALM=" << mRealm << endl; + break; + case SASL_CB_AUTHNAME: + value = mUser; + kdDebug(7125) << "SASL_AUTHNAME=" << mUser << endl; + break; + case SASL_CB_PASS: + value = mPassword; + kdDebug(7125) << "SASL_PASSWD=[hidden]" << endl; + break; + case SASL_CB_USER: + value = mBindName; + kdDebug(7125) << "SASL_AUTHZID=" << mBindName << endl; + break; + } + if ( value.isEmpty() ) { + interact->result = NULL; + interact->len = 0; + } else { + interact->result = strdup( value.utf8() ); + interact->len = strlen( (const char *) interact->result ); + } + interact++; + } + +#endif + return LDAP_SUCCESS; +} + +void LDAPProtocol::openConnection() +{ + if ( mLDAP ) return; + + int version,ret; + + version = ( mVer == 2 ) ? LDAP_VERSION2 : LDAP_VERSION3; + + KURL Url; + Url.setProtocol( mProtocol ); + Url.setHost( mHost ); + Url.setPort( mPort ); + + AuthInfo info; + fillAuthInfo( info ); +/////////////////////////////////////////////////////////////////////////// + kdDebug(7125) << "OpenConnection to " << mHost << ":" << mPort << endl; + + ret = ldap_initialize( &mLDAP, Url.htmlURL().utf8() ); + if ( ret != LDAP_SUCCESS ) { + LDAPErr( Url, ret ); + return; + } + + if ( (ldap_set_option( mLDAP, LDAP_OPT_PROTOCOL_VERSION, &version )) != + LDAP_OPT_SUCCESS ) { + + closeConnection(); + error( ERR_UNSUPPORTED_ACTION, + i18n("Cannot set LDAP protocol version %1").arg(version) ); + return; + } + + if ( mTLS ) { + kdDebug(7125) << "start TLS" << endl; + if ( ( ret = ldap_start_tls_s( mLDAP, NULL, NULL ) ) != LDAP_SUCCESS ) { + LDAPErr( Url ); + return; + } + } + + if ( mSizeLimit ) { + kdDebug(7125) << "sizelimit: " << mSizeLimit << endl; + if ( ldap_set_option( mLDAP, LDAP_OPT_SIZELIMIT, &mSizeLimit ) != LDAP_SUCCESS ) { + closeConnection(); + error( ERR_UNSUPPORTED_ACTION, + i18n("Cannot set size limit.")); + return; + } + } + + if ( mTimeLimit ) { + kdDebug(7125) << "timelimit: " << mTimeLimit << endl; + if ( ldap_set_option( mLDAP, LDAP_OPT_TIMELIMIT, &mTimeLimit ) != LDAP_SUCCESS ) { + closeConnection(); + error( ERR_UNSUPPORTED_ACTION, + i18n("Cannot set time limit.")); + return; + } + } + +#if !defined HAVE_SASL_H && !defined HAVE_SASL_SASL_H + if ( mAuthSASL ) { + closeConnection(); + error( ERR_SLAVE_DEFINED, + i18n("SASL authentication not compiled into the ldap ioslave.") ); + return; + } +#endif + + bool auth = false; + QString mechanism = mMech.isEmpty() ? "DIGEST-MD5" : mMech; + mFirstAuth = true; mCancel = false; + + const bool cached = checkCachedAuthentication( info ); + + ret = LDAP_SUCCESS; + while (!auth) { + if ( !mAuthSASL && ( + ( mFirstAuth && + !( mBindName.isEmpty() && mPassword.isEmpty() ) && //For anonymous bind + ( mBindName.isEmpty() || mPassword.isEmpty() ) ) || !mFirstAuth ) ) + { + if ( ( mFirstAuth && cached ) || + ( mFirstAuth ? + openPassDlg( info ) : + openPassDlg( info, i18n("Invalid authorization information.") ) ) ) { + + mBindName = info.username; + mPassword = info.password; + } else { + kdDebug(7125) << "Dialog cancelled!" << endl; + error( ERR_USER_CANCELED, QString::null ); + closeConnection(); + return; + } + } + kdDebug(7125) << "user: " << mUser << " bindname: " << mBindName << endl; + ret = +#if defined HAVE_SASL_H || defined HAVE_SASL_SASL_H + mAuthSASL ? + ldap_sasl_interactive_bind_s( mLDAP, NULL, mechanism.utf8(), + NULL, NULL, LDAP_SASL_INTERACTIVE, &kldap_sasl_interact, this ) : +#endif + ldap_simple_bind_s( mLDAP, mBindName.utf8(), mPassword.utf8() ); + + mFirstAuth = false; + if ( ret != LDAP_INVALID_CREDENTIALS && + ret != LDAP_INSUFFICIENT_ACCESS && + ret != LDAP_INAPPROPRIATE_AUTH ) { + kdDebug(7125) << "ldap_bind retval: " << ret << endl; + auth = true; + if ( ret != LDAP_SUCCESS ) { + if ( mCancel ) + error( ERR_USER_CANCELED, QString::null ); + else + LDAPErr( Url ); + closeConnection(); + return; + } + } + } + + kdDebug(7125) << "connected!" << endl; + connected(); +} + +void LDAPProtocol::closeConnection() +{ + if (mLDAP) ldap_unbind(mLDAP); + mLDAP = 0; + kdDebug(7125) << "connection closed!" << endl; +} + +/** + * Get the information contained in the URL. + */ +void LDAPProtocol::get( const KURL &_url ) +{ + kdDebug(7125) << "get(" << _url << ")" << endl; + + LDAPUrl usrc(_url); + int ret, id; + LDAPMessage *msg,*entry; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + if ( (id = asyncSearch( usrc )) == -1 ) { + LDAPErr( _url ); + return; + } + + // tell the mimetype + mimeType("text/plain"); + // collect the result + QCString result; + filesize_t processed_size = 0; + QByteArray array; + + while( true ) { + ret = ldap_result( mLDAP, id, 0, NULL, &msg ); + if ( ret == -1 ) { + LDAPErr( _url ); + return; + } + kdDebug(7125) << " ldap_result: " << ret << endl; + if ( ret == LDAP_RES_SEARCH_RESULT ) break; + if ( ret != LDAP_RES_SEARCH_ENTRY ) continue; + + entry = ldap_first_entry( mLDAP, msg ); + while ( entry ) { + result = LDAPEntryAsLDIF(entry); + result += '\n'; + uint len = result.length(); + processed_size += len; + array.setRawData( result.data(), len ); + data(array); + processedSize( processed_size ); + array.resetRawData( result.data(), len ); + + entry = ldap_next_entry( mLDAP, entry ); + } + LDAPErr( _url ); + + ldap_msgfree(msg); + // tell the length + } + + totalSize(processed_size); + + array.resize(0); + // tell we are finished + data(array); + + // tell we are finished + finished(); +} + +/** + * Test if the url contains a directory or a file. + */ +void LDAPProtocol::stat( const KURL &_url ) +{ + kdDebug(7125) << "stat(" << _url << ")" << endl; + + QStringList att,saveatt; + LDAPUrl usrc(_url); + LDAPMessage *msg; + int ret, id; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + // look how many entries match + saveatt = usrc.attributes(); + att.append( "dn" ); + usrc.setAttributes( att ); + if ( _url.query().isEmpty() ) usrc.setScope( LDAPUrl::One ); + + if ( (id = asyncSearch( usrc )) == -1 ) { + LDAPErr( _url ); + return; + } + + kdDebug(7125) << "stat() getting result" << endl; + do { + ret = ldap_result( mLDAP, id, 0, NULL, &msg ); + if ( ret == -1 ) { + LDAPErr( _url ); + return; + } + if ( ret == LDAP_RES_SEARCH_RESULT ) { + ldap_msgfree( msg ); + error( ERR_DOES_NOT_EXIST, _url.prettyURL() ); + return; + } + } while ( ret != LDAP_RES_SEARCH_ENTRY ); + + ldap_msgfree( msg ); + ldap_abandon( mLDAP, id ); + + usrc.setAttributes( saveatt ); + + UDSEntry uds; + bool critical; + LDAPEntry2UDSEntry( usrc.dn(), uds, usrc, usrc.extension("x-dir", critical) != "base" ); + + statEntry( uds ); + // we are done + finished(); +} + +/** + * Deletes one entry; + */ +void LDAPProtocol::del( const KURL &_url, bool ) +{ + kdDebug(7125) << "del(" << _url << ")" << endl; + + LDAPUrl usrc(_url); + int ret; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + kdDebug(7125) << " del: " << usrc.dn().utf8() << endl ; + + if ( (ret = ldap_delete_s( mLDAP,usrc.dn().utf8() )) != LDAP_SUCCESS ) { + LDAPErr( _url ); + return; + } + finished(); +} + +#define FREELDAPMEM { \ + ldap_mods_free( lmod, 1 ); \ + ldap_controls_free( serverctrls ); \ + ldap_controls_free( clientctrls ); \ + lmod = 0; serverctrls = 0; clientctrls = 0; \ + } + +void LDAPProtocol::put( const KURL &_url, int, bool overwrite, bool ) +{ + kdDebug(7125) << "put(" << _url << ")" << endl; + + LDAPUrl usrc(_url); + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + + LDAPMod **lmod = 0; + LDAPControl **serverctrls = 0, **clientctrls = 0; + QByteArray buffer; + int result = 0; + LDIF::ParseVal ret; + LDIF ldif; + ret = LDIF::MoreData; + int ldaperr; + + + do { + if ( ret == LDIF::MoreData ) { + dataReq(); // Request for data + result = readData( buffer ); + ldif.setLDIF( buffer ); + } + if ( result < 0 ) { + //error + FREELDAPMEM; + return; + } + if ( result == 0 ) { + kdDebug(7125) << "EOF!" << endl; + ldif.endLDIF(); + } + do { + + ret = ldif.nextItem(); + kdDebug(7125) << "nextitem: " << ret << endl; + + switch ( ret ) { + case LDIF::None: + case LDIF::NewEntry: + case LDIF::MoreData: + break; + case LDIF::EndEntry: + ldaperr = LDAP_SUCCESS; + switch ( ldif.entryType() ) { + case LDIF::Entry_None: + error( ERR_INTERNAL, i18n("The LDIF parser failed.") ); + FREELDAPMEM; + return; + case LDIF::Entry_Del: + kdDebug(7125) << "kio_ldap_del" << endl; + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_delete_ext_s( mLDAP, ldif.dn().utf8(), + serverctrls, clientctrls ); + FREELDAPMEM; + break; + case LDIF::Entry_Modrdn: + kdDebug(7125) << "kio_ldap_modrdn olddn:" << ldif.dn() << + " newRdn: " << ldif.newRdn() << + " newSuperior: " << ldif.newSuperior() << + " deloldrdn: " << ldif.delOldRdn() << endl; + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_rename_s( mLDAP, ldif.dn().utf8(), ldif.newRdn().utf8(), + ldif.newSuperior().isEmpty() ? QCString() : ldif.newSuperior().utf8(), + ldif.delOldRdn(), serverctrls, clientctrls ); + + FREELDAPMEM; + break; + case LDIF::Entry_Mod: + kdDebug(7125) << "kio_ldap_mod" << endl; + if ( lmod ) { + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_modify_ext_s( mLDAP, ldif.dn().utf8(), lmod, + serverctrls, clientctrls ); + FREELDAPMEM; + } + break; + case LDIF::Entry_Add: + kdDebug(7125) << "kio_ldap_add " << ldif.dn() << endl; + if ( lmod ) { + controlsFromMetaData( &serverctrls, &clientctrls ); + ldaperr = ldap_add_ext_s( mLDAP, ldif.dn().utf8(), lmod, + serverctrls, clientctrls ); + if ( ldaperr == LDAP_ALREADY_EXISTS && overwrite ) { + kdDebug(7125) << ldif.dn() << " already exists, delete first" << endl; + ldaperr = ldap_delete_s( mLDAP, ldif.dn().utf8() ); + if ( ldaperr == LDAP_SUCCESS ) + ldaperr = ldap_add_ext_s( mLDAP, ldif.dn().utf8(), lmod, + serverctrls, clientctrls ); + } + FREELDAPMEM; + } + break; + } + if ( ldaperr != LDAP_SUCCESS ) { + kdDebug(7125) << "put ldap error: " << ldap_err2string(ldaperr) << endl; + LDAPErr( _url ); + FREELDAPMEM; + return; + } + break; + case LDIF::Item: + switch ( ldif.entryType() ) { + case LDIF::Entry_Mod: { + int modtype = 0; + switch ( ldif.modType() ) { + case LDIF::Mod_None: + modtype = 0; + break; + case LDIF::Mod_Add: + modtype = LDAP_MOD_ADD; + break; + case LDIF::Mod_Replace: + modtype = LDAP_MOD_REPLACE; + break; + case LDIF::Mod_Del: + modtype = LDAP_MOD_DELETE; + break; + } + addModOp( &lmod, modtype, ldif.attr(), ldif.val() ); + break; + } + case LDIF::Entry_Add: + if ( ldif.val().size() > 0 ) + addModOp( &lmod, 0, ldif.attr(), ldif.val() ); + break; + default: + error( ERR_INTERNAL, i18n("The LDIF parser failed.") ); + FREELDAPMEM; + return; + } + break; + case LDIF::Control: + addControlOp( &serverctrls, ldif.oid(), ldif.val(), ldif.critical() ); + break; + case LDIF::Err: + error( ERR_SLAVE_DEFINED, + i18n( "Invalid LDIF file in line %1." ).arg( ldif.lineNo() ) ); + FREELDAPMEM; + return; + } + } while ( ret != LDIF::MoreData ); + } while ( result > 0 ); + + FREELDAPMEM; + finished(); +} + +/** + * List the contents of a directory. + */ +void LDAPProtocol::listDir( const KURL &_url ) +{ + int ret, ret2, id, id2; + unsigned long total=0; + char *dn; + QStringList att,saveatt; + LDAPMessage *entry,*msg,*entry2,*msg2; + LDAPUrl usrc(_url),usrc2; + bool critical; + bool isSub = ( usrc.extension( "x-dir", critical ) == "sub" ); + + kdDebug(7125) << "listDir(" << _url << ")" << endl; + + changeCheck( usrc ); + if ( !mLDAP ) { + finished(); + return; + } + usrc2 = usrc; + + saveatt = usrc.attributes(); + // look up the entries + if ( isSub ) { + att.append("dn"); + usrc.setAttributes(att); + } + if ( _url.query().isEmpty() ) usrc.setScope( LDAPUrl::One ); + + if ( (id = asyncSearch( usrc )) == -1 ) { + LDAPErr( _url ); + return; + } + + usrc.setAttributes( "" ); + usrc.setExtension( "x-dir", "base" ); + // publish the results + UDSEntry uds; + + while( true ) { + ret = ldap_result( mLDAP, id, 0, NULL, &msg ); + if ( ret == -1 ) { + LDAPErr( _url ); + return; + } + if ( ret == LDAP_RES_SEARCH_RESULT ) break; + if ( ret != LDAP_RES_SEARCH_ENTRY ) continue; + kdDebug(7125) << " ldap_result: " << ret << endl; + + entry = ldap_first_entry( mLDAP, msg ); + while( entry ) { + + total++; + uds.clear(); + + dn = ldap_get_dn( mLDAP, entry ); + kdDebug(7125) << "dn: " << dn << endl; + LDAPEntry2UDSEntry( QString::fromUtf8(dn), uds, usrc ); + listEntry( uds, false ); +// processedSize( total ); + kdDebug(7125) << " total: " << total << " " << usrc.prettyURL() << endl; + + // publish the sub-directories (if dirmode==sub) + if ( isSub ) { + usrc2.setDn( QString::fromUtf8( dn ) ); + usrc2.setScope( LDAPUrl::One ); + usrc2.setAttributes( att ); + usrc2.setFilter( QString::null ); + kdDebug(7125) << "search2 " << dn << endl; + if ( (id2 = asyncSearch( usrc2 )) != -1 ) { + while ( true ) { + kdDebug(7125) << " next result " << endl; + ret2 = ldap_result( mLDAP, id2, 0, NULL, &msg2 ); + if ( ret2 == -1 ) break; + if ( ret2 == LDAP_RES_SEARCH_RESULT ) { + ldap_msgfree( msg2 ); + break; + } + if ( ret2 == LDAP_RES_SEARCH_ENTRY ) { + entry2=ldap_first_entry( mLDAP, msg2 ); + if ( entry2 ) { + usrc2.setAttributes( saveatt ); + usrc2.setFilter( usrc.filter() ); + LDAPEntry2UDSEntry( QString::fromUtf8( dn ), uds, usrc2, true ); + listEntry( uds, false ); + total++; + } + ldap_msgfree( msg2 ); + ldap_abandon( mLDAP, id2 ); + break; + } + } + } + } + free( dn ); + + entry = ldap_next_entry( mLDAP, entry ); + } + LDAPErr( _url ); + ldap_msgfree( msg ); + } + +// totalSize( total ); + + uds.clear(); + listEntry( uds, true ); + // we are done + finished(); +} diff --git a/kioslave/ldap/kio_ldap.h b/kioslave/ldap/kio_ldap.h new file mode 100644 index 000000000..ff722d345 --- /dev/null +++ b/kioslave/ldap/kio_ldap.h @@ -0,0 +1,65 @@ +#ifndef __LDAP_H__ +#define __LDAP_H__ + +#include <qstring.h> +#include <qvaluelist.h> + +#include <kio/slavebase.h> +#include <kio/authinfo.h> + +#define LDAP_DEPRECATED 1 /* Needed for ldap_simple_bind_s with openldap >= 2.3.x */ +#include <lber.h> +#include <ldap.h> +#include <kabc/ldapurl.h> + +class LDAPProtocol : public KIO::SlaveBase +{ + public: + LDAPProtocol( const QCString &protocol, const QCString &pool, const QCString &app ); + virtual ~LDAPProtocol(); + + virtual void setHost( const QString& host, int port, + const QString& user, const QString& pass ); + + virtual void openConnection(); + virtual void closeConnection(); + + virtual void get( const KURL& url ); + virtual void stat( const KURL& url ); + virtual void listDir( const KURL& url ); + virtual void del( const KURL& url, bool isfile ); + virtual void put( const KURL& url, int permissions, bool overwrite, bool resume ); + + int saslInteract( void *in ); + + private: + + QString mHost; + int mPort; + QString mUser; + QString mPassword; + LDAP *mLDAP; + int mVer, mSizeLimit, mTimeLimit; + bool mTLS; + bool mAuthSASL; + QString mMech,mRealm,mBindName; + bool mCancel, mFirstAuth; + + void controlsFromMetaData( LDAPControl ***serverctrls, + LDAPControl ***clientctrls ); + void addControlOp( LDAPControl ***pctrls, const QString &oid, + const QByteArray &value, bool critical ); + void addModOp( LDAPMod ***pmods, int mod_type, + const QString &attr, const QByteArray &value ); + void LDAPEntry2UDSEntry( const QString &dn, KIO::UDSEntry &entry, + const KABC::LDAPUrl &usrc, bool dir=false ); + int asyncSearch( KABC::LDAPUrl &usrc ); + + QCString LDAPEntryAsLDIF( LDAPMessage *msg ); + void LDAPErr( const KURL &url, int err = LDAP_SUCCESS ); + void changeCheck( KABC::LDAPUrl &url ); + + void fillAuthInfo( KIO::AuthInfo &info ); +}; + +#endif diff --git a/kioslave/ldap/ldap.protocol b/kioslave/ldap/ldap.protocol new file mode 100644 index 000000000..3ab0b7eb5 --- /dev/null +++ b/kioslave/ldap/ldap.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_ldap +protocol=ldap +input=none +output=filesystem +listing=Name, +reading=true +source=true +writing=true +#makedir=true +deleting=true +#linking=true +#moving=true +mimetype=text/plain +determineMimetypeFromExtension=false +DocPath=kioslave/ldap.html +Icon=kaddressbook diff --git a/kioslave/ldap/ldaps.protocol b/kioslave/ldap/ldaps.protocol new file mode 100644 index 000000000..542faa597 --- /dev/null +++ b/kioslave/ldap/ldaps.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_ldap +protocol=ldaps +input=none +output=filesystem +listing=Name, +reading=true +source=true +writing=true +#makedir=true +deleting=true +#linking=true +#moving=true +mimetype=text/plain +determineMimetypeFromExtension=false +DocPath=kioslave/ldap.html +Icon=kaddressbook diff --git a/kioslave/mac/AUTHORS b/kioslave/mac/AUTHORS new file mode 100644 index 000000000..78b940be4 --- /dev/null +++ b/kioslave/mac/AUTHORS @@ -0,0 +1 @@ +Jonathan Riddell, jr@jriddell.org diff --git a/kioslave/mac/ChangeLog b/kioslave/mac/ChangeLog new file mode 100644 index 000000000..6c8f647ce --- /dev/null +++ b/kioslave/mac/ChangeLog @@ -0,0 +1,39 @@ +10 Feb 2002 - Jonathan Riddell <jr@jriddell.org> + - v1.0 + - Nicer icon (thanks to ikons project) + - moved into KDE CVS kdenonbeta + - Everything seems to be stable, lets up the version number to prove me wrong + +1 Feb 2002 - Jonathan Riddell <jr@jriddell.org> + - v0.8 + - Now displays hidden files + - Locked files are copied as read only + - Nice icon + - sources now use autoconf/automake + - Fixed regular expression which matches some files as directories + - Aliases now display as links + +26 Jan 2002 - Jonathan Riddell <jr@jriddell.org> + - v0.7 + - Converts some HFS+ file types and application labels into mimetypes + - Added a SuSE Makefile + - Hopefully managed to get the SuSE RPMs working + - When copying files kio-mac now reports the amount progressed so + you can see how much has been copied + - Text files are now copies over in text mode by default + +24 Jan2002 - Jonathan Riddell <jr@jriddell.org> + - v0.6 + - It can now read empty directories without complaining + - Fixed the way data was being passed back to KDE which corrupted some files + - Fixed Makefile a bit + - Now reports the modified date to as good an acuracy as hpls -l gives it + - Found a truly bizarre bug while doing the above which broke + things a lot less than it should have + +21 Jan 2002 - Jonathan Riddell <jr@jriddell.org> + - v0.5 + - Initial release + - talks to hfs+ partitions using hptools + - surprisingly successful + diff --git a/kioslave/mac/Makefile.am b/kioslave/mac/Makefile.am new file mode 100644 index 000000000..f2f0d97fb --- /dev/null +++ b/kioslave/mac/Makefile.am @@ -0,0 +1,23 @@ +## Makfile.am for kio_mac + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +####### Files + +kde_module_LTLIBRARIES = kio_mac.la + +kio_mac_la_SOURCES = kio_mac.cpp +kio_mac_la_LIBADD = -lkio +kio_mac_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_mac.h + +kdelnk_DATA = mac.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO +KDE_ICON = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_mac.pot diff --git a/kioslave/mac/README b/kioslave/mac/README new file mode 100644 index 000000000..bb907dd9c --- /dev/null +++ b/kioslave/mac/README @@ -0,0 +1,65 @@ +From the hfsplus man page: + + "HFS+, also known as the Macintosh Extended Format, was + introduced by Apple Computer in 1998 with the release of + MacOS 8.1. It contains many improvements over the old HFS + file system, most notably the ability to allocate up to + 2^64 blocks, resulting in much more efficient storage of + many small files on large disks." + +This kio slave lets you read an HFS+ partition from konqueror +or any other KDE file dialogue. It uses hfsplus tools so you will +need these installed for it to work. + +TO INSTALL + +Read the INSTALL file. + + +NOTES + +Just enter mac:/ into Konqueror and you should see the contents of +your MacOS partition. Actually you'll probably get an error message +saying you havn't specified the right partition. Enter something +like mac:/?dev=/dev/hda2 to specify the partition (if you don't know +which partition MacOS is on you can probably guess by changing hda2 to +hda3 and so on or use the print command from mac-fdisk. The partition +will be used the next time so you don't have to specify it each time. + +Hfsplus tools let you see the file and copy data from the HFS+ +partition but not to copy data to it or change the filenames or such like. + +HFS+ actually keeps two files for every one you see (called forks), a +resource fork and a data fork. The default copy mode when you're +copying files across to you native drive is raw data which means it +just copies the data. Text files are copied in text mode (same as raw +format but changes the line endings to be Unix friendly and gets rid +of some funny extra characters - strongly advised for text files) +unless you specify otherwise. You can also copy the files across in +Mac Binary II format or specify text or raw format with another query: +mac:/myfile?mode=b or mac:/myfile?mode=t See man hpcopy for more. + +Note that you need permissions to read your HFS+ partition. How you +get this depends on your distribution, do a ls -l /dev/hdaX on it to +see. Under Debian you have to be in the disk group (just add your +username to the end of the entry in /etc/group). + +File types are done with matching the HFS+ type and application label +and then by extentions. See the source for the exact matching that +happens and feel free to suggest improvements. + +For some reason some directories in MacOS end in a funny tall f +character. This seems to confuse hfstools. + +You can't easiily use the command line tools while you are browsing +using kio-mac in Konqueror. Konqueror continuously refreshes it's +view which mean hpmount is being called every few seconds. Click on +Konqueror's home button before using the tools yourself on the command +line. + +Hidden files are now shown all the time. Apparantly Konqueror only +considers files with a dot at the front of the name to be hidden which +is a bit system dependant. + +Please e-mail me with any comments, problems and success stories: +Jonathan Riddell, jr@jriddell.org diff --git a/kioslave/mac/TODO b/kioslave/mac/TODO new file mode 100644 index 000000000..e94d88254 --- /dev/null +++ b/kioslave/mac/TODO @@ -0,0 +1,14 @@ +FIXMEs: + Amazingly, none that I can think of + +grep TODO kio_mac.cpp + //TODO this means dev=foo must be the last argument in the query + //TODO this error interrupts the user when typing ?dev=foo on each letter of foo + //TODO are there any more characters to escape? + QString theSize(fileRE.group(4)); //TODO: this is data size, what about resource size? + +Future things: + - maybe make it work with plain old hfs partitions + - possibly use libhfsp directly + - A Friend suggested reading the resource data for the icon to display - advanced I think. + - Follow symlinks/aliases (requires reading the resource fork as well) diff --git a/kioslave/mac/cr16-app-mac.png b/kioslave/mac/cr16-app-mac.png Binary files differnew file mode 100644 index 000000000..930694eab --- /dev/null +++ b/kioslave/mac/cr16-app-mac.png diff --git a/kioslave/mac/cr32-app-mac.png b/kioslave/mac/cr32-app-mac.png Binary files differnew file mode 100644 index 000000000..87df6fb85 --- /dev/null +++ b/kioslave/mac/cr32-app-mac.png diff --git a/kioslave/mac/kio_mac.cpp b/kioslave/mac/kio_mac.cpp new file mode 100644 index 000000000..56989487a --- /dev/null +++ b/kioslave/mac/kio_mac.cpp @@ -0,0 +1,561 @@ +/*************************************************************************** + kio_mac.cpp + ------------------- + copyright : (C) 2002 Jonathan Riddell + email : jr@jriddell.org + version : 1.0.1 + release date : 19 July 2002 + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#define PARTITION "/dev/hda11" + +#include <kinstance.h> +#include <kdebug.h> +#include <klocale.h> +#include <kconfig.h> +#include <qstring.h> +#include <qregexp.h> + +#include <sys/stat.h> +#include <stdlib.h> +#include <iostream> +#include <time.h> + +#include "kio_mac.moc" + +using namespace KIO; + +extern "C" { + int KDE_EXPORT kdemain(int, char **argv) { + KInstance instance("kio_mac"); + MacProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; + } +} + +MacProtocol::MacProtocol(const QCString &pool, const QCString &app) + : QObject(), SlaveBase("mac", pool, app) { +/* logFile = new QFile("/home/jr/logfile"); + logFile->open(IO_ReadWrite | IO_Append); + logStream = new QTextStream(logFile); + *logStream << "Start Macprotocol()" << endl; + */ +} + +MacProtocol::~MacProtocol() { +/* *logStream << "destructor ~MacProtocol()" << endl; + logFile->close(); + delete logFile; + logFile = 0; + delete logStream; + logStream = 0; +*/ + delete myKProcess; + myKProcess = 0L; +} + +//get() called when a file is to be read +void MacProtocol::get(const KURL& url) { + QString path = prepareHP(url); //mount and change to correct directory - return the filename + QString query = url.query(); + QString mode("-"); + QString mime; + processedBytes = 0; + + //Find out the size and if it's a text file + UDSEntry entry = doStat(url); + UDSEntry::Iterator it; + for(it = entry.begin(); it != entry.end(); ++it) { + if ((*it).m_uds == KIO::UDS_MIME_TYPE) { + mime = (*it).m_str; + } + if ((*it).m_uds == KIO::UDS_SIZE) { + totalSize((*it).m_long); + } + } + + //find out if a mode has been specified in the query e.g. ?mode=t + //or if it's a text file then set the mode to text + int modepos = query.find("mode="); + int textpos = mime.find("text"); + if (modepos != -1) { + mode += query.mid(modepos + 5, 1); + if (mode != "-r" && mode != "-b" && mode != "-m" && mode != "-t" && mode != "-a") { + error(ERR_SLAVE_DEFINED, i18n("Unknown mode")); + } + } else if (textpos != -1) { + mode += "t"; + } else { + mode += "r"; + } + + //now we can read the file + myKProcess = new KProcess(); + + *myKProcess << "hpcopy" << mode << path << "-"; + + //data is now sent directly from the slot + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotSetDataStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + if (!myKProcess->normalExit() || !(myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("There was an error with hpcopy - please ensure it is installed")); + return; + } + + //clean up + delete myKProcess; myKProcess = 0; + //finish + data(QByteArray()); + finished(); +} + +//listDir() called when the user is looking at a directory +void MacProtocol::listDir(const KURL& url) { + QString filename = prepareHP(url); + + if (filename.isNull()) { + error(ERR_CANNOT_LAUNCH_PROCESS, i18n("No filename was found")); + } else { + myKProcess = new KProcess(); + *myKProcess << "hpls" << "-la" << filename; + + standardOutputStream = QString::null; + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("There was an error with hpls - please ensure it is installed")); + } + + //clean up + delete myKProcess; myKProcess = 0; + disconnect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + UDSEntry entry; + if (!standardOutputStream.isEmpty()) { + QTextStream in(&standardOutputStream, IO_ReadOnly); + QString line = in.readLine(); //throw away top file which shows current directory + line = in.readLine(); + + while (line != NULL) { + //1.0.4 puts this funny line in sometimes, we don't want it + if (line.contains("Thread ") == 0) { + entry = makeUDS(line); + listEntry(entry, false); + } + line = in.readLine(); + } + }//if standardOutputStream != null + + listEntry(entry, true); + finished(); + + }//if filename == null +} + +//stat() called to see if it's a file or directory, called before listDir() or get() +void MacProtocol::stat(const KURL& url) { + statEntry(doStat(url)); + finished(); +} + +//doStat(), does all the work that stat() needs +//it's been separated out so it can be called from get() which +//also need information +QValueList<KIO::UDSAtom> MacProtocol::doStat(const KURL& url) { + QString filename = prepareHP(url); + + if (filename.isNull()) { + error(ERR_SLAVE_DEFINED, i18n("No filename was found in the URL")); + } else if (! filename.isEmpty()) { + myKProcess = new KShellProcess(); + + *myKProcess << "hpls" << "-ld" << filename; + + standardOutputStream = QString::null; + connect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("hpls did not exit normally - please ensure you have installed the hfsplus tools")); + } + + //clean up + delete myKProcess; myKProcess = 0; + disconnect(myKProcess, SIGNAL(receivedStdout(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + if (standardOutputStream.isEmpty()) { + filename.replace("\\ ", " "); //get rid of escapes + filename.replace("\\&", "&"); //mm, slashes... + filename.replace("\\!", "!"); + filename.replace("\\(", "("); + filename.replace("\\)", ")"); + error(ERR_DOES_NOT_EXIST, filename); + } else { + //remove trailing \n + QString line = standardOutputStream.left(standardOutputStream.length()-1); + UDSEntry entry = makeUDS(line); + return entry; + } + } else { //filename is empty means we're looking at root dir + //we don't have a listing for the root directory so here's a dummy one + UDSEntry entry = makeUDS("d 0 item Jan 01 2000 /"); + return entry; + }//if filename == null + + return QValueList<KIO::UDSAtom>(); +} + +//prepareHP() called from get() listDir() and stat() +//(re)mounts the partition and changes to the appropriate directory +QString MacProtocol::prepareHP(const KURL& url) { + QString path = url.path(-1); + if (path.left(1) == "/") { + path = path.mid(1); // strip leading slash + } + + //find out if a device has been specified in the query e.g. ?dev=/dev/fd0 + //or in the config file (query device entries are saved to config file) + QString device; + KConfig* config = new KConfig("macrc"); + + QString query = url.query(); + int modepos = query.find("dev="); + if (modepos == -1) { + //no device specified, read from config or go with #define PARTITION + device = config->readEntry("device",PARTITION); + } else { + //TODO this means dev=foo must be the last argument in the query + device = query.mid(modepos + 4); + config->writeEntry("device",device); + } + delete config; config = 0; + + //first we run just hpmount and check the output to see if it's version 1.0.2 or 1.0.4 + myKProcess = new KProcess(); + *myKProcess << "hpmount"; + standardOutputStream = QString::null; + connect(myKProcess, SIGNAL(receivedStderr(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + myKProcess->start(KProcess::Block, KProcess::All); + + bool version102 = true; + + if (standardOutputStream.contains("options") != 0) { + version102 = false; + } + + delete myKProcess; myKProcess = 0; + disconnect(myKProcess, SIGNAL(receivedStderr(KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + + //now mount the drive + myKProcess = new KProcess(); + if (version102) { + *myKProcess << "hpmount" << device; + } else { + *myKProcess << "hpmount" << "-r" << device; + } + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + //TODO this error interrupts the user when typing ?dev=foo on each letter of foo + error(ERR_SLAVE_DEFINED, + i18n("hpmount did not exit normally - please ensure that hfsplus utils are installed,\n" + "that you have permission to read the partition (ls -l /dev/hdaX)\n" + "and that you have specified the correct partition.\n" + "You can specify partitions by adding ?dev=/dev/hda2 to the URL.")); + return NULL; + } + + //clean up + delete myKProcess; myKProcess = 0; + + //escape any funny characters + //TODO are there any more characters to escape? + path.replace(" ", "\\ "); + path.replace("&", "\\&"); + path.replace("!", "\\!"); + path.replace("(", "\\("); + path.replace(")", "\\)"); + + //then change to the right directory + int s; QString dir; + s = path.find('/'); + while (s != -1) { + dir = path.left(s); + path = path.mid(s+1); + + myKProcess = new KProcess(); + *myKProcess << "hpcd" << dir; + + myKProcess->start(KProcess::Block, KProcess::All); + + if ((!myKProcess->normalExit()) || (!myKProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("hpcd did not exit normally - please ensure it is installed")); + return NULL; + } + + //clean up + delete myKProcess; myKProcess = 0; + + s = path.find('/'); + } + + return path; +} + +//makeUDS() takes a line of output from hpls -l and converts it into +// one of these UDSEntrys to return +//called from listDir() and stat() +QValueList<KIO::UDSAtom> MacProtocol::makeUDS(const QString& _line) { + QString line(_line); + UDSEntry entry; + + //is it a file or a directory + QRegExp dirRE("^d. +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +(.*)"); + QRegExp fileRE("^([f|F]). +(....)/(....) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +(.*)"); + if (dirRE.exactMatch(line)) { + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = dirRE.cap(6); + entry.append(atom); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = makeTime(dirRE.cap(4), dirRE.cap(3), dirRE.cap(5)); + entry.append(atom); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append(atom); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = 0755; + entry.append(atom); + + } else if (fileRE.exactMatch(line)) { + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = fileRE.cap(9); + entry.append(atom); + + atom.m_uds = KIO::UDS_SIZE; + QString theSize(fileRE.cap(4)); //TODO: this is data size, what about resource size? + atom.m_long = theSize.toLong(); + entry.append(atom); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = makeTime(fileRE.cap(7), fileRE.cap(6), fileRE.cap(8)); + entry.append(atom); + + atom.m_uds = KIO::UDS_ACCESS; + if (QString(fileRE.cap(1)) == QString("F")) { //if locked then read only + atom.m_long = 0444; + } else { + atom.m_long = 0644; + } + entry.append(atom); + + atom.m_uds = KIO::UDS_MIME_TYPE; + QString mimetype = getMimetype(fileRE.cap(2),fileRE.cap(3)); + atom.m_str = mimetype.local8Bit(); + entry.append(atom); + + // Is it a file or a link/alias, just make aliases link to themselves + if (QString(fileRE.cap(2)) == QString("adrp") || + QString(fileRE.cap(2)) == QString("fdrp")) { + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = KIO::UDS_LINK_DEST; + atom.m_str = fileRE.cap(9); //I have a file called "Mozilla alias" the name + // of which displays funny because of this. + // No idea why. Same for other kioslaves. A font thing? + entry.append(atom); + } else { + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + } + } else { + error(ERR_INTERNAL, i18n("hpls output was not matched")); + } //if match dirRE or fileRE + + return entry; +} + +//slotGetStdOutput() grabs output from the hp commands +// and adds it to the buffer +void MacProtocol::slotGetStdOutput(KProcess*, char *s, int len) { + standardOutputStream += QString::fromLocal8Bit(s, len); +} + +//slotSetDataStdOutput() is used during hpcopy to give +//standard output to KDE +void MacProtocol::slotSetDataStdOutput(KProcess*, char *s, int len) { + processedBytes += len; + processedSize(processedBytes); + QByteArray array; + array.setRawData(s, len); + data(array); + array.resetRawData(s, len); +} + +//makeTime() takes in the date output from hpls -l +//and returns as good a timestamp as we're going to get +int MacProtocol::makeTime(QString mday, QString mon, QString third) { + int year; int month; int day; + int hour; int minute; + + //find the month + if (mon == "Jan") { month = 1; } + else if (mon == "Feb") { month = 2; } + else if (mon == "Mar") { month = 3; } + else if (mon == "Apr") { month = 4; } + else if (mon == "May") { month = 5; } + else if (mon == "Jun") { month = 6; } + else if (mon == "Jul") { month = 7; } + else if (mon == "Aug") { month = 8; } + else if (mon == "Sep") { month = 9; } + else if (mon == "Oct") { month = 10; } + else if (mon == "Nov") { month = 11; } + else if (mon == "Dec") { month = 12; } + else { + error(ERR_INTERNAL, i18n("Month output from hpls -l not matched")); + month = 13; + } + + //if the file is recent (last 12 months) hpls gives us the time, + // otherwise it only prints the year + QRegExp hourMin("(..):(..)"); + if (hourMin.exactMatch(third)) { + QDate currentDate(QDate::currentDate()); + + if (month > currentDate.month()) { + year = currentDate.year() - 1; + } else { + year = currentDate.year(); + } + QString h(hourMin.cap(1)); + QString m(hourMin.cap(2)); + hour = h.toInt(); + minute = m.toInt(); + } else { + year = third.toInt(); + hour = 0; + minute = 0; + }// if hour:min or year + + day = mday.toInt(); + + //check it's valid + if ( (!QDate::isValid(year, month, day)) || (!QTime::isValid(hour, minute, 0) ) ) { + error(ERR_INTERNAL, i18n("Could not parse a valid date from hpls")); + } + + //put it together and work it out + QDate fileDate(year, month, day); + QTime fileTime(hour, minute); + QDateTime fileDateTime(fileDate, fileTime); + + return fileDateTime.toTime_t(); +} + +QString MacProtocol::getMimetype(QString type, QString app) { + if (type == QString("TEXT") && app == QString("ttxt")) { + return QString("text/plain"); + } else if (type == QString("TEXT") && app == QString("udog")) { + return QString("text/html"); + } else if (type == QString("svgs")) { + return QString("text/xml"); + } else if (type == QString("ZIP ")) { + return QString("application/zip"); + } else if (type == QString("pZip")) { + return QString("application/zip"); + } else if (type == QString("APPL")) { + return QString("application/x-executable"); + } else if (type == QString("MooV")) { + return QString("video/quicktime"); + } else if (type == QString("TEXT") && app == QString("MSWD")) { + return QString("application/vnd.ms-word"); + } else if (type == QString("PDF ")) { + return QString("application/pdf"); + } else if (app == QString("CARO")) { + return QString("application/pdf"); + } else if (type == QString("SIT5")) { + return QString("application/x-stuffit"); + } else if (type == QString("SITD")) { + return QString("application/x-stuffit"); + } else if (type == QString("SIT!")) { + return QString("application/x-stuffit"); + } else if (app == QString("SIT!")) { + return QString("application/x-stuffit"); + } else if (type == QString("RTFf")) { + return QString("text/rtf"); + } else if (type == QString("GIFf")) { + return QString("image/gif"); + } else if (type == QString("JPEG")) { + return QString("image/jpeg"); + } else if (type == QString("PNGf")) { + return QString("image/png"); + } else if (type == QString("XBMm")) { + return QString("image/x-xbm"); + } else if (type == QString("EPSF")) { + return QString("image/x-epsf"); + } else if (type == QString("TIFF")) { + return QString("image/tiff"); + } else if (type == QString("PICT")) { + return QString("image/pict"); + } else if (type == QString("TPIC")) { + return QString("image/x-targa"); + } else if (type == QString("ULAW")) { + return QString("audio/basic"); + } else if (type == QString("AIFF")) { + return QString("audio/x-aiff"); + } else if (type == QString("WAVE")) { + return QString("audio/x-wav"); + } else if (type == QString("FFIL") && app == QString("DMOV")) { + return QString("application/x-font"); + } else if (type == QString("XLS3")) { + return QString("application/vnd.ms-excel"); + } else if (type == QString("XLS4")) { + return QString("application/vnd.ms-excel"); + } else if (type == QString("XLS5")) { + return QString("application/vnd.ms-excel"); + } else if (app == QString("MSWD")) { + return QString("application/vnd.ms-word"); + } else if (type == QString("TEXT")) { + return QString("text/plain"); + } else if (app == QString("ttxt")) { + return QString("text/plain"); + } + return QString("application/octet-stream"); +} + + diff --git a/kioslave/mac/kio_mac.h b/kioslave/mac/kio_mac.h new file mode 100644 index 000000000..c87217e08 --- /dev/null +++ b/kioslave/mac/kio_mac.h @@ -0,0 +1,55 @@ +/*************************************************************************** + mac.cpp + ------------------- + copyright : (C) 2002 Jonathan Riddell + email : jr@jriddell.org + version : 1.0 + release date : 10 Feburary 2002 + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <kio/slavebase.h> +#include <kio/global.h> +#include <kurl.h> +#include <kprocess.h> + +#include <qstring.h> +#include <qcstring.h> +#include <qfile.h> +#include <qtextstream.h> + +class MacProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT +public: + MacProtocol(const QCString &pool, const QCString &app); + ~MacProtocol(); + virtual void get(const KURL& url ); + virtual void listDir(const KURL& url); + virtual void stat(const KURL& url); +protected slots: + void slotGetStdOutput(KProcess*, char*, int); + void slotSetDataStdOutput(KProcess*, char *s, int len); +protected: + QString prepareHP(const KURL& _url); + QValueList<KIO::UDSAtom> makeUDS(const QString& _line); + int makeTime(QString mday, QString mon, QString third); + QString getMimetype(QString type, QString app); + QValueList<KIO::UDSAtom> doStat(const KURL& url); + + KIO::filesize_t processedBytes; + QString standardOutputStream; + KProcess* myKProcess; + + //for debugging + //QFile* logFile; + //QTextStream* logStream; +}; diff --git a/kioslave/mac/mac.protocol b/kioslave/mac/mac.protocol new file mode 100644 index 000000000..cef77621d --- /dev/null +++ b/kioslave/mac/mac.protocol @@ -0,0 +1,73 @@ +[Protocol] +exec=kio_mac +protocol=mac +input=none +output=filesystem +reading=true +listing=Name,Type,Size,Date +defaultMimetype=application/octet-stream +Description=A kioslave for MacOS HFS+ partitions +Description[af]='n Kioslave vir MacOS HFS+ partisies +Description[be]=Kioslave Ð´Ð»Ñ Ñ€Ð°Ð·Ð´Ð·ÐµÐ»Ð°Ñž MacOS HFS+ +Description[bn]=মà§à¦¯à¦¾à¦•-ও-à¦à¦¸ HFS+ পারà§à¦Ÿà¦¿à¦¶à¦¨-à¦à¦° জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ kioslave +Description[br]=Ur c'hioslave evit ar parzhadurioù MacOS HFS+ +Description[bs]=kioslave za MacOS HFS+ particije +Description[ca]=Un kioslave per a particions MacOS HFS+ +Description[cs]=Pomocný protokol pro diskové oddÃly MacOS HFS+ +Description[csb]=Plugins protokòłu dlô particëji HFS+ systemë MacOS +Description[da]=En kioslave for MacOS HFS+ partitioner +Description[de]=Ein-/Ausgabemodul für MacOS HFS+ Partitionen +Description[el]=Ένας kioslave για κατατμήσεις MacOS HFS+ +Description[eo]=K-enel-sklavo por MacOS HFS+ subdiskoj +Description[es]=Un kioslave para particiones MacOS HFS+ +Description[et]=MacOS-i HFS+-partitsioonide IO-moodul +Description[eu]=MacOS HFS+ zatiketetarako kioslavea +Description[fa]=یک kioslave برای اÙرازهای HFS+ سیستم عامل مکینتاش +Description[fi]=Liitäntä MacOS HFS+ osioinneille +Description[fr]=Un module d'entrées / sorties pour les partitions MacOS HFS+ +Description[fy]=In kioslave foar MacOS HFS+-partities +Description[ga]=kioslave le haghaidh deighiltà MacOS HFS+ +Description[gl]=Un kioslave para particións MacOS HFS+ +Description[he]=ממשק kioslave עבור מחיצות MacOS HFS+ +Description[hi]=मॅक-ओà¤à¤¸ à¤à¤šà¤à¤«à¤¼à¤à¤¸+ पारà¥à¤Ÿà¥€à¤¶à¤¨à¥‹à¤‚ के लिठके-आई-ओ-सà¥à¤²à¥‡à¤µ +Description[hr]=Kioslave za MacOS HFS+ particije +Description[hu]=KDE-protokoll MacOS HFS+ partÃciók kezeléséhez +Description[is]=kioslave fyrir MacOS HFS+ disksneiðar +Description[it]=Un kioslave per partizioni MacOS HFS+ +Description[ja]=MacOS HFS+ パーティションã®ãŸã‚ã® kioslave +Description[ka]=kioslave MacOS HFS+ პáƒáƒ ტიციებისთვის +Description[kk]=MacOS HFS+ файл жүйеÑінің енгізу-шығару модулі +Description[km]=kioslave សម្រាប់​ភាគ MacOS HFS+ +Description[lt]=Kiovergas MacOS HFS+ dalmenims +Description[lv]=KIO vergs MacOS HFS+ partÄ«cijÄm +Description[mk]=kio-Ñлужител за HFS+ партиции од MacOS +Description[ms]=Hamba kio untuk MacOS HFS+ petak +Description[nb]=En kioskslave for MacOS HFS+-partisjoner +Description[nds]=En In-/Utgaavdeenst för MacOS-HFS+-Partitschonen +Description[ne]=MacOS HFS+ विà¤à¤¾à¤œà¤¨à¤•à¤¾ लागि किओसà¥à¤²à¥‡à¤ +Description[nl]=Een kioslave voor MacOS HFS+-partities +Description[nn]=Ein IU-slave for MacOS HFS+-partisjonar +Description[pa]=MacOS HFS+ à¨à¨¾à¨—ਾਂ ਲਈ kioslave +Description[pl]=Wtyczka protokoÅ‚u dla partycji HFS+ systemu MacOS +Description[pt]=Um 'kioslave' para partições MacOS HFS+ +Description[pt_BR]=Um protocolo para as partições HFS+ do MacOS +Description[ro]=Un dispozitiv de I/E pentru partiÈ›ii HFS+ MacOS +Description[ru]=Модуль ввода-вывода Ð´Ð»Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²Ð¾Ð¹ ÑиÑтемы MacOS HFS+ +Description[rw]=Kio-umugaragu ya MacOS HFS+ibicedisiki +Description[se]=SO-Å¡láva MacOS HFS+-partiÅ¡uvnnaid várás +Description[sk]=kioslave pre MacOS HFS+ +Description[sl]=kioslave za razdelke MacOS HFS+ +Description[sr]=Kioslave за MacOS-ове HFS+ партиције +Description[sr@Latn]=Kioslave za MacOS-ove HFS+ particije +Description[sv]=En I/O-slav för MacOS HFS+ partitioner +Description[ta]=MacOS HFS+ partitionsகà¯à®•à¯ ஒர௠கà¯à®¯à¯‹à®¸à¯à®²à¯‡à®µà¯ +Description[th]=ตัวนำข้à¸à¸¡à¸¹à¸¥à¹€à¸‚้า-à¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸šà¸žà¸²à¸£à¹Œà¸•à¸´à¸Šà¸±à¹ˆà¸™à¸—ี่ใช้ระบบไฟล์ HFS+ ขà¸à¸‡ MacOS +Description[tr]=MacOS HFS+ bölümleri için kioslave +Description[tt]=MacOS HFS+ bülemnäre öçen birem sistemeneñ modulı +Description[uk]=Підлеглий B/Ð’ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð´Ñ–Ð»Ñ–Ð² MacOS HFS+ +Description[vi]=A kioslave (Ä‘Ã y tá»› và o ra KDE) cho MacOS HFS và các phân vùng +Description[wa]=On kioslave po MacOS HFS + pÃ¥rticions +Description[zh_CN]=MacOS HFS+ 分区的 KIO 仆人 +Description[zh_TW]=用於 MacOS HFS+ 分割å€çš„ kioslave +Icon=mac +DocPath=kioslave/mac.html diff --git a/kioslave/man/LICENSE b/kioslave/man/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/kioslave/man/LICENSE @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/man/Makefile.am b/kioslave/man/Makefile.am new file mode 100644 index 000000000..365d2f774 --- /dev/null +++ b/kioslave/man/Makefile.am @@ -0,0 +1,51 @@ +## Makefile.am of kdebase/kioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +EXTRA_PROGRAMS = kio_man_test man2html + +####### just for testing (j.habenicht@europemail.com, 15.02.2001) + +kio_man_test_SOURCES = kio_man_test.cpp +kio_man_test_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kio_man_test_LDADD = man2html.lo kio_man.lo $(LIB_KIO) $(LIB_KDEUI) $(LIB_KDECORE) $(LIB_QT) + +####### Files + +kde_module_LTLIBRARIES = kio_man.la libkmanpart.la + +kio_man_la_SOURCES = man2html.cpp kio_man.cpp +kio_man_la_LIBADD = $(LIB_KSYCOCA) +kio_man_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = kio_man.h +### TODO Why is man2htmk.h distributed? + +libkmanpart_la_SOURCES = kmanpart.cpp +libkmanpart_la_LIBADD = -lkhtml $(LIB_KPARTS) +libkmanpart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) + +kdelnk_DATA = man.protocol kmanpart.desktop +kdelnkdir = $(kde_servicesdir) + +kio_man_data_DATA = kio_man.css +kio_man_datadir = $(kde_datadir)/kio_man +EXTRA_DIST=$(kio_man_data_DATA) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/kio_man.pot + +man2html_SOURCES = dummy.cpp +man2html_LDADD = man2html_simple.o $(LIB_QT) +man2html_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +dummy.cpp: + echo > $@ + +man2html_simple.o: $(srcdir)/man2html.cpp + -rm -f man2html_simple.cpp + $(LN_S) $(srcdir)/man2html.cpp man2html_simple.cpp + $(CXX) $(DEFS) $(DEFAULT_INCLUDES) -DSIMPLE_MAN2HTML $(INCLUDES) $(CPPFLAGS) $(CXXFLAGS) -c man2html_simple.cpp + diff --git a/kioslave/man/kio_man.cpp b/kioslave/man/kio_man.cpp new file mode 100644 index 000000000..0511a165d --- /dev/null +++ b/kioslave/man/kio_man.cpp @@ -0,0 +1,1532 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <string.h> +#include <dirent.h> + +#include <qdir.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qdatastream.h> +#include <qcstring.h> +#include <qptrlist.h> +#include <qmap.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kprocess.h> +#include <klocale.h> +#include <kmimetype.h> + +#include "kio_man.h" +#include "kio_man.moc" +#include "man2html.h" +#include <assert.h> +#include <kfilterbase.h> +#include <kfilterdev.h> + +using namespace KIO; + +MANProtocol *MANProtocol::_self = 0; + +#define SGML2ROFF_DIRS "/usr/lib/sgml" + +/* + * Drop trailing ".section[.gz]" from name + */ +static +void stripExtension( QString *name ) +{ + int pos = name->length(); + + if ( name->find(".gz", -3) != -1 ) + pos -= 3; + else if ( name->find(".z", -2, false) != -1 ) + pos -= 2; + else if ( name->find(".bz2", -4) != -1 ) + pos -= 4; + else if ( name->find(".bz", -3) != -1 ) + pos -= 3; + + if ( pos > 0 ) + pos = name->findRev('.', pos-1); + + if ( pos > 0 ) + name->truncate( pos ); +} + +static +bool parseUrl(const QString& _url, QString &title, QString §ion) +{ + section = QString::null; + + QString url = _url; + if (url.at(0) == '/') { + if (KStandardDirs::exists(url)) { + title = url; + return true; + } else + { + // If the directory does not exist, then it is perhaps a normal man page + kdDebug(7107) << url << " does not exist" << endl; + } + } + + while (url.at(0) == '/') + url.remove(0,1); + + title = url; + + int pos = url.find('('); + if (pos < 0) + return true; + + title = title.left(pos); + + section = url.mid(pos+1); + section = section.left(section.length()-1); + + return true; +} + + +MANProtocol::MANProtocol(const QCString &pool_socket, const QCString &app_socket) + : QObject(), SlaveBase("man", pool_socket, app_socket) +{ + assert(!_self); + _self = this; + const QString common_dir = KGlobal::dirs()->findResourceDir( "html", "en/common/kde-common.css" ); + const QString strPath=QString( "file:%1/en/common" ).arg( common_dir ); + m_htmlPath=strPath.local8Bit(); // ### TODO encode for HTML + m_cssPath=strPath.local8Bit(); // ### TODO encode for CSS + section_names << "1" << "2" << "3" << "3n" << "3p" << "4" << "5" << "6" << "7" + << "8" << "9" << "l" << "n"; + m_manCSSFile = locate( "data", "kio_man/kio_man.css" ); +} + +MANProtocol *MANProtocol::self() { return _self; } + +MANProtocol::~MANProtocol() +{ + _self = 0; +} + +void MANProtocol::parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark ) +{ + QRegExp re( mark ); + QString l; + while ( !t.atEnd() ) + { + l = t.readLine(); + int pos = re.search( l ); + if (pos != -1) + { + QString names = l.left(pos); + QString descr = l.mid(pos + re.matchedLength()); + while ((pos = names.find(",")) != -1) + { + i[names.left(pos++)] = descr; + while (names[pos] == ' ') + pos++; + names = names.mid(pos); + } + i[names] = descr; + } + } +} + +bool MANProtocol::addWhatIs(QMap<QString, QString> &i, const QString &name, const QString &mark) +{ + QFile f(name); + if (!f.open(IO_ReadOnly)) + return false; + QTextStream t(&f); + parseWhatIs( i, t, mark ); + return true; +} + +QMap<QString, QString> MANProtocol::buildIndexMap(const QString §ion) +{ + QMap<QString, QString> i; + QStringList man_dirs = manDirectories(); + // Supplementary places for whatis databases + man_dirs += m_mandbpath; + if (man_dirs.find("/var/cache/man")==man_dirs.end()) + man_dirs << "/var/cache/man"; + if (man_dirs.find("/var/catman")==man_dirs.end()) + man_dirs << "/var/catman"; + + QStringList names; + names << "whatis.db" << "whatis"; + QString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+"; + + for ( QStringList::ConstIterator it_dir = man_dirs.begin(); + it_dir != man_dirs.end(); + ++it_dir ) + { + if ( QFile::exists( *it_dir ) ) { + QStringList::ConstIterator it_name; + for ( it_name = names.begin(); + it_name != names.end(); + it_name++ ) + { + if (addWhatIs(i, (*it_dir) + "/" + (*it_name), mark)) + break; + } + if ( it_name == names.end() ) { + KProcess proc; + proc << "whatis" << "-M" << (*it_dir) << "-w" << "*"; + myStdStream = QString::null; + connect( &proc, SIGNAL( receivedStdout(KProcess *, char *, int ) ), + SLOT( slotGetStdOutput( KProcess *, char *, int ) ) ); + proc.start( KProcess::Block, KProcess::Stdout ); + QTextStream t( &myStdStream, IO_ReadOnly ); + parseWhatIs( i, t, mark ); + } + } + } + return i; +} + +QStringList MANProtocol::manDirectories() +{ + checkManPaths(); + // + // Build a list of man directories including translations + // + QStringList man_dirs; + + for ( QStringList::ConstIterator it_dir = m_manpath.begin(); + it_dir != m_manpath.end(); + it_dir++ ) + { + // Translated pages in "<mandir>/<lang>" if the directory + // exists + QStringList languages = KGlobal::locale()->languageList(); + + for (QStringList::ConstIterator it_lang = languages.begin(); + it_lang != languages.end(); + it_lang++ ) + { + if ( !(*it_lang).isEmpty() && (*it_lang) != QString("C") ) { + QString dir = (*it_dir) + '/' + (*it_lang); + + struct stat sbuf; + + if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + const QString p = QDir(dir).canonicalPath(); + if (!man_dirs.contains(p)) man_dirs += p; + } + } + } + + // Untranslated pages in "<mandir>" + const QString p = QDir(*it_dir).canonicalPath(); + if (!man_dirs.contains(p)) man_dirs += p; + } + return man_dirs; +} + +QStringList MANProtocol::findPages(const QString &_section, + const QString &title, + bool full_path) +{ + QString section = _section; + + QStringList list; + + // kdDebug() << "findPages '" << section << "' '" << title << "'\n"; + if (title.at(0) == '/') { + list.append(title); + return list; + } + + const QString star( "*" ); + + // + // Find man sections in this directory + // + QStringList sect_list; + if ( section.isEmpty() ) + section = star; + + if ( section != star ) + { + // + // Section given as argument + // + sect_list += section; + while (section.at(section.length() - 1).isLetter()) { + section.truncate(section.length() - 1); + sect_list += section; + } + } else { + sect_list += section; + } + + QStringList man_dirs = manDirectories(); + + // + // Find man pages in the sections listed above + // + for ( QStringList::ConstIterator it_sect = sect_list.begin(); + it_sect != sect_list.end(); + it_sect++ ) + { + QString it_real = (*it_sect).lower(); + // + // Find pages + // + for ( QStringList::ConstIterator it_dir = man_dirs.begin(); + it_dir != man_dirs.end(); + it_dir++ ) + { + QString man_dir = (*it_dir); + + // + // Sections = all sub directories "man*" and "sman*" + // + DIR *dp = ::opendir( QFile::encodeName( man_dir ) ); + + if ( !dp ) + continue; + + struct dirent *ep; + + const QString man = QString("man"); + const QString sman = QString("sman"); + + while ( (ep = ::readdir( dp )) != 0L ) { + const QString file = QFile::decodeName( ep->d_name ); + QString sect = QString::null; + + if ( file.startsWith( man ) ) + sect = file.mid(3); + else if (file.startsWith(sman)) + sect = file.mid(4); + + if (sect.lower()==it_real) it_real = sect; + + // Only add sect if not already contained, avoid duplicates + if (!sect_list.contains(sect) && _section.isEmpty()) { + kdDebug() << "another section " << sect << endl; + sect_list += sect; + } + } + + ::closedir( dp ); + + if ( *it_sect != star ) { // in that case we only look around for sections + const QString dir = man_dir + QString("/man") + (it_real) + '/'; + const QString sdir = man_dir + QString("/sman") + (it_real) + '/'; + + findManPagesInSection(dir, title, full_path, list); + findManPagesInSection(sdir, title, full_path, list); + } + } + } + +// kdDebug(7107) << "finished " << list << " " << sect_list << endl; + + return list; +} + +void MANProtocol::findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list) +{ + kdDebug() << "findManPagesInSection " << dir << " " << title << endl; + bool title_given = !title.isEmpty(); + + DIR *dp = ::opendir( QFile::encodeName( dir ) ); + + if ( !dp ) + return; + + struct dirent *ep; + + while ( (ep = ::readdir( dp )) != 0L ) { + if ( ep->d_name[0] != '.' ) { + + QString name = QFile::decodeName( ep->d_name ); + + // check title if we're looking for a specific page + if ( title_given ) { + if ( !name.startsWith( title ) ) { + continue; + } + else { + // beginning matches, do a more thorough check... + QString tmp_name = name; + stripExtension( &tmp_name ); + if ( tmp_name != title ) + continue; + } + } + + if ( full_path ) + name.prepend( dir ); + + list += name ; + } + } + ::closedir( dp ); +} + +void MANProtocol::output(const char *insert) +{ + if (insert) + { + m_outputBuffer.writeBlock(insert,strlen(insert)); + } + if (!insert || m_outputBuffer.at() >= 2048) + { + m_outputBuffer.close(); + data(m_outputBuffer.buffer()); + m_outputBuffer.setBuffer(QByteArray()); + m_outputBuffer.open(IO_WriteOnly); + } +} + +// called by man2html +char *read_man_page(const char *filename) +{ + return MANProtocol::self()->readManPage(filename); +} + +// called by man2html +void output_real(const char *insert) +{ + MANProtocol::self()->output(insert); +} + +static QString text2html(const QString& txt) +{ + QString reply = txt; + + reply = reply.replace('&', "&"); + reply = reply.replace('<', "<"); + reply = reply.replace('>', ">"); + reply = reply.replace('"', "&dquot;"); + reply = reply.replace('\'', """); + return reply; +} + +void MANProtocol::get(const KURL& url ) +{ + kdDebug(7107) << "GET " << url.url() << endl; + + QString title, section; + + if (!parseUrl(url.path(), title, section)) + { + showMainIndex(); + return; + } + + // see if an index was requested + if (url.query().isEmpty() && (title.isEmpty() || title == "/" || title == ".")) + { + if (section == "index" || section.isEmpty()) + showMainIndex(); + else + showIndex(section); + return; + } + + // tell the mimetype + mimeType("text/html"); + + const QStringList foundPages=findPages(section, title); + bool pageFound=true; + if (foundPages.isEmpty()) + { + outputError(i18n("No man page matching to %1 found.<br><br>" + "Check that you have not mistyped the name of the page that you want.\n" + "Be careful that you must take care about upper case and lower case characters!<br>" + "If everything looks correct, then perhaps you need to set a better search path " + "for man pages, be it by the environment variable MANPATH or a matching file " + "in the directory /etc .").arg(text2html(title))); + pageFound=false; + } + else if (foundPages.count()>1) + { + pageFound=false; + //check for the case that there is foo.1 and foo.1.gz found: + // ### TODO make it more generic (other extensions) + if ((foundPages.count()==2) && + (((foundPages[0]+".gz") == foundPages[1]) || + (foundPages[0] == (foundPages[1]+".gz")))) + pageFound=true; + else + outputMatchingPages(foundPages); + } + //yes, we found exactly one man page + + if (pageFound) + { + setResourcePath(m_htmlPath,m_cssPath); + m_outputBuffer.open(IO_WriteOnly); + const QCString filename=QFile::encodeName(foundPages[0]); + char *buf = readManPage(filename); + + if (!buf) + { + outputError(i18n("Open of %1 failed.").arg(title)); + finished(); + return; + } + // will call output_real + scan_man_page(buf); + delete [] buf; + + output(0); // flush + + m_outputBuffer.close(); + data(m_outputBuffer.buffer()); + m_outputBuffer.setBuffer(QByteArray()); + // tell we are done + data(QByteArray()); + } + finished(); +} + +void MANProtocol::slotGetStdOutput(KProcess* /* p */, char *s, int len) +{ + myStdStream += QString::fromLocal8Bit(s, len); +} + +char *MANProtocol::readManPage(const char *_filename) +{ + QCString filename = _filename; + + char *buf = NULL; + + /* Determine type of man page file by checking its path. Determination by + * MIME type with KMimeType doesn't work reliablely. E.g., Solaris 7: + * /usr/man/sman7fs/pcfs.7fs -> text/x-csrc : WRONG + * If the path name constains the string sman, assume that it's SGML and + * convert it to roff format (used on Solaris). */ + //QString file_mimetype = KMimeType::findByPath(QString(filename), 0, false)->name(); + if (filename.contains("sman", false)) //file_mimetype == "text/html" || ) + { + myStdStream =QString::null; + KProcess proc; + + /* Determine path to sgml2roff, if not already done. */ + getProgramPath(); + proc << mySgml2RoffPath << filename; + + QApplication::connect(&proc, SIGNAL(receivedStdout (KProcess *, char *, int)), + this, SLOT(slotGetStdOutput(KProcess *, char *, int))); + proc.start(KProcess::Block, KProcess::All); + + const QCString cstr=myStdStream.latin1(); + const int len = cstr.size()-1; + buf = new char[len + 4]; + qmemmove(buf + 1, cstr.data(), len); + buf[0]=buf[len]='\n'; // Start and end with a end of line + buf[len+1]=buf[len+2]='\0'; // Two additional NUL characters at end + } + else + { + if (QDir::isRelativePath(filename)) { + kdDebug(7107) << "relative " << filename << endl; + filename = QDir::cleanDirPath(lastdir + "/" + filename).utf8(); + if (!KStandardDirs::exists(filename)) { // exists perhaps with suffix + lastdir = filename.left(filename.findRev('/')); + QDir mandir(lastdir); + mandir.setNameFilter(filename.mid(filename.findRev('/') + 1) + ".*"); + filename = lastdir + "/" + QFile::encodeName(mandir.entryList().first()); + } + kdDebug(7107) << "resolved to " << filename << endl; + } + lastdir = filename.left(filename.findRev('/')); + + QIODevice *fd= KFilterDev::deviceForFile(filename); + + if ( !fd || !fd->open(IO_ReadOnly)) + { + delete fd; + return 0; + } + QByteArray array(fd->readAll()); + kdDebug(7107) << "read " << array.size() << endl; + fd->close(); + delete fd; + + if (array.isEmpty()) + return 0; + + const int len = array.size(); + buf = new char[len + 4]; + qmemmove(buf + 1, array.data(), len); + buf[0]=buf[len]='\n'; // Start and end with a end of line + buf[len+1]=buf[len+2]='\0'; // Two NUL characters at end + } + return buf; +} + + +void MANProtocol::outputError(const QString& errmsg) +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl; + os << "<title>" << i18n("Man output") << "</title>\n" << endl; + if ( !m_manCSSFile.isEmpty() ) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" << endl; + os << i18n("<body><h1>KDE Man Viewer Error</h1>") << errmsg << "</body>" << endl; + os << "</html>" << endl; + + data(array); +} + +void MANProtocol::outputMatchingPages(const QStringList &matchingPages) +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html>\n<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"<<endl; + os << "<title>" << i18n("Man output") <<"</title>" << endl; + if ( !m_manCSSFile.isEmpty() ) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" <<endl; + os << "<body><h1>" << i18n("There is more than one matching man page."); + os << "</h1>\n<ul>\n"; + + int acckey=1; + for (QStringList::ConstIterator it = matchingPages.begin(); it != matchingPages.end(); ++it) + { + os<<"<li><a href='man:"<<(*it)<<"' accesskey='"<< acckey <<"'>"<< *it <<"</a><br>\n<br>\n"; + acckey++; + } + os << "</ul>\n"; + os << "<hr>\n"; + os << "<p>" << i18n("Note: if you read a man page in your language," + " be aware it can contain some mistakes or be obsolete." + " In case of doubt, you should have a look at the English version.") << "</p>"; + + os << "</body>\n</html>"<<endl; + + data(array); + finished(); +} + +void MANProtocol::stat( const KURL& url) +{ + kdDebug(7107) << "ENTERING STAT " << url.url() << endl; + + QString title, section; + + if (!parseUrl(url.path(), title, section)) + { + error(KIO::ERR_MALFORMED_URL, url.url()); + return; + } + + kdDebug(7107) << "URL " << url.url() << " parsed to title='" << title << "' section=" << section << endl; + + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = UDS_NAME; + atom.m_long = 0; + atom.m_str = title; + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = UDS_URL; + atom.m_long = 0; + QString newUrl = "man:"+title; + if (!section.isEmpty()) + newUrl += QString("(%1)").arg(section); + atom.m_str = newUrl; + entry.append(atom); + + atom.m_uds = UDS_MIME_TYPE; + atom.m_long = 0; + atom.m_str = "text/html"; + entry.append(atom); + + statEntry(entry); + + finished(); +} + + +extern "C" +{ + + int KDE_EXPORT kdemain( int argc, char **argv ) { + + KInstance instance("kio_man"); + + kdDebug(7107) << "STARTING " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_man protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + MANProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7107) << "Done" << endl; + + return 0; + } + +} + +void MANProtocol::mimetype(const KURL & /*url*/) +{ + mimeType("text/html"); + finished(); +} + +static QString sectionName(const QString& section) +{ + if (section == "1") + return i18n("User Commands"); + else if (section == "2") + return i18n("System Calls"); + else if (section == "3") + return i18n("Subroutines"); + else if (section == "3p") + return i18n("Perl Modules"); + else if (section == "3n") + return i18n("Network Functions"); + else if (section == "4") + return i18n("Devices"); + else if (section == "5") + return i18n("File Formats"); + else if (section == "6") + return i18n("Games"); + else if (section == "7") + return i18n("Miscellaneous"); + else if (section == "8") + return i18n("System Administration"); + else if (section == "9") + return i18n("Kernel"); + else if (section == "l") + return i18n("Local Documentation"); + else if (section == "n") + return i18n("New"); + + return QString::null; +} + +QStringList MANProtocol::buildSectionList(const QStringList& dirs) const +{ + QStringList l; + + for (QStringList::ConstIterator it = section_names.begin(); + it != section_names.end(); ++it) + { + for (QStringList::ConstIterator dir = dirs.begin(); + dir != dirs.end(); ++dir) + { + QDir d((*dir)+"/man"+(*it)); + if (d.exists()) + { + l << *it; + break; + } + } + } + return l; +} + +void MANProtocol::showMainIndex() +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + // print header + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl; + os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl; + if (!m_manCSSFile.isEmpty()) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" << endl; + os << "<body><h1>" << i18n("UNIX Manual Index") << "</h1>" << endl; + + // ### TODO: why still the environment variable + const QString sectList = getenv("MANSECT"); + QStringList sections; + if (sectList.isEmpty()) + sections = buildSectionList(manDirectories()); + else + sections = QStringList::split(':', sectList); + + os << "<table>" << endl; + + QStringList::ConstIterator it; + for (it = sections.begin(); it != sections.end(); ++it) + os << "<tr><td><a href=\"man:(" << *it << ")\" accesskey=\"" << + (((*it).length()==1)?(*it):(*it).right(1))<<"\">" << i18n("Section ") + << *it << "</a></td><td> </td><td> " << sectionName(*it) << "</td></tr>" << endl; + + os << "</table>" << endl; + + // print footer + os << "</body></html>" << endl; + + data(array); + finished(); +} + +void MANProtocol::constructPath(QStringList& constr_path, QStringList constr_catmanpath) +{ + QMap<QString, QString> manpath_map; + QMap<QString, QString> mandb_map; + + // Add paths from /etc/man.conf + // + // Explicit manpaths may be given by lines starting with "MANPATH" or + // "MANDATORY_MANPATH" (depending on system ?). + // Mappings from $PATH to manpath are given by lines starting with + // "MANPATH_MAP" + + QRegExp manpath_regex( "^MANPATH\\s" ); + QRegExp mandatory_regex( "^MANDATORY_MANPATH\\s" ); + QRegExp manpath_map_regex( "^MANPATH_MAP\\s" ); + QRegExp mandb_map_regex( "^MANDB_MAP\\s" ); + //QRegExp section_regex( "^SECTION\\s" ); + QRegExp space_regex( "\\s+" ); // for parsing manpath map + + QFile mc("/etc/man.conf"); // Caldera + if (!mc.exists()) + mc.setName("/etc/manpath.config"); // SuSE, Debian + if (!mc.exists()) + mc.setName("/etc/man.config"); // Mandrake + + if (mc.open(IO_ReadOnly)) + { + QTextStream is(&mc); + is.setEncoding(QTextStream::Locale); + + while (!is.eof()) + { + const QString line = is.readLine(); + if ( manpath_regex.search(line, 0) == 0 ) + { + const QString path = line.mid(8).stripWhiteSpace(); + constr_path += path; + } + else if ( mandatory_regex.search(line, 0) == 0 ) + { + const QString path = line.mid(18).stripWhiteSpace(); + constr_path += path; + } + else if ( manpath_map_regex.search(line, 0) == 0 ) + { + // The entry is "MANPATH_MAP <path> <manpath>" + const QStringList mapping = + QStringList::split(space_regex, line); + + if ( mapping.count() == 3 ) + { + const QString dir = QDir::cleanDirPath( mapping[1] ); + const QString mandir = QDir::cleanDirPath( mapping[2] ); + + manpath_map[ dir ] = mandir; + } + } + else if ( mandb_map_regex.search(line, 0) == 0 ) + { + // The entry is "MANDB_MAP <manpath> <catmanpath>" + const QStringList mapping = + QStringList::split(space_regex, line); + + if ( mapping.count() == 3 ) + { + const QString mandir = QDir::cleanDirPath( mapping[1] ); + const QString catmandir = QDir::cleanDirPath( mapping[2] ); + + mandb_map[ mandir ] = catmandir; + } + } + /* sections are not used + else if ( section_regex.find(line, 0) == 0 ) + { + if ( !conf_section.isEmpty() ) + conf_section += ':'; + conf_section += line.mid(8).stripWhiteSpace(); + } + */ + } + mc.close(); + } + + // Default paths + static const char *manpaths[] = { + "/usr/X11/man", + "/usr/X11R6/man", + "/usr/man", + "/usr/local/man", + "/usr/exp/man", + "/usr/openwin/man", + "/usr/dt/man", + "/opt/freetool/man", + "/opt/local/man", + "/usr/tex/man", + "/usr/www/man", + "/usr/lang/man", + "/usr/gnu/man", + "/usr/share/man", + "/usr/motif/man", + "/usr/titools/man", + "/usr/sunpc/man", + "/usr/ncd/man", + "/usr/newsprint/man", + NULL }; + + + int i = 0; + while (manpaths[i]) { + if ( constr_path.findIndex( QString( manpaths[i] ) ) == -1 ) + constr_path += QString( manpaths[i] ); + i++; + } + + // Directories in $PATH + // - if a manpath mapping exists, use that mapping + // - if a directory "<path>/man" or "<path>/../man" exists, add it + // to the man path (the actual existence check is done further down) + + if ( ::getenv("PATH") ) { + const QStringList path = + QStringList::split( ":", + QString::fromLocal8Bit( ::getenv("PATH") ) ); + + for ( QStringList::const_iterator it = path.begin(); + it != path.end(); + ++it ) + { + const QString dir = QDir::cleanDirPath( *it ); + QString mandir = manpath_map[ dir ]; + + if ( !mandir.isEmpty() ) { + // a path mapping exists + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + } + else { + // no manpath mapping, use "<path>/man" and "<path>/../man" + + mandir = dir + QString( "/man" ); + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + + int pos = dir.findRev( '/' ); + if ( pos > 0 ) { + mandir = dir.left( pos ) + QString("/man"); + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + } + } + QString catmandir = mandb_map[ mandir ]; + if ( !mandir.isEmpty() ) + { + if ( constr_catmanpath.findIndex( catmandir ) == -1 ) + constr_catmanpath += catmandir; + } + else + { + // What is the default mapping? + catmandir = mandir; + catmandir.replace("/usr/share/","/var/cache/"); + if ( constr_catmanpath.findIndex( catmandir ) == -1 ) + constr_catmanpath += catmandir; + } + } + } +} + +void MANProtocol::checkManPaths() +{ + static bool inited = false; + + if (inited) + return; + + inited = true; + + const QString manpath_env = QString::fromLocal8Bit( ::getenv("MANPATH") ); + //QString mansect_env = QString::fromLocal8Bit( ::getenv("MANSECT") ); + + // Decide if $MANPATH is enough on its own or if it should be merged + // with the constructed path. + // A $MANPATH starting or ending with ":", or containing "::", + // should be merged with the constructed path. + + bool construct_path = false; + + if ( manpath_env.isEmpty() + || manpath_env[0] == ':' + || manpath_env[manpath_env.length()-1] == ':' + || manpath_env.contains( "::" ) ) + { + construct_path = true; // need to read config file + } + + // Constucted man path -- consists of paths from + // /etc/man.conf + // default dirs + // $PATH + QStringList constr_path; + QStringList constr_catmanpath; // catmanpath + + QString conf_section; + + if ( construct_path ) + { + constructPath(constr_path, constr_catmanpath); + } + + m_mandbpath=constr_catmanpath; + + // Merge $MANPATH with the constructed path to form the + // actual manpath. + // + // The merging syntax with ":" and "::" in $MANPATH will be + // satisfied if any empty string in path_list_env (there + // should be 1 or 0) is replaced by the constructed path. + + const QStringList path_list_env = QStringList::split( ':', manpath_env , true ); + + for ( QStringList::const_iterator it = path_list_env.begin(); + it != path_list_env.end(); + ++it ) + { + struct stat sbuf; + + QString dir = (*it); + + if ( !dir.isEmpty() ) { + // Add dir to the man path if it exists + if ( m_manpath.findIndex( dir ) == -1 ) { + if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + m_manpath += dir; + } + } + } + else { + // Insert constructed path ($MANPATH was empty, or + // there was a ":" at an end or "::") + + for ( QStringList::Iterator it2 = constr_path.begin(); + it2 != constr_path.end(); + it2++ ) + { + dir = (*it2); + + if ( !dir.isEmpty() ) { + if ( m_manpath.findIndex( dir ) == -1 ) { + if ( ::stat( QFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + m_manpath += dir; + } + } + } + } + } + } + +/* sections are not used + // Sections + QStringList m_mansect = QStringList::split( ':', mansect_env, true ); + + const char* default_sect[] = + { "1", "2", "3", "4", "5", "6", "7", "8", "9", "n", 0L }; + + for ( int i = 0; default_sect[i] != 0L; i++ ) + if ( m_mansect.findIndex( QString( default_sect[i] ) ) == -1 ) + m_mansect += QString( default_sect[i] ); +*/ + +} + + +//#define _USE_OLD_CODE + +#ifdef _USE_OLD_CODE +#warning "using old code" +#else + +// Define this, if you want to compile with qsort from stdlib.h +// else the Qt Heapsort will be used. +// Note, qsort seems to be a bit faster (~10%) on a large man section +// eg. man section 3 +#define _USE_QSORT + +// Setup my own structure, with char pointers. +// from now on only pointers are copied, no strings +// +// containing the whole path string, +// the beginning of the man page name +// and the length of the name +struct man_index_t { + char *manpath; // the full path including man file + const char *manpage_begin; // pointer to the begin of the man file name in the path + int manpage_len; // len of the man file name +}; +typedef man_index_t *man_index_ptr; + +#ifdef _USE_QSORT +int compare_man_index(const void *s1, const void *s2) +{ + struct man_index_t *m1 = *(struct man_index_t **)s1; + struct man_index_t *m2 = *(struct man_index_t **)s2; + int i; + // Compare the names of the pages + // with the shorter length. + // Man page names are not '\0' terminated, so + // this is a bit tricky + if ( m1->manpage_len > m2->manpage_len) + { + i = qstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m2->manpage_len); + if (!i) + return 1; + return i; + } + + if ( m1->manpage_len < m2->manpage_len) + { + i = qstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + if (!i) + return -1; + return i; + } + + return qstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); +} + +#else /* !_USE_QSORT */ +#warning using heapsort +// Set up my own man page list, +// with a special compare function to sort itself +typedef QPtrList<struct man_index_t> QManIndexListBase; +typedef QPtrListIterator<struct man_index_t> QManIndexListIterator; + +class QManIndexList : public QManIndexListBase +{ +public: +private: + int compareItems( QPtrCollection::Item s1, QPtrCollection::Item s2 ) + { + struct man_index_t *m1 = (struct man_index_t *)s1; + struct man_index_t *m2 = (struct man_index_t *)s2; + int i; + // compare the names of the pages + // with the shorter length + if (m1->manpage_len > m2->manpage_len) + { + i = qstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m2->manpage_len); + if (!i) + return 1; + return i; + } + + if (m1->manpage_len > m2->manpage_len) + { + + i = qstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + if (!i) + return -1; + return i; + } + + return qstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + } +}; + +#endif /* !_USE_QSORT */ +#endif /* !_USE_OLD_CODE */ + + + + +void MANProtocol::showIndex(const QString& section) +{ + QByteArray array; + QTextStream os(array, IO_WriteOnly); + os.setEncoding(QTextStream::UnicodeUTF8); + + // print header + os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\">" << endl; + os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl; + os << "<title>" << i18n("UNIX Manual Index") << "</title>" << endl; + if ( !m_manCSSFile.isEmpty() ) + os << "<link href=\"file:///" << m_manCSSFile << "\" type=\"text/css\" rel=\"stylesheet\">" << endl; + os << "</head>" << endl; + os << "<body><div class=\"secidxmain\">" << endl; + os << "<h1>" << i18n( "Index for Section %1: %2").arg(section).arg(sectionName(section)) << "</h1>" << endl; + + // compose list of search paths ------------------------------------------------------------- + + checkManPaths(); + infoMessage(i18n("Generating Index")); + + // search for the man pages + QStringList pages = findPages( section, QString::null ); + + QMap<QString, QString> indexmap = buildIndexMap(section); + + // print out the list + os << "<table>" << endl; + +#ifdef _USE_OLD_CODE + pages.sort(); + + QMap<QString, QString> pagemap; + + QStringList::ConstIterator page; + for (page = pages.begin(); page != pages.end(); ++page) + { + QString fileName = *page; + + stripExtension( &fileName ); + + pos = fileName.findRev('/'); + if (pos > 0) + fileName = fileName.mid(pos+1); + + if (!fileName.isEmpty()) + pagemap[fileName] = *page; + + } + + for (QMap<QString,QString>::ConstIterator it = pagemap.begin(); + it != pagemap.end(); ++it) + { + os << "<tr><td><a href=\"man:" << it.data() << "\">\n" + << it.key() << "</a></td><td> </td><td> " + << (indexmap.contains(it.key()) ? indexmap[it.key()] : "" ) + << "</td></tr>" << endl; + } + +#else /* ! _USE_OLD_CODE */ + +#ifdef _USE_QSORT + + int listlen = pages.count(); + man_index_ptr *indexlist = new man_index_ptr[listlen]; + listlen = 0; + +#else /* !_USE_QSORT */ + + QManIndexList manpages; + manpages.setAutoDelete(TRUE); + +#endif /* _USE_QSORT */ + + QStringList::const_iterator page; + for (page = pages.begin(); page != pages.end(); ++page) + { + // I look for the beginning of the man page name + // i.e. "bla/pagename.3.gz" by looking for the last "/" + // Then look for the end of the name by searching backwards + // for the last ".", not counting zip extensions. + // If the len of the name is >0, + // store it in the list structure, to be sorted later + + char *manpage_end; + struct man_index_t *manindex = new man_index_t; + manindex->manpath = strdup((*page).utf8()); + + manindex->manpage_begin = strrchr(manindex->manpath, '/'); + if (manindex->manpage_begin) + { + manindex->manpage_begin++; + assert(manindex->manpage_begin >= manindex->manpath); + } + else + { + manindex->manpage_begin = manindex->manpath; + assert(manindex->manpage_begin >= manindex->manpath); + } + + // Skip extension ".section[.gz]" + + char *begin = (char*)(manindex->manpage_begin); + int len = strlen( begin ); + char *end = begin+(len-1); + + if ( len >= 3 && strcmp( end-2, ".gz" ) == 0 ) + end -= 3; + else if ( len >= 2 && strcmp( end-1, ".Z" ) == 0 ) + end -= 2; + else if ( len >= 2 && strcmp( end-1, ".z" ) == 0 ) + end -= 2; + else if ( len >= 4 && strcmp( end-3, ".bz2" ) == 0 ) + end -= 4; + + while ( end >= begin && *end != '.' ) + end--; + + if ( end < begin ) + manpage_end = 0; + else + manpage_end = end; + + if (NULL == manpage_end) + { + // no '.' ending ??? + // set the pointer past the end of the filename + manindex->manpage_len = (*page).length(); + manindex->manpage_len -= (manindex->manpage_begin - manindex->manpath); + assert(manindex->manpage_len >= 0); + } + else + { + manindex->manpage_len = (manpage_end - manindex->manpage_begin); + assert(manindex->manpage_len >= 0); + } + + if (0 < manindex->manpage_len) + { + +#ifdef _USE_QSORT + + indexlist[listlen] = manindex; + listlen++; + +#else /* !_USE_QSORT */ + + manpages.append(manindex); + +#endif /* _USE_QSORT */ + + } + } + + // + // Now do the sorting on the page names + // and the printout afterwards + // While printing avoid duplicate man page names + // + + struct man_index_t dummy_index = {0l,0l,0}; + struct man_index_t *last_index = &dummy_index; + +#ifdef _USE_QSORT + + // sort and print + qsort(indexlist, listlen, sizeof(struct man_index_t *), compare_man_index); + + QChar firstchar, tmp; + QString indexLine="<div class=\"secidxshort\">\n"; + if (indexlist[0]->manpage_len>0) + { + firstchar=QChar((indexlist[0]->manpage_begin)[0]).lower(); + + const QString appendixstr = QString( + " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n" + ).arg(firstchar).arg(firstchar).arg(firstchar); + indexLine.append(appendixstr); + } + os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\"" + << firstchar << "\">" << firstchar <<"</a>\n</td></tr>" << endl; + + for (int i=0; i<listlen; i++) + { + struct man_index_t *manindex = indexlist[i]; + + // qstrncmp(): + // "last_man" has already a \0 string ending, but + // "manindex->manpage_begin" has not, + // so do compare at most "manindex->manpage_len" of the strings. + if (last_index->manpage_len == manindex->manpage_len && + !qstrncmp(last_index->manpage_begin, + manindex->manpage_begin, + manindex->manpage_len) + ) + { + continue; + } + + tmp=QChar((manindex->manpage_begin)[0]).lower(); + if (firstchar != tmp) + { + firstchar = tmp; + os << "<tr><td class=\"secidxnextletter\"" << " colspan=\"3\">\n <a name=\"" + << firstchar << "\">" << firstchar << "</a>\n</td></tr>" << endl; + + const QString appendixstr = QString( + " [<a href=\"#%1\" accesskey=\"%2\">%3</a>]\n" + ).arg(firstchar).arg(firstchar).arg(firstchar); + indexLine.append(appendixstr); + } + os << "<tr><td><a href=\"man:" + << manindex->manpath << "\">\n"; + + ((char *)manindex->manpage_begin)[manindex->manpage_len] = '\0'; + os << manindex->manpage_begin + << "</a></td><td> </td><td> " + << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" ) + << "</td></tr>" << endl; + last_index = manindex; + } + indexLine.append("</div>"); + + for (int i=0; i<listlen; i++) { + ::free(indexlist[i]->manpath); // allocated by strdup + delete indexlist[i]; + } + + delete [] indexlist; + +#else /* !_USE_QSORT */ + + manpages.sort(); // using + + for (QManIndexListIterator mit(manpages); + mit.current(); + ++mit ) + { + struct man_index_t *manindex = mit.current(); + + // qstrncmp(): + // "last_man" has already a \0 string ending, but + // "manindex->manpage_begin" has not, + // so do compare at most "manindex->manpage_len" of the strings. + if (last_index->manpage_len == manindex->manpage_len && + !qstrncmp(last_index->manpage_begin, + manindex->manpage_begin, + manindex->manpage_len) + ) + { + continue; + } + + os << "<tr><td><a href=\"man:" + << manindex->manpath << "\">\n"; + + manindex->manpage_begin[manindex->manpage_len] = '\0'; + os << manindex->manpage_begin + << "</a></td><td> </td><td> " + << (indexmap.contains(manindex->manpage_begin) ? indexmap[manindex->manpage_begin] : "" ) + << "</td></tr>" << endl; + last_index = manindex; + } +#endif /* _USE_QSORT */ +#endif /* _USE_OLD_CODE */ + + os << "</table></div>" << endl; + + os << indexLine << endl; + + // print footer + os << "</body></html>" << endl; + + infoMessage(QString::null); + mimeType("text/html"); + data(array); + finished(); +} + +void MANProtocol::listDir(const KURL &url) +{ + kdDebug( 7107 ) << "ENTER listDir: " << url.prettyURL() << endl; + + QString title; + QString section; + + if ( !parseUrl(url.path(), title, section) ) { + error( KIO::ERR_MALFORMED_URL, url.url() ); + return; + } + + QStringList list = findPages( section, QString::null, false ); + + UDSEntryList uds_entry_list; + UDSEntry uds_entry; + UDSAtom uds_atom; + + uds_atom.m_uds = KIO::UDS_NAME; // we only do names... + uds_entry.append( uds_atom ); + + QStringList::Iterator it = list.begin(); + QStringList::Iterator end = list.end(); + + for ( ; it != end; ++it ) { + stripExtension( &(*it) ); + + uds_entry[0].m_str = *it; + uds_entry_list.append( uds_entry ); + } + + listEntries( uds_entry_list ); + finished(); +} + +void MANProtocol::getProgramPath() +{ + if (!mySgml2RoffPath.isEmpty()) + return; + + mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff"); + if (!mySgml2RoffPath.isEmpty()) + return; + + /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */ + mySgml2RoffPath = KGlobal::dirs()->findExe("sgml2roff", QString(SGML2ROFF_DIRS)); + if (!mySgml2RoffPath.isEmpty()) + return; + + /* Cannot find sgml2roff programm: */ + outputError(i18n("Could not find the sgml2roff program on your system. Please install it, if necessary, and extend the search path by adjusting the environment variable PATH before starting KDE.")); + finished(); + exit(); +} diff --git a/kioslave/man/kio_man.css b/kioslave/man/kio_man.css new file mode 100644 index 000000000..8a9f378bc --- /dev/null +++ b/kioslave/man/kio_man.css @@ -0,0 +1,21 @@ +body {background-color:#fffff} + +/*for the list of one manpage section*/ +.secidxshort { + display:block; + position:absolute;overflow:auto; + top:0px;bottom:95%;left:0;right:0; +} +.secidxmain { +/*misfortunately accessing anchors in a scrollview + doesn't seem to work yet with konqi, so:*/ +/* + position:absolute;overflow:auto; + top:5%;bottom:0;left:0;right:0; +*/ +} +.secidxnextletter { + font-size:larger; + border-bottom:1px solid black; + text-align:center +} diff --git a/kioslave/man/kio_man.h b/kioslave/man/kio_man.h new file mode 100644 index 000000000..f571082db --- /dev/null +++ b/kioslave/man/kio_man.h @@ -0,0 +1,99 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Matthias Hoelzer-Kluepfel <mhk@caldera.de> + + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ +#ifndef __kio_man_h__ +#define __kio_man_h__ + + +#include <qstring.h> +#include <qcstring.h> +#include <qstringlist.h> +#include <qdict.h> +#include <qbuffer.h> + + +#include <kio/global.h> +#include <kio/slavebase.h> + + +class MANProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT + +public: + + MANProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~MANProtocol(); + + virtual void get(const KURL& url); + virtual void stat(const KURL& url); + + virtual void mimetype(const KURL &url); + virtual void listDir(const KURL &url); + + void outputError(const QString& errmsg); + void outputMatchingPages(const QStringList &matchingPages); + + void showMainIndex(); + void showIndex(const QString& section); + + // the following two functions are the interface to man2html + void output(const char *insert); + char *readManPage(const char *filename); + + static MANProtocol *self(); + +private slots: + void slotGetStdOutput(KProcess*, char*, int); + +private: + void checkManPaths(); + QStringList manDirectories(); + QMap<QString, QString> buildIndexMap(const QString& section); + bool addWhatIs(QMap<QString, QString>& i, const QString& f, const QString& mark); + void parseWhatIs( QMap<QString, QString> &i, QTextStream &t, const QString &mark ); + QStringList findPages(const QString& section, + const QString &title, + bool full_path = true); + + void addToBuffer(const char *buffer, int buflen); + QString pageName(const QString& page) const; + QStringList buildSectionList(const QStringList& dirs) const; + void constructPath(QStringList& constr_path, QStringList constr_catmanpath); +private: + static MANProtocol *_self; + QCString lastdir; + + void findManPagesInSection(const QString &dir, const QString &title, bool full_path, QStringList &list); + QStringList m_manpath; ///< Path of man directories + QStringList m_mandbpath; ///< Path of catman directories + QStringList section_names; + + QString myStdStream; + QString mySgml2RoffPath; + void getProgramPath(); + + QCString m_htmlPath; ///< Path to KDE resources, encoded for HTML + QCString m_cssPath; ///< Path to KDE resources, encoded for CSS + QBuffer m_outputBuffer; ///< Buffer for the output + QString m_manCSSFile; ///< Path to kio_man.css +}; + + +#endif diff --git a/kioslave/man/kio_man_test.cpp b/kioslave/man/kio_man_test.cpp new file mode 100644 index 000000000..bfb78a652 --- /dev/null +++ b/kioslave/man/kio_man_test.cpp @@ -0,0 +1,38 @@ + + +#include <qobject.h> + +#include "kio_man.h" + + +#include <kapplication.h> +#include <klocale.h> + + +class kio_man_test : public MANProtocol +{ + Q_OBJECT + +public: + kio_man_test(const QCString &pool_socket, const QCString &app_socket); + +protected: + virtual void data(int); + +}; + + + + + +int main(int argc, char **argv) +{ + KApplication a( argc, argv , "p2"); + + MANProtocol testproto("/tmp/kiotest.in", "/tmp/kiotest.out"); + testproto.showIndex("3"); + + return 0; +} + + diff --git a/kioslave/man/kmanpart.cpp b/kioslave/man/kmanpart.cpp new file mode 100644 index 000000000..307a8c6b2 --- /dev/null +++ b/kioslave/man/kmanpart.cpp @@ -0,0 +1,115 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kmanpart.h" +#include <qstring.h> + +#include <kinstance.h> +#include <kglobal.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kaboutdata.h> +#include <kdeversion.h> + +extern "C" +{ + KDE_EXPORT void* init_libkmanpart() + { + return new KManPartFactory; + } +} + +KInstance* KManPartFactory::s_instance = 0L; +KAboutData* KManPartFactory::s_about = 0L; + +KManPartFactory::KManPartFactory( QObject* parent, const char* name ) + : KParts::Factory( parent, name ) +{} + +KManPartFactory::~KManPartFactory() +{ + delete s_instance; + s_instance = 0L; + delete s_about; +} + +KParts::Part* KManPartFactory::createPartObject( QWidget * parentWidget, const char* /*widgetName*/, QObject *, + const char* name, const char* /*className*/,const QStringList & ) +{ + KManPart* part = new KManPart(parentWidget, name ); + return part; +} + +KInstance* KManPartFactory::instance() +{ + if( !s_instance ) + { + s_about = new KAboutData( "kmanpart", + I18N_NOOP( "KMan" ), KDE_VERSION_STRING ); + s_instance = new KInstance( s_about ); + } + return s_instance; +} + + +KManPart::KManPart( QWidget * parent, const char * name ) +: KHTMLPart( parent, name ) +,m_job(0) +{ + KInstance * instance = new KInstance( "kmanpart" ); + setInstance( instance ); + m_extension=new KParts::BrowserExtension(this); +} + +bool KManPart::openURL( const KURL &url ) +{ + return KParts::ReadOnlyPart::openURL(url); +} + +bool KManPart::openFile() +{ + if (m_job!=0) + m_job->kill(); + + begin(); + + KURL url; + url.setProtocol( "man" ); + url.setPath( m_file ); + + m_job = KIO::get( url, true, false ); + connect( m_job, SIGNAL( data( KIO::Job *, const QByteArray &) ), SLOT( readData( KIO::Job *, const QByteArray &) ) ); + connect( m_job, SIGNAL( result( KIO::Job * ) ), SLOT( jobDone( KIO::Job * ) ) ); + return true; +} + +void KManPart::readData(KIO::Job * , const QByteArray & data) +{ + write(data,data.size()); +} + +void KManPart::jobDone( KIO::Job *) +{ + m_job=0; + end(); +} + +#include "kmanpart.moc" + diff --git a/kioslave/man/kmanpart.desktop b/kioslave/man/kmanpart.desktop new file mode 100644 index 000000000..1d9bb06bf --- /dev/null +++ b/kioslave/man/kmanpart.desktop @@ -0,0 +1,91 @@ +[Desktop Entry] +Type=Service +Comment=Embeddable Troff Viewer +Comment[af]=Inlegbare Troff Kyker +Comment[ar]=مستعرض Troff المدمج +Comment[be]=Убудаваны праглÑдальнік Troff +Comment[bg]=Визуализатор за вграждане на Troff +Comment[bn]=সনà§à¦¨à¦¿à¦¬à§‡à¦¶à¦¯à§‹à¦—à§à¦¯ টà§à¦°à¦«à§ পà§à¦°à¦¦à¦°à§à¦¶à¦• +Comment[bs]=Ugradivi preglednik Troff datoteka +Comment[ca]=Visor Troff encastable +Comment[cs]=Komponenta pro zobrazovánà manuálových stránek +Comment[csb]=Przezérnik lopków troff +Comment[cy]=Gwelydd Troff Mewnadeiladwy +Comment[da]=Indlejrbar Troff-fremviser +Comment[de]=Eingebetteter Troff-Betrachter +Comment[el]=Ενσωματώσιμος Ï€ÏοβολÎας Troff +Comment[eo]=Enkonstruebla bildrigardilo +Comment[es]=Visor empotrable de Troff +Comment[et]=Põimitav Troff komponent +Comment[eu]=Troff ikustailu txertagarria +Comment[fa]=مشاهده‌گر Troff نهÙتنی +Comment[fi]=Upotettava Troff-näytin +Comment[fr]=Afficheur troff incorporé +Comment[fy]=Ynsletten Troff-werjefteprogramma +Comment[ga]=Amharcán Inleabaithe troff +Comment[gl]=Visor de Troff Incrustábel +Comment[he]=מציג Troff בר־הטבעה +Comment[hi]=à¤à¤®à¥à¤¬à¥‡à¤¡à¥‡à¤¬à¤² टà¥à¤°à¤¾à¤« पà¥à¤°à¤¦à¤°à¥à¤¶à¤• +Comment[hr]=Ugradivi preglednik slika +Comment[hu]=Beágyazható Troff-komponens +Comment[is]=Ãvefjanlegur troff skoðari +Comment[it]=Visualizzatore integrabile di file Troff +Comment[ja]=埋ã‚込㿠Troff ビューア +Comment[ka]=ჩáƒáƒ¨áƒ”ნებული დáƒáƒ›áƒ—ვáƒáƒšáƒ˜áƒ”რებელი პრáƒáƒ’რáƒáƒ›áƒ Troff +Comment[kk]=Құрамына енгізілетін Troff қарау құралы +Comment[km]=កម្មវិធី​មើល Troff ដែល​អាច​បង្កប់​បាន +Comment[ko]=í¬í•¨ 가능한 Troff ë·°ì–´ +Comment[lo]=ຄàºàº¡à»‚ປເນັນຕົວສະà»àº”ງພາບທີ່àºàº±à»ˆàº‡à»„ດ້ +Comment[lt]=Ä®dedamas Troff žiÅ«riklis +Comment[lv]=Iegults Troff skatÄ«tÄjs +Comment[mk]=Вгнездлив Troff прегледувач +Comment[mn]=Холбох боломжит Troff-Харагч +Comment[ms]=Pemapar Troff Boleh Benam +Comment[mt]=Werrej integrat Troff +Comment[nb]=Innebyggbar Troff-viser +Comment[nds]=Kieker för Troff, de inbett warrn kann +Comment[ne]=समà¥à¤®à¤¿à¤²à¤¿à¤¤ गरà¥à¤¨ सकिने टà¥à¤°à¤« दरà¥à¤¶à¤• +Comment[nl]=Ingebed Troff-weergaveprogramma +Comment[nn]=Innebyggbar Troff-visar +Comment[nso]=Selebeledi seo se Robaditswego sa Troff +Comment[pa]=ਸ਼ਾਮਿਲ ਹੋਣਯੋਗ Troff ਦਰਸ਼ਕ +Comment[pl]=PrzeglÄ…dania plików troff +Comment[pt]=Visualizador de Troff incorporado +Comment[pt_BR]=Visualizar Troff integrado +Comment[ro]=Componentă de vizualizare Troff înglobată +Comment[ru]=Ð’ÑÑ‚Ñ€Ð°Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° проÑмотра Troff +Comment[rw]=Mugaragaza Troff Ishyirwamo +Comment[se]=Vuojohahtti Troff-cájeheaddji +Comment[sk]=Vložiteľný prehliadaÄ Troff +Comment[sl]=VkljuÄen pregledovalnik za Troff +Comment[sr]=Уградиви Troff приказивач +Comment[sr@Latn]=Ugradivi Troff prikazivaÄ +Comment[sv]=Inbäddningsbar Troff-visare +Comment[ta]=உடà¯à®ªà¯Šà®¤à®¿à®¨à¯à®¤ டà¯à®°à®¾à®ƒà®ªà¯ காடà¯à®šà®¿ +Comment[te]=పొదగదగà±à°— à°Ÿà±à°°à°¾à°«à± వీకà±à°·à°¿à°£à°¿ +Comment[tg]=Биношавандаи барномаи намоишгари Troff +Comment[th]=โปรà¹à¸à¸£à¸¡à¸”ู Troff ที่สามารถà¸à¸±à¸‡à¸•à¸±à¸§à¹„ด้ +Comment[tr]=Gömülebilir Troff Görüntüleyici +Comment[tt]=Quşılulı Troff-Kürsätkeç +Comment[uk]=Вмонтований переглÑдач Troff +Comment[uz]=Ichiga oÊ»rnatib boÊ»ladigan Troff-faylini koÊ»ruvchi +Comment[uz@cyrillic]=Ичига ўрнатиб бўладиган Troff-файлини кўрувчи +Comment[ven]=Tshivhoni tsha Troff tsho dzheniswaho +Comment[vi]=Trình xem Troff nhúng được +Comment[wa]=RavalÃ¥ve hÃ¥yneu di fitchîs Troff +Comment[xh]=Umboniseli we Troff Elungiselekayo +Comment[zh_CN]=嵌入的 Troff 查看器 +Comment[zh_TW]=å¯åµŒå…¥çš„ Troff 檢視元件 +Comment[zu]=Umbukisi we-Troff Oshuthekiwe +MimeType=application/x-troff;application/x-troff-man; +Name=KManPart +Name[eo]=KMan-parto +Name[hi]=के-मेन-पारà¥à¤Ÿ +Name[lo]=ຕົວຮງàºàºžàº·à»‰àº™àº—ີ່ທຳງານ - K +Name[ne]=K मà¥à¤¯à¤¾à¤¨ à¤à¤¾à¤— +Name[pt_BR]=Componente KMan +Name[ro]=Componentă KMan +Name[sv]=Kman-del +Name[te]=కెమేనౠà°à°¾à°—à°‚ +ServiceTypes=KParts/ReadOnlyPart,Browser/View +X-KDE-Library=libkmanpart diff --git a/kioslave/man/kmanpart.h b/kioslave/man/kmanpart.h new file mode 100644 index 000000000..f68b68784 --- /dev/null +++ b/kioslave/man/kmanpart.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + + +#ifndef KMANPART_H +#define KMANPART_H + +#include <kparts/factory.h> +#include <kparts/part.h> +#include <kparts/browserextension.h> +#include <khtml_part.h> +#include <kio/job.h> +#include <kio/jobclasses.h> + +#include <qcstring.h> + +class KInstance; +class KAboutData; + +/** + * Man Page Viewer + * \todo: Why is it needed? Why is KHTML alone not possible? + */ +class KManPartFactory: public KParts::Factory +{ + Q_OBJECT + public: + KManPartFactory( QObject * parent = 0, const char * name = 0 ); + virtual ~KManPartFactory(); + + virtual KParts::Part* createPartObject( QWidget * parentWidget, const char * widgetName , + QObject* parent, const char* name, const char * classname, + const QStringList &args); + + static KInstance * instance(); + + private: + static KInstance * s_instance; + static KAboutData * s_about; + +}; + +class KManPart : public KHTMLPart +{ + Q_OBJECT + public: + KManPart( QWidget * parent, const char * name = 0L ); + KParts::BrowserExtension * extension() {return m_extension;} + + public slots: + virtual bool openURL( const KURL &url ); + protected slots: + void readData(KIO::Job * , const QByteArray & data); + void jobDone( KIO::Job *); + protected: + virtual bool openFile(); + KInstance *m_instance; + KParts::BrowserExtension * m_extension; + KIO::TransferJob *m_job; +}; + +#endif + diff --git a/kioslave/man/man.protocol b/kioslave/man/man.protocol new file mode 100644 index 000000000..5df21f19d --- /dev/null +++ b/kioslave/man/man.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=kio_man +protocol=man +input=none +output=filesystem +reading=true +listing=Name +defaultMimetype=text/html +determineMimetypeFromExtension=false +DocPath=kioslave/man.html +Icon=man +Class=:local diff --git a/kioslave/man/man2html.cpp b/kioslave/man/man2html.cpp new file mode 100644 index 000000000..725fba36a --- /dev/null +++ b/kioslave/man/man2html.cpp @@ -0,0 +1,5683 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2005 Nicolas GOUTTE <goutte@kde.org> + + ### TODO: who else? +*/ + +// Start of verbatim comment + +/* +** This program was written by Richard Verhoeven (NL:5482ZX35) +** at the Eindhoven University of Technology. Email: rcb5@win.tue.nl +** +** Permission is granted to distribute, modify and use this program as long +** as this comment is not removed or changed. +*/ + +// End of verbatim comment + +// kate: space-indent on; indent-width 4; replace-tabs on; + +/* + * man2html-linux-1.0/1.1 + * This version modified for Redhat/Caldera linux - March 1996. + * Michael Hamilton <michael@actrix.gen.nz>. + * + * man2html-linux-1.2 + * Added support for BSD mandoc pages - I didn't have any documentation + * on the mandoc macros, so I may have missed some. + * Michael Hamilton <michael@actrix.gen.nz>. + * + * vh-man2html-1.3 + * Renamed to avoid confusion (V for Verhoeven, H for Hamilton). + * + * vh-man2html-1.4 + * Now uses /etc/man.config + * Added support for compressed pages. + * Added "length-safe" string operations for client input parameters. + * More secure, -M secured, and client input string lengths checked. + * + */ + +/* +** If you want to use this program for your WWW server, adjust the line +** which defines the CGIBASE or compile it with the -DCGIBASE='"..."' option. +** +** You have to adjust the built-in manpath to your local system. Note that +** every directory should start and end with the '/' and that the first +** directory should be "/" to allow a full path as an argument. +** +** The program first check if PATH_INFO contains some information. +** If it does (t.i. man2html/some/thing is used), the program will look +** for a manpage called PATH_INFO in the manpath. +** +** Otherwise the manpath is searched for the specified command line argument, +** where the following options can be used: +** +** name name of manpage (csh, printf, xv, troff) +** section the section (1 2 3 4 5 6 7 8 9 n l 1v ...) +** -M path an extra directory to look for manpages (replaces "/") +** +** If man2html finds multiple manpages that satisfy the options, an index +** is displayed and the user can make a choice. If only one page is +** found, that page will be displayed. +** +** man2html will add links to the converted manpages. The function add_links +** is used for that. At the moment it will add links as follows, where +** indicates what should match to start with: +** ^^^ +** Recognition Item Link +** ---------------------------------------------------------- +** name(*) Manpage ../man?/name.* +** ^ +** name@hostname Email address mailto:name@hostname +** ^ +** method://string URL method://string +** ^^^ +** www.host.name WWW server http://www.host.name +** ^^^^ +** ftp.host.name FTP server ftp://ftp.host.name +** ^^^^ +** <file.h> Include file file:/usr/include/file.h +** ^^^ +** +** Since man2html does not check if manpages, hosts or email addresses exist, +** some links might not work. For manpages, some extra checks are performed +** to make sure not every () pair creates a link. Also out of date pages +** might point to incorrect places. +** +** The program will not allow users to get system specific files, such as +** /etc/passwd. It will check that "man" is part of the specified file and +** that "/../" isn't. Even if someone manages to get such file, man2html will +** handle it like a manpage and will usually not produce any output (or crash). +** +** If you find any bugs when normal manpages are converted, please report +** them to me (rcb5@win.tue.nl) after you have checked that man(1) can handle +** the manpage correct. +** +** Known bugs and missing features: +** +** * Equations are not converted at all. +** * Tables are converted but some features are not possible in html. +** * The tabbing environment is converted by counting characters and adding +** spaces. This might go wrong (outside <PRE>) +** * Some manpages rely on the fact that troff/nroff is used to convert +** them and use features which are not descripted in the man manpages. +** (definitions, calculations, conditionals, requests). I can't guarantee +** that all these features work on all manpages. (I didn't have the +** time to look through all the available manpages.) +*/ + +#ifdef SIMPLE_MAN2HTML + // We suppose that we run on a standard Linux +# define HAVE_STRING_H 1 +# define HAVE_UNISTD_H 1 +#else +# include <config.h> +#endif + +#include <ctype.h> + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifdef HAVE_STRING_H +# include <string.h> +#endif + +#include <stdio.h> + +#include <qvaluestack.h> +#include <qstring.h> +#include <qptrlist.h> +#include <qmap.h> +#include <qdatetime.h> + +#ifdef SIMPLE_MAN2HTML +# include <stdlib.h> +# include <iostream> +# include <dirent.h> +# include <sys/stat.h> +# define kdDebug(x) cerr +# define kdWarning(x) cerr << "WARNING " +#else +# include <qtextcodec.h> +# include <kdebug.h> +# include <kdeversion.h> +#endif + + + +#include "man2html.h" + +using namespace std; + +#define NULL_TERMINATED(n) ((n) + 1) + +#define HUGE_STR_MAX 10000 +#define LARGE_STR_MAX 2000 +#define MED_STR_MAX 500 +#define SMALL_STR_MAX 100 +#define TINY_STR_MAX 10 + + +#if 1 +// The output is current too horrible to be called HTML 4.01 +#define DOCTYPE "<!DOCTYPE HTML>" +#else +#define DOCTYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" +#endif + +/* mdoc(7) Bl/El lists to HTML list types */ +#define BL_DESC_LIST 1 +#define BL_BULLET_LIST 2 +#define BL_ENUM_LIST 4 + +/* mdoc(7) Bd/Ed example(?) blocks */ +#define BD_LITERAL 1 +#define BD_INDENT 2 + +static int s_nroff = 1; // NROFF mode by default + +static int mandoc_name_count = 0; /* Don't break on the first Nm */ + +static char *stralloc(int len) +{ + /* allocate enough for len + NULL */ + char *news = new char [len+1]; +#ifdef SIMPLE_MAN2HTML + if (!news) + { + cerr << "man2html: out of memory" << endl; + exit(EXIT_FAILURE); + } +#else +// modern compilers do not return a NULL pointer for a new +#endif + return news; +} + +static char *strlimitcpy(char *to, char *from, int n, int limit) +{ /* Assumes space for limit plus a null */ + const int len = n > limit ? limit : n; + qstrncpy(to, from, len + 1); + to[len] = '\0'; + return to; +} + +/* below this you should not change anything unless you know a lot +** about this program or about troff. +*/ + + +/// Structure for character definitions +struct CSTRDEF { + int nr, slen; + const char *st; +}; + + + +const char NEWLINE[2]="\n"; + +/** + * Class for defining strings and macros + */ +class StringDefinition +{ +public: + StringDefinition( void ) : m_length(0) {} + StringDefinition( int len, const char* cstr ) : m_length( len ), m_output( cstr ) {} +public: + int m_length; ///< Length of output text + QCString m_output; ///< Defined string +}; + +/** + * Class for defining number registers + * \note Not for internal read-only registers + */ +class NumberDefinition +{ + public: + NumberDefinition( void ) : m_value(0), m_increment(0) {} + NumberDefinition( int value ) : m_value( value ), m_increment(0) {} + NumberDefinition( int value, int incr) : m_value( value ), m_increment( incr ) {} + public: + int m_value; ///< value of number register + int m_increment; ///< Increment of number register + // ### TODO: display form (.af) +}; + +/** + * Map of character definitions + */ +static QMap<QCString,StringDefinition> s_characterDefinitionMap; + +/** + * Map of string variable and macro definitions + * \note String variables and macros are the same thing! + */ +static QMap<QCString,StringDefinition> s_stringDefinitionMap; + +/** + * Map of number registers + * \note Intern number registers (starting with a dot are not handled here) + */ +static QMap<QCString,NumberDefinition> s_numberDefinitionMap; + +static void fill_old_character_definitions( void ); + +/** + * Initialize character variables + */ +static void InitCharacterDefinitions( void ) +{ + fill_old_character_definitions(); + // ### HACK: as we are converting to HTML too early, define characters with HTML references + s_characterDefinitionMap.insert( "<-", StringDefinition( 1, "←" ) ); // <- + s_characterDefinitionMap.insert( "->", StringDefinition( 1, "→" ) ); // -> + s_characterDefinitionMap.insert( "<>", StringDefinition( 1, "↔" ) ); // <> + s_characterDefinitionMap.insert( "<=", StringDefinition( 1, "≤" ) ); // <= + s_characterDefinitionMap.insert( ">=", StringDefinition( 1, "≥" ) ); // >= + // End HACK +} + +/** + * Initialize string variables + */ +static void InitStringDefinitions( void ) +{ + // mdoc-only, see mdoc.samples(7) + s_stringDefinitionMap.insert( "<=", StringDefinition( 1, "≤" ) ); + s_stringDefinitionMap.insert( ">=", StringDefinition( 1, "≥" ) ); + s_stringDefinitionMap.insert( "Rq", StringDefinition( 1, "”" ) ); + s_stringDefinitionMap.insert( "Lq", StringDefinition( 1, "“" ) ); + s_stringDefinitionMap.insert( "ua", StringDefinition( 1, "&circ" ) ); // Note this is different from \(ua + s_stringDefinitionMap.insert( "aa", StringDefinition( 1, "´" ) ); + s_stringDefinitionMap.insert( "ga", StringDefinition( 1, "`" ) ); + s_stringDefinitionMap.insert( "q", StringDefinition( 1, """ ) ); + s_stringDefinitionMap.insert( "Pi", StringDefinition( 1, "π" ) ); + s_stringDefinitionMap.insert( "Ne", StringDefinition( 1, "≠" ) ); + s_stringDefinitionMap.insert( "Le", StringDefinition( 1, "≤" ) ); + s_stringDefinitionMap.insert( "Ge", StringDefinition( 1, "≥" ) ); + s_stringDefinitionMap.insert( "Lt", StringDefinition( 1, "<" ) ); + s_stringDefinitionMap.insert( "Gt", StringDefinition( 1, ">" ) ); + s_stringDefinitionMap.insert( "Pm", StringDefinition( 1, "±" ) ); + s_stringDefinitionMap.insert( "If", StringDefinition( 1, "∞" ) ); + s_stringDefinitionMap.insert( "Na", StringDefinition( 3, "NaN" ) ); + s_stringDefinitionMap.insert( "Ba", StringDefinition( 1, "|" ) ); + // end mdoc-only + // man(7) + s_stringDefinitionMap.insert( "Tm", StringDefinition( 1, "™" ) ); // \*(TM + s_stringDefinitionMap.insert( "R", StringDefinition( 1, "®" ) ); // \*R + // end man(7) + // Missing characters from man(7): + // \*S "Change to default font size" +#ifndef SIMPLE_MAN2HTML + // Special KDE KIO man: + const QCString kdeversion(KDE_VERSION_STRING); + s_stringDefinitionMap.insert( ".KDE_VERSION_STRING", StringDefinition( kdeversion.length(), kdeversion ) ); +#endif +} + +/** + * Initialize number registers + * \note Internal read-only registers are not handled here + */ +static void InitNumberDefinitions( void ) +{ + // As the date number registers are more for end-users, better choose local time. + // Groff seems to support Gregorian dates only + QDate today( QDate::currentDate( Qt::LocalTime ) ); + s_numberDefinitionMap.insert( "year", today.year() ); // Y2K-correct year + s_numberDefinitionMap.insert( "yr", today.year() - 1900 ); // Y2K-incorrect year + s_numberDefinitionMap.insert( "mo", today.month() ); + s_numberDefinitionMap.insert( "dy", today.day() ); + s_numberDefinitionMap.insert( "dw", today.dayOfWeek() ); +} + + +#define V(A,B) ((A)*256+(B)) + +//used in expand_char, e.g. for "\(bu" +// see groff_char(7) for list +static CSTRDEF standardchar[] = { + { V('*','*'), 1, "*" }, + { V('*','A'), 1, "Α" }, + { V('*','B'), 1, "Β" }, + { V('*','C'), 1, "Ξ" }, + { V('*','D'), 1, "Δ" }, + { V('*','E'), 1, "Ε" }, + { V('*','F'), 1, "Φ" }, + { V('*','G'), 1, "Γ" }, + { V('*','H'), 1, "Θ" }, + { V('*','I'), 1, "Ι" }, + { V('*','K'), 1, "Κ" }, + { V('*','L'), 1, "Λ" }, + { V('*','M'), 1, "&Mu:" }, + { V('*','N'), 1, "Ν" }, + { V('*','O'), 1, "Ο" }, + { V('*','P'), 1, "Π" }, + { V('*','Q'), 1, "Ψ" }, + { V('*','R'), 1, "Ρ" }, + { V('*','S'), 1, "Σ" }, + { V('*','T'), 1, "Τ" }, + { V('*','U'), 1, "Υ" }, + { V('*','W'), 1, "Ω" }, + { V('*','X'), 1, "Χ" }, + { V('*','Y'), 1, "Η" }, + { V('*','Z'), 1, "Ζ" }, + { V('*','a'), 1, "α"}, + { V('*','b'), 1, "β"}, + { V('*','c'), 1, "ξ"}, + { V('*','d'), 1, "δ"}, + { V('*','e'), 1, "ε"}, + { V('*','f'), 1, "φ"}, + { V('*','g'), 1, "γ"}, + { V('*','h'), 1, "θ"}, + { V('*','i'), 1, "ι"}, + { V('*','k'), 1, "κ"}, + { V('*','l'), 1, "λ"}, + { V('*','m'), 1, "μ" }, + { V('*','n'), 1, "ν"}, + { V('*','o'), 1, "ο"}, + { V('*','p'), 1, "π"}, + { V('*','q'), 1, "ψ"}, + { V('*','r'), 1, "ρ"}, + { V('*','s'), 1, "σ"}, + { V('*','t'), 1, "τ"}, + { V('*','u'), 1, "υ"}, + { V('*','w'), 1, "ω"}, + { V('*','x'), 1, "χ"}, + { V('*','y'), 1, "η"}, + { V('*','z'), 1, "ζ"}, + { V('+','-'), 1, "±" }, // not in groff_char(7) + { V('+','f'), 1, "φ"}, // phi1, we use the standard phi + { V('+','h'), 1, "θ"}, // theta1, we use the standard theta + { V('+','p'), 1, "ω"}, // omega1, we use the standard omega + { V('1','2'), 1, "½" }, + { V('1','4'), 1, "¼" }, + { V('3','4'), 1, "¾" }, + { V('F','i'), 1, "ffi" }, // ffi ligature + { V('F','l'), 1, "ffl" }, // ffl ligature + { V('a','p'), 1, "~" }, + { V('b','r'), 1, "|" }, + { V('b','u'), 1, "•" }, + { V('b','v'), 1, "|" }, + { V('c','i'), 1, "○" }, // circle ### TODO verify + { V('c','o'), 1, "©" }, + { V('c','t'), 1, "¢" }, + { V('d','e'), 1, "°" }, + { V('d','g'), 1, "†" }, + { V('d','i'), 1, "÷" }, + { V('e','m'), 1, "&emdash;" }, + { V('e','n'), 1, "&endash;"}, + { V('e','q'), 1, "=" }, + { V('e','s'), 1, "∅" }, + { V('f','f'), 1, "�xFB00;" }, // ff ligature + { V('f','i'), 1, "�xFB01;" }, // fi ligature + { V('f','l'), 1, "�xFB02;" }, // fl ligature + { V('f','m'), 1, "′" }, + { V('g','a'), 1, "`" }, + { V('h','y'), 1, "-" }, + { V('l','c'), 2, "|¯" }, // ### TODO: not in groff_char(7) + { V('l','f'), 2, "|_" }, // ### TODO: not in groff_char(7) + { V('l','k'), 1, "<FONT SIZE=+2>{</FONT>" }, // ### TODO: not in groff_char(7) + { V('m','i'), 1, "-" }, // ### TODO: not in groff_char(7) + { V('m','u'), 1, "×" }, + { V('n','o'), 1, "¬" }, + { V('o','r'), 1, "|" }, + { V('p','l'), 1, "+" }, + { V('r','c'), 2, "¯|" }, // ### TODO: not in groff_char(7) + { V('r','f'), 2, "_|" }, // ### TODO: not in groff_char(7) + { V('r','g'), 1, "®" }, + { V('r','k'), 1, "<FONT SIZE=+2>}</FONT>" }, // ### TODO: not in groff_char(7) + { V('r','n'), 1, "‾" }, + { V('r','u'), 1, "_" }, + { V('s','c'), 1, "§" }, + { V('s','l'), 1, "/" }, + { V('s','q'), 2, "□" }, // WHITE SQUARE + { V('t','s'), 1, "ς" }, // FINAL SIGMA + { V('u','l'), 1, "_" }, + { V('-','D'), 1, "Ð" }, + { V('S','d'), 1, "ð" }, + { V('T','P'), 1, "Þ" }, + { V('T','p'), 1, "þ" }, + { V('A','E'), 1, "Æ" }, + { V('a','e'), 1, "æ" }, + { V('O','E'), 1, "Œ" }, + { V('o','e'), 1, "œ" }, + { V('s','s'), 1, "ß" }, + { V('\'','A'), 1, "Á" }, + { V('\'','E'), 1, "É" }, + { V('\'','I'), 1, "Í" }, + { V('\'','O'), 1, "Ó" }, + { V('\'','U'), 1, "Ú" }, + { V('\'','Y'), 1, "Ý" }, + { V('\'','a'), 1, "á" }, + { V('\'','e'), 1, "é" }, + { V('\'','i'), 1, "í" }, + { V('\'','o'), 1, "ó" }, + { V('\'','u'), 1, "ú" }, + { V('\'','y'), 1, "ý" }, + { V(':','A'), 1, "Ä" }, + { V(':','E'), 1, "Ë" }, + { V(':','I'), 1, "Ï" }, + { V(':','O'), 1, "Ö" }, + { V(':','U'), 1, "Ü" }, + { V(':','a'), 1, "ä" }, + { V(':','e'), 1, "ë" }, + { V(':','i'), 1, "ï" }, + { V(':','o'), 1, "ö" }, + { V(':','u'), 1, "ü" }, + { V(':','y'), 1, "ÿ" }, + { V('^','A'), 1, "Â" }, + { V('^','E'), 1, "Ê" }, + { V('^','I'), 1, "Î" }, + { V('^','O'), 1, "Ô" }, + { V('^','U'), 1, "Û" }, + { V('^','a'), 1, "â" }, + { V('^','e'), 1, "ê" }, + { V('^','i'), 1, "î" }, + { V('^','o'), 1, "ô" }, + { V('^','u'), 1, "û" }, + { V('`','A'), 1, "À" }, + { V('`','E'), 1, "È" }, + { V('`','I'), 1, "Ì" }, + { V('`','O'), 1, "Ò" }, + { V('`','U'), 1, "Ù" }, + { V('`','a'), 1, "à" }, + { V('`','e'), 1, "è" }, + { V('`','i'), 1, "ì" }, + { V('`','o'), 1, "ò" }, + { V('`','u'), 1, "ù" }, + { V('~','A'), 1, "Ã" }, + { V('~','N'), 1, "Ñ" }, + { V('~','O'), 1, "Õ" }, + { V('~','a'), 1, "ã" }, + { V('~','n'), 1, "&ntidle;" }, + { V('~','o'), 1, "&otidle;" }, + { V(',','C'), 1, "Ç" }, + { V(',','c'), 1, "ç" }, + { V('/','L'), 1, "Ł" }, + { V('/','l'), 1, "ł" }, + { V('/','O'), 1, "Ø" }, + { V('/','o'), 1, "ø" }, + { V('o','A'), 1, "Å" }, + { V('o','a'), 1, "å" }, + { V('a','"'), 1, "\"" }, + { V('a','-'), 1, "¯" }, + { V('a','.'), 1, "." }, + { V('a','^'), 1, "ˆ" }, + { V('a','a'), 1, "´" }, + { V('a','b'), 1, "`" }, + { V('a','c'), 1, "¸" }, + { V('a','d'), 1, "¨" }, + { V('a','h'), 1, "˂" }, // caron + { V('a','o'), 1, "˚" }, // ring + { V('a','~'), 1, "˜" }, + { V('h','o'), 1, "˛" }, // ogonek + { V('.','i'), 1, "ı" }, // dot less i + { V('C','s'), 1, "¤" }, + { V('D','o'), 1, "$" }, + { V('P','o'), 1, "£" }, + { V('Y','e'), 1, "¥" }, + { V('F','n'), 1, "ƒ" }, + { V('F','o'), 1, "«" }, + { V('F','c'), 1, "»" }, + { V('f','o'), 1, "‹" }, // single left guillemet + { V('f','c'), 1, "›" }, // single right guillemet + { V('r','!'), 1, "&iecl;" }, + { V('r','?'), 1, "¿" }, + { V('O','f'), 1, "ª" }, + { V('O','m'), 1, "º" }, + { V('p','c'), 1, "·" }, + { V('S','1'), 1, "¹" }, + { V('S','2'), 1, "²" }, + { V('S','3'), 1, "³" }, + { V('<','-'), 1, "←" }, + { V('-','>'), 1, "→" }, + { V('<','>'), 1, "↔" }, + { V('d','a'), 1, "↓" }, + { V('u','a'), 1, "↑" }, + { V('l','A'), 1, "⇐" }, + { V('r','A'), 1, "⇒" }, + { V('h','A'), 1, "⇔" }, + { V('d','A'), 1, "⇓" }, + { V('u','A'), 1, "⇑" }, + { V('b','a'), 1, "|" }, + { V('b','b'), 1, "¦" }, + { V('t','m'), 1, "™" }, + { V('d','d'), 1, "‡" }, + { V('p','s'), 1, "¶" }, + { V('%','0'), 1, "‰" }, + { V('f','/'), 1, "⁄" }, // Fraction slash + { V('s','d'), 1, "″" }, + { V('h','a'), 1, "^" }, + { V('t','i'), 1, "&tidle;" }, + { V('l','B'), 1, "[" }, + { V('r','B'), 1, "]" }, + { V('l','C'), 1, "{" }, + { V('r','C'), 1, "}" }, + { V('l','a'), 1, "<" }, + { V('r','a'), 1, ">" }, + { V('l','h'), 1, "≤" }, + { V('r','h'), 1, "≥" }, + { V('B','q'), 1, "„" }, + { V('b','q'), 1, "‚" }, + { V('l','q'), 1, "“" }, + { V('r','q'), 1, "”" }, + { V('o','q'), 1, "‘" }, + { V('c','q'), 1, "’" }, + { V('a','q'), 1, "'" }, + { V('d','q'), 1, "\"" }, + { V('a','t'), 1, "@" }, + { V('s','h'), 1, "#" }, + { V('r','s'), 1, "\\" }, + { V('t','f'), 1, "∴" }, + { V('~','~'), 1, "≅" }, + { V('~','='), 1, "≈" }, + { V('!','='), 1, "≠" }, + { V('<','='), 1, "≤" }, + { V('=','='), 1, "≡" }, + { V('=','~'), 1, "≅" }, // ### TODO: verify + { V('>','='), 1, "≥" }, + { V('A','N'), 1, "∧" }, + { V('O','R'), 1, "∨" }, + { V('t','e'), 1, "∃" }, + { V('f','a'), 1, "∀" }, + { V('A','h'), 1, "ℵ" }, + { V('I','m'), 1, "ℑ" }, + { V('R','e'), 1, "ℜ" }, + { V('i','f'), 1, "∞" }, + { V('m','d'), 1, "⋅" }, + { V('m','o'), 1, "∆" }, // element ### TODO verify + { V('n','m'), 1, "∉" }, + { V('p','t'), 1, "∝" }, + { V('p','p'), 1, "⊥" }, + { V('s','b'), 1, "⊂" }, + { V('s','p'), 1, "⊃" }, + { V('i','b'), 1, "⊆" }, + { V('i','p'), 1, "⊇" }, + { V('i','s'), 1, "∫" }, + { V('s','r'), 1, "√" }, + { V('p','d'), 1, "∂" }, + { V('c','*'), 1, "⊗" }, + { V('c','+'), 1, "⊕" }, + { V('c','a'), 1, "∩" }, + { V('c','u'), 1, "∪" }, + { V('g','r'), 1, "V" }, // gradient ### TODO Where in Unicode? + { V('C','R'), 1, "↵" }, + { V('s','t'), 2, "-)" }, // "such that" ### TODO Where in Unicode? + { V('/','_'), 1, "∠" }, + { V('w','p'), 1, "℘" }, + { V('l','z'), 1, "◊" }, + { V('a','n'), 1, "-" }, // "horizontal arrow extension" ### TODO Where in Unicode? +}; + +/* default: print code */ + + +/* static char eqndelimopen=0, eqndelimclose=0; */ +static char escapesym='\\', nobreaksym='\'', controlsym='.', fieldsym=0, padsym=0; + +static char *buffer=NULL; +static int buffpos=0, buffmax=0; +static bool scaninbuff=false; +static int itemdepth=0; +static int section=0; +static int dl_set[20]= { 0 }; +static bool still_dd=0; +static int tabstops[20] = { 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96 }; +static int maxtstop=12; +static int curpos=0; + +static char *scan_troff(char *c, bool san, char **result); +static char *scan_troff_mandoc(char *c, bool san, char **result); + +static QValueList<char*> s_argumentList; + +static QCString htmlPath, cssPath; + +static QCString s_dollarZero; // Value of $0 + +void setResourcePath(const QCString& _htmlPath, const QCString& _cssPath) +{ + htmlPath=_htmlPath; + cssPath=_cssPath; +} + +static void fill_old_character_definitions( void ) +{ + for (size_t i = 0; i < sizeof(standardchar)/sizeof(CSTRDEF); i++) + { + const int nr = standardchar[i].nr; + const char temp[3] = { nr / 256, nr % 256, 0 }; + QCString name( temp ); + s_characterDefinitionMap.insert( name, StringDefinition( standardchar[i].slen, standardchar[i].st ) ); + } +} + +static char outbuffer[NULL_TERMINATED(HUGE_STR_MAX)]; +static int no_newline_output=0; +static int newline_for_fun=0; +static bool output_possible=false; + +static const char *includedirs[] = { + "/usr/include", + "/usr/include/sys", + "/usr/local/include", + "/opt/local/include", + "/usr/ccs", + "/usr/X11R6/include", + "/usr/openwin/include", + "/usr/include/g++", + 0 +}; + +static bool ignore_links=false; + +static void add_links(char *c) +{ + /* + ** Add the links to the output. + ** At the moment the following are recognized: + ** + ** name(*) -> ../man?/name.* + ** method://string -> method://string + ** www.host.name -> http://www.host.name + ** ftp.host.name -> ftp://ftp.host.name + ** name@host -> mailto:name@host + ** <name.h> -> file:/usr/include/name.h (guess) + ** + ** Other possible links to add in the future: + ** + ** /dir/dir/file -> file:/dir/dir/file + */ + if (ignore_links) + { + output_real(c); + return; + } + + int i,j,nr; + char *f, *g,*h; + const int numtests=6; // Nmber of tests + char *idtest[numtests]; // url, mailto, www, ftp, manpage, C header file + bool ok; + /* search for (section) */ + nr=0; + idtest[0]=strstr(c+1,"://"); + idtest[1]=strchr(c+1,'@'); + idtest[2]=strstr(c,"www."); + idtest[3]=strstr(c,"ftp."); + idtest[4]=strchr(c+1,'('); + idtest[5]=strstr(c+1,".h>"); + for (i=0; i<numtests; ++i) nr += (idtest[i]!=NULL); + while (nr) { + j=-1; + for (i=0; i<numtests; i++) + if (idtest[i] && (j<0 || idtest[i]<idtest[j])) j=i; + switch (j) { + case 5: { /* <name.h> */ + f=idtest[5]; + h=f+2; + g=f; + while (g>c && g[-1]!=';') g--; + bool wrote_include = false; + + if (g!=c) { + + QCString dir; + QCString file(g, h - g + 1); + file = file.stripWhiteSpace(); + for (int index = 0; includedirs[index]; index++) { + QCString str = QCString(includedirs[index]) + "/" + file; + if (!access(str, R_OK)) { + dir = includedirs[index]; + break; + } + } + if (!dir.isEmpty()) { + + char t; + t=*g; + *g=0; + output_real(c); + *g=t;*h=0; + + QCString str; + str.sprintf("<A HREF=\"file:%s/%s\">%s</A>>", dir.data(), file.data(), file.data()); + output_real(str.data()); + c=f+6; + wrote_include = true; + } + + } + + if (!wrote_include) { + f[5]=0; + output_real(c); + f[5]=';'; + c=f+5; + } + } + break; + case 4: /* manpage */ + f=idtest[j]; + /* check section */ + g=strchr(f,')'); + // The character before f must alphanumeric, the end of a HTML tag or the end of a + if (g!=NULL && f>c && (g-f)<12 && (isalnum(f[-1]) || f[-1]=='>' || ( f[-1] == ';' ) ) && + isdigit(f[1]) && f[1]!='0' && ((g-f)<=2 || isalpha(f[2]))) + { + ok = TRUE; + h = f+2; + while (h<g) + { + if (!isalnum(*h++)) + { + ok = FALSE; + break; + } + } + } + else + ok = false; + + h = f - 1; + if ( ok ) + { + // Skip + kdDebug(7107) << "BEFORE SECTION:" << *h << endl; + if ( ( h > c + 5 ) && ( ! memcmp( h-5, " ", 6 ) ) ) + { + h -= 6; + kdDebug(7107) << "Skip " << endl; + } + else if ( *h == ';' ) + { + // Not a non-breaking space, so probably not ok + ok = false; + } + } + + if (ok) + { + /* this might be a link */ + /* skip html makeup */ + while (h>c && *h=='>') { + while (h!=c && *h!='<') h--; + if (h!=c) h--; + } + if (isalnum(*h)) { + char t,sec, *e; + QString subsec; // ### TODO avoid using QString, as we do not know the encoding + QString fstr(f); // ### TODO avoid using QString, as we do not know the encoding + e=h+1; + sec=f[1]; + subsec=f[2]; + int index = fstr.find(')', 2); + if (index != -1) + subsec = fstr.mid(2, index - 2); + else // No closing ')' found, take first character as subsection. + subsec = fstr.mid(2, 1); + while (h>c && (isalnum(h[-1]) || h[-1]=='_' + || h[-1]==':' || h[-1]=='-' || h[-1]=='.')) + h--; + t=*h; + *h='\0'; + output_real(c); + *h=t; + t=*e; + *e='\0'; + QCString str; + if (subsec.isEmpty()) + str.sprintf("<A HREF=\"man:%s(%c)\">%s</A>", h, sec, h); + else + str.sprintf("<A HREF=\"man:%s(%c%s)\">%s</A>", h, sec, subsec.lower().latin1(), h); + output_real(str.data()); + *e=t; + c=e; + } + } + *f='\0'; + output_real(c); + *f='('; + idtest[4]=f-1; + c=f; + break; /* manpage */ + case 3: /* ftp */ + case 2: /* www */ + g=f=idtest[j]; + while (*g && (isalnum(*g) || *g=='_' || *g=='-' || *g=='+' || + *g=='.' || *g=='/')) g++; + if (g[-1]=='.') g--; + if (g-f>4) { + char t; + t=*f; *f='\0'; + output_real(c); + *f=t; t=*g;*g='\0'; + QCString str; + str.sprintf("<A HREF=\"%s://%s\">%s</A>", ((j==3)?"ftp":"http"), f, f); + output_real(str.data()); + *g=t; + c=g; + } else { + f[3]='\0'; + output_real(c); + c=f+3; + f[3]='.'; + } + break; + case 1: /* mailto */ + g=f=idtest[1]; + while (g>c && (isalnum(g[-1]) || g[-1]=='_' || g[-1]=='-' || + g[-1]=='+' || g[-1]=='.' || g[-1]=='%')) g--; + if (g-7>=c && g[-1]==':') + { + // We have perhaps an email address starting with mailto: + if (!qstrncmp("mailto:",g-7,7)) + g-=7; + } + h=f+1; + while (*h && (isalnum(*h) || *h=='_' || *h=='-' || *h=='+' || + *h=='.')) h++; + if (*h=='.') h--; + if (h-f>4 && f-g>1) { + char t; + t=*g; + *g='\0'; + output_real(c); + *g=t;t=*h;*h='\0'; + QCString str; + str.sprintf("<A HREF=\"mailto:%s\">%s</A>", g, g); + output_real(str.data()); + *h=t; + c=h; + } else { + *f='\0'; + output_real(c); + *f='@'; + idtest[1]=c; + c=f; + } + break; + case 0: /* url */ + g=f=idtest[0]; + while (g>c && isalpha(g[-1]) && islower(g[-1])) g--; + h=f+3; + while (*h && !isspace(*h) && *h!='<' && *h!='>' && *h!='"' && + *h!='&') h++; + if (f-g>2 && f-g<7 && h-f>3) { + char t; + t=*g; + *g='\0'; + output_real(c); + *g=t; t=*h; *h='\0'; + QCString str; + str.sprintf("<A HREF=\"%s\">%s</A>", g, g); + output_real(str.data()); + *h=t; + c=h; + } else { + f[1]='\0'; + output_real(c); + f[1]='/'; + c=f+1; + } + break; + default: + break; + } + nr=0; + if (idtest[0] && idtest[0]<=c) idtest[0]=strstr(c+1,"://"); + if (idtest[1] && idtest[1]<=c) idtest[1]=strchr(c+1,'@'); + if (idtest[2] && idtest[2]<c) idtest[2]=strstr(c,"www."); + if (idtest[3] && idtest[3]<c) idtest[3]=strstr(c,"ftp."); + if (idtest[4] && idtest[4]<=c) idtest[4]=strchr(c+1,'('); + if (idtest[5] && idtest[5]<=c) idtest[5]=strstr(c+1,".h>"); + for (i=0; i<numtests; i++) nr += (idtest[i]!=NULL); + } + output_real(c); +} + +static QCString current_font; +static int current_size=0; +static int fillout=1; + +static void out_html(const char *c) +{ + if (!c) return; + + // Added, probably due to the const? + char *c2 = qstrdup(c); + char *c3 = c2; + + static int obp=0; + + if (no_newline_output) { + int i=0; + no_newline_output=1; + while (c2[i]) { + if (!no_newline_output) c2[i-1]=c2[i]; + if (c2[i]=='\n') no_newline_output=0; + i++; + } + if (!no_newline_output) c2[i-1]=0; + } + if (scaninbuff) { + while (*c2) { + if (buffpos>=buffmax) { + char *h = new char[buffmax*2]; + +#ifdef SIMPLE_MAN2HTML + if (!h) + { + cerr << "Memory full, cannot output!" << endl; + exit(1); + } +#else +// modern compiler do not return a NULL for a new +#endif + memcpy(h, buffer, buffmax); + delete [] buffer; + buffer=h; + buffmax=buffmax*2; + } + buffer[buffpos++]=*c2++; + } + } else + if (output_possible) { + while (*c2) { + outbuffer[obp++]=*c2; + if (*c=='\n' || obp >= HUGE_STR_MAX) { + outbuffer[obp]='\0'; + add_links(outbuffer); + obp=0; + } + c2++; + } + } + delete [] c3; +} + +static QCString set_font( const QCString& name ) +{ + // Every font but R (Regular) creates <span> elements + QCString markup; + if ( current_font != "R" && !current_font.isEmpty() ) + markup += "</span>"; + const uint len = name.length(); + bool fontok = true; + if ( len == 1 ) + { + const char lead = name[0]; + switch (lead) + { + case 'P': // Palatino? + case 'R': break; // regular, do nothing + case 'I': markup += "<span style=\"font-style:italic\">"; break; + case 'B': markup += "<span style=\"font-weight:bold\">"; break; + case 'L': markup += "<span style=\"font-family:monospace\">"; break; // ### What's L? + default: fontok = false; + } + } + else if ( len == 2 ) + { + if ( name == "BI" ) + markup += "<span style=\"font-style:italic;font-weight:bold\">"; + // Courier + else if ( name == "CR" ) + markup += "<span style=\"font-family:monospace\">"; + else if ( name == "CW" ) // CW is used by pod2man(1) (alias PerlDoc) + markup += "<span style=\"font-family:monospace\">"; + else if ( name == "CI" ) + markup += "<span style=\"font-family:monospace;font-style:italic\">"; + else if ( name == "CB" ) + markup += "<span style=\"font-family:monospace;font-weight:bold\">"; + // Times + else if ( name == "TR" ) + markup += "<span style=\"font-family:serif\">"; + else if ( name == "TI" ) + markup += "<span style=\"font-family:serif;font-style:italic\">"; + else if ( name == "TB" ) + markup += "<span style=\"font-family:serif;font-weight:bold\">"; + // Helvetica + else if ( name == "HR" ) + markup += "<span style=\"font-family:sans-serif\">"; + else if ( name == "HI" ) + markup += "<span style=\"font-family:sans-serif;font-style:italic\">"; + else if ( name == "HB" ) + markup += "<span style=\"font-family:sans-serif;font-weight:bold\">"; + else + fontok = false; + } + else if ( len == 3 ) + { + if ( name == "CBI" ) + markup += "<span style=\"font-family:monospace;font-style:italic;font-weight:bold\">"; + else if ( name == "TBI" ) + markup += "<span style=\"font-family:serif;font-style:italic;font-weight:bold\">"; + else if ( name == "HBI" ) + markup += "<span style=\"font-family:sans-serif;font-style:italic;font-weight:bold\">"; + } + if (fontok) + current_font = name; + else + current_font = "R"; // Still nothing, then it is 'R' (Regular) + return markup; +} + +/// \deprecated +static QCString set_font( const char ch ) +#ifndef SIMPLE_MAN2HTML + KDE_DEPRECATED; + +static QCString set_font( const char ch ) +#endif +{ + const QCString name = &ch; + return set_font( name ); +} + +static QCString change_to_size(int nr) +{ + switch (nr) + { + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': nr=nr-'0'; break; + case '\0': break; + default: nr=current_size+nr; if (nr>9) nr=9; if (nr< -9) nr=-9; break; + } + if ( nr == current_size ) + return ""; + const QCString font ( current_font ); + QCString markup; + markup = set_font("R"); + if (current_size) + markup += "</FONT>"; + current_size=nr; + if (nr) + { + markup += "<FONT SIZE=\""; + if (nr>0) + markup += '+'; + else + { + markup += '-'; + nr=-nr; + } + markup += char( nr + '0' ); + markup += "\">"; + } + markup += set_font( font ); + return markup; +} + +/* static int asint=0; */ +static int intresult=0; + +#define SKIPEOL while (*c && *c++!='\n') + +static bool skip_escape=false; +static bool single_escape=false; + +static char *scan_escape_direct( char *c, QCString& cstr ); + +/** + * scan a named character + * param c position +*/ +static QCString scan_named_character( char*& c ) +{ + QCString name; + if ( *c == '(' ) + { + // \*(ab Name of two characters + if ( c[1] == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+2, cstr ); + // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used. + name = cstr; + } + else + { + name+=c[1]; + name+=c[2]; + c+=3; + } + } + else if ( *c == '[' ) + { + // \*[long_name] Long name + // Named character groff(7) + // We must find the ] to get a name + c++; + while ( *c && *c != ']' && *c != '\n' ) + { + if ( *c == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+1, cstr ); + const int result = cstr.find(']'); + if ( result == -1 ) + name += cstr; + else + { + // Note: we drop the characters after the ] + name += cstr.left( result ); + } + } + else + { + name+=*c; + c++; + } + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse character name: " << name << endl; + return ""; + } + c++; + } + else if ( *c =='C' || c[1]== '\'' ) + { + // \C'name' + c+=2; + while ( *c && *c != '\'' && *c != '\n' ) + { + if ( *c == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+1, cstr ); + const int result = cstr.find('\''); + if ( result == -1 ) + name += cstr; + else + { + // Note: we drop the characters after the ] + name += cstr.left( result ); + } + } + else + { + name+=*c; + c++; + } + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse (\\C mode) character name: " << name << endl; + return ""; + } + c++; + } + // Note: characters with a one character length name doe not exist, as they would collide with other escapes + + // Now we have the name, let us find it between the string names + QMap<QCString,StringDefinition>::iterator it=s_characterDefinitionMap.find(name); + if (it==s_characterDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find character with name: " << name << endl; + // No output, as an undefined string is empty by default + return ""; + } + else + { + kdDebug(7107) << "Character with name: \"" << name << "\" => " << (*it).m_output << endl; + return (*it).m_output; + } +} + +static QCString scan_named_string(char*& c) +{ + QCString name; + if ( *c == '(' ) + { + // \*(ab Name of two characters + if ( c[1] == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+2, cstr ); + kdDebug(7107) << "\\(" << cstr << endl; + // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used. + name = cstr; + } + else + { + name+=c[1]; + name+=c[2]; + c+=3; + } + } + else if ( *c == '[' ) + { + // \*[long_name] Long name + // Named character groff(7) + // We must find the ] to get a name + c++; + while ( *c && *c != ']' && *c != '\n' ) + { + if ( *c == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+1, cstr ); + const int result = cstr.find(']'); + if ( result == -1 ) + name += cstr; + else + { + // Note: we drop the characters after the ] + name += cstr.left( result ); + } + } + else + { + name+=*c; + c++; + } + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse string name: " << name << endl; + return ""; + } + c++; + } + else + { + // \*a Name of one character + name+=*c; + c++; + } + // Now we have the name, let us find it between the string names + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); + if (it==s_stringDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find string with name: " << name << endl; + // No output, as an undefined string is empty by default + return ""; + } + else + { + kdDebug(7107) << "String with name: \"" << name << "\" => " << (*it).m_output << endl; + return (*it).m_output; + } +} + +static QCString scan_dollar_parameter(char*& c) +{ + unsigned int argno = 0; // No dollar argument number yet! + if ( *c == '0' ) + { + //kdDebug(7107) << "$0" << endl; + c++; + return s_dollarZero; + } + else if ( *c >= '1' && *c <= '9' ) + { + //kdDebug(7107) << "$ direct" << endl; + argno = ( *c - '0' ); + c++; + } + else if ( *c == '(' ) + { + //kdDebug(7107) << "$(" << endl; + if ( c[1] && c[2] && c[1] >= '0' && c[1] <= '9' && c[2] >= '0' && c[2] <= '9' ) + { + argno = ( c[1] - '0' ) * 10 + ( c[2] - '0' ); + c += 3; + } + else + { + if ( !c[1] ) + c++; + else if ( !c[2] ) + c+=2; + else + c += 3; + return ""; + } + } + else if ( *c == '[' ) + { + //kdDebug(7107) << "$[" << endl; + argno = 0; + c++; + while ( *c && *c>='0' && *c<='9' && *c!=']' ) + { + argno *= 10; + argno += ( *c - '0' ); + c++; + } + if ( *c != ']' ) + { + return ""; + } + c++; + } + else if ( ( *c == '*' ) || ( *c == '@' ) ) + { + const bool quote = ( *c == '@' ); + QValueList<char*>::const_iterator it = s_argumentList.begin(); + QCString param; + bool space = false; + for ( ; it != s_argumentList.end(); ++it ) + { + if (space) + param += " "; + if (quote) + param += '\"'; // Not as HTML, as it could be used by macros ! + param += (*it); + if (quote) + param += '\"'; // Not as HTML, as it could be used by macros! + space = true; + } + c++; + return param; + } + else + { + kdDebug(7107) << "EXCEPTION: unknown parameter $" << *c << endl; + return ""; + } + //kdDebug(7107) << "ARG $" << argno << endl; + if ( !s_argumentList.isEmpty() && argno > 0 ) + { + //kdDebug(7107) << "ARG $" << argno << " OK!" << endl; + argno--; + if ( argno >= s_argumentList.size() ) + { + kdDebug(7107) << "EXCEPTION: cannot find parameter $" << (argno+1) << endl; + return ""; + } + + return s_argumentList[argno]; + } + return ""; +} + +/// return the value of read-only number registers +static int read_only_number_register( const QCString& name ) +{ + // Internal read-only variables + if ( name == ".$" ) + { + kdDebug(7107) << "\\n[.$] == " << s_argumentList.size() << endl; + return s_argumentList.size(); + } + else if ( name == ".g" ) + return 0; // We are not groff(1) + else if ( name == ".s" ) + return current_size; +#if 0 + // ### TODO: map the fonts to a number + else if ( name == ".f" ) + return current_font; +#endif + else if ( name == ".P" ) + return 0; // We are not printing + else if ( name == ".A" ) + return s_nroff; +#ifndef SIMPLE_MAN2HTML + // Special KDE KIO man: + else if ( name == ".KDE_VERSION_MAJOR" ) + return KDE_VERSION_MAJOR; + else if ( name == ".KDE_VERSION_MINOR" ) + return KDE_VERSION_MINOR; + else if ( name == ".KDE_VERSION_RELEASE" ) + return KDE_VERSION_RELEASE; + else if ( name == ".KDE_VERSION" ) + return KDE_VERSION; +#endif + // ### TODO: should .T be set to "html"? But we are not the HTML post-processor. :-( + + // ### TODO: groff defines much more read-only number registers +#ifndef SIMPLE_MAN2HTML + kdDebug(7107) << "EXCEPTION: unknown read-only number register: " << name << endl; +#endif + + return 0; // Undefined variable + +} + +/// get the value of a number register and auto-increment if asked +static int scan_number_register( char*& c) +{ + int sign = 0; // Sign for auto-increment (if any) + switch (*c) + { + case '+': sign = 1; c++; break; + case '-': sign = -1; c++; break; + default: break; + } + QCString name; + if ( *c == '[' ) + { + c++; + if ( *c == '+' ) + { + sign = 1; + c++; + } + else if ( *c == '-' ) + { + sign = -1; + c++; + } + while ( *c && *c != ']' && *c != '\n' ) + { + // ### TODO: a \*[string] could be inside and should be processed + name+=*c; + c++; + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse number register name: " << name << endl; + return 0; + } + c++; + } + else if ( *c == '(' ) + { + c++; + if ( *c == '+' ) + { + sign = 1; + c++; + } + else if ( *c == '-' ) + { + sign = -1; + c++; + } + name+=c[0]; + name+=c[1]; + c+=2; + } + else + { + name += *c; + c++; + } + if ( name[0] == '.' ) + { + return read_only_number_register( name ); + } + else + { + QMap< QCString, NumberDefinition >::iterator it = s_numberDefinitionMap.find( name ); + if ( it == s_numberDefinitionMap.end() ) + { + return 0; // Undefined variable + } + else + { + (*it).m_value += sign * (*it).m_increment; + return (*it).m_value; + } + } +} + +/// get and set font +static QCString scan_named_font( char*& c ) +{ + QCString name; + if ( *c == '(' ) + { + // \f(ab Name of two characters + if ( c[1] == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+2, cstr ); + kdDebug(7107) << "\\(" << cstr << endl; + // ### HACK: as we convert characters too early to HTML, we need to support more than 2 characters here and assume that all characters passed by the variable are to be used. + name = cstr; + } + else + { + name+=c[1]; + name+=c[2]; + c+=3; + } + } + else if ( *c == '[' ) + { + // \f[long_name] Long name + // We must find the ] to get a name + c++; + while ( *c && *c != ']' && *c != '\n' ) + { + if ( *c == escapesym ) + { + QCString cstr; + c = scan_escape_direct( c+1, cstr ); + const int result = cstr.find(']'); + if ( result == -1 ) + name += cstr; + else + { + // Note: we drop the characters after the ] + name += cstr.left( result ); + } + } + else + { + name+=*c; + c++; + } + } + if ( !*c || *c == '\n' ) + { + kdDebug(7107) << "Found linefeed! Could not parse font name: " << name << endl; + return ""; + } + c++; + } + else + { + // \fa Font name with one character or one digit + // ### HACK do *not* use: name = *c; or name would be empty + name += *c; + c++; + } + //kdDebug(7107) << "FONT NAME: " << name << endl; + // Now we have the name, let us find the font + bool ok = false; + const unsigned int number = name.toUInt( &ok ); + if ( ok ) + { + if ( number < 5 ) + { + const char* fonts[] = { "R", "I", "B", "BI", "CR" }; // Regular, Italic, Bold, Bold Italic, Courier regular + name = fonts[ number ]; + } + else + { + kdDebug(7107) << "EXCEPTION: font has too big number: " << name << " => " << number << endl; + name = "R"; // Let assume Regular + } + } + else if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: font has no name: " << name << endl; + name = "R"; // Let assume Regular + } + if ( !skip_escape ) + return set_font( name ); + else + return ""; +} + +static QCString scan_number_code( char*& c ) +{ + QCString number; + if ( *c != '\'' ) + return ""; + while ( *c && ( *c != '\n' ) && ( *c != '\'' ) ) + { + number += *c; + c++; + } + bool ok = false; + unsigned int result = number.toUInt( &ok ); + if ( ( result < ' ' ) || ( result > 65535 ) ) + return ""; + else if ( result == '\t' ) + { + curpos += 8; + curpos &= 0xfff8; + return "\t"; + } + number.setNum( result ); + number.prepend( "&#" ); + number.append( ";" ); + curpos ++; + return number; +} + +// ### TODO known missing escapes from groff(7): +// ### TODO \& \! \) \: \R + +static char *scan_escape_direct( char *c, QCString& cstr ) +{ + bool exoutputp; + bool exskipescape; + int i,j; + bool cplusplus = true; // Should the c++ be done at the end of the function + + cstr = ""; + intresult=0; + switch (*c) { + case 'e': cstr = "\\"; curpos++;break; // ### FIXME: it should be the current escape symbol + case '0': // ### TODO Where in Unicode? (space of digit width) + case '~': // non-breakable-space (resizeable!) + case ' ': + case '|': // half-non-breakable-space + case '^': // quarter-non-breakable-space + cstr = " "; curpos++; break; + case '"': SKIPEOL; c--; break; + // ### TODO \# like \" but does not ignore the end of line (groff(7)) + case '$': + { + c++; + cstr = scan_dollar_parameter( c ); + cplusplus = false; + break; + } + case 'z': + { + c++; + if (*c=='\\') + { + c=scan_escape_direct( c+1, cstr ); + c--; + } + else + cstr = QCString( c, 1 ); + break; + } + case 'k': c++; if (*c=='(') c+=2; // ### FIXME \k[REG] exists too + case '!': + case '%': + case 'a': + case 'd': + case 'r': + case 'u': + case '\n': + case '&': + cstr = ""; break; + case '(': + case '[': + case 'C': + { + // Do not go forward as scan_named_character needs the leading symbol + cstr = scan_named_character( c ); + cplusplus = false; + break; + } + case '*': + { + c++; + cstr = scan_named_string( c ); + cplusplus = false; + break; + } + case 'f': + { + c++; + cstr = scan_named_font( c ); + cplusplus = false; + break; + } + case 's': // ### FIXME: many forms are missing + c++; + j=0;i=0; + if (*c=='-') {j= -1; c++;} else if (*c=='+') {j=1; c++;} + if (*c=='0') c++; else if (*c=='\\') { + c++; + c=scan_escape_direct( c, cstr ); + i=intresult; if (!j) j=1; + } else + while (isdigit(*c) && (!i || (!j && i<4))) i=i*10+(*c++)-'0'; + if (!j) { j=1; if (i) i=i-10; } + if (!skip_escape) cstr=change_to_size(i*j); + c--; + break; + case 'n': + { + c++; + intresult = scan_number_register( c ); + cplusplus = false; + break; + } + case 'w': + c++; + i=*c; + c++; + exoutputp=output_possible; + exskipescape=skip_escape; + output_possible=false; + skip_escape=true; + j=0; + while (*c!=i) + { + j++; + if ( *c == escapesym ) + c = scan_escape_direct( c+1, cstr); + else + c++; + } + output_possible=exoutputp; + skip_escape=exskipescape; + intresult=j; + break; + case 'l': cstr = "<HR>"; curpos=0; + case 'b': + case 'v': + case 'x': + case 'o': + case 'L': + case 'h': + c++; + i=*c; + c++; + exoutputp=output_possible; + exskipescape=skip_escape; + output_possible=0; + skip_escape=true; + while (*c != i) + if (*c==escapesym) c=scan_escape_direct( c+1, cstr ); + else c++; + output_possible=exoutputp; + skip_escape=exskipescape; + break; + case 'c': no_newline_output=1; break; + case '{': newline_for_fun++; break; // Start conditional block + case '}': if (newline_for_fun) newline_for_fun--; break; // End conditional block + case 'p': cstr = "<BR>\n";curpos=0; break; + case 't': cstr = "\t";curpos=(curpos+8)&0xfff8; break; + case '<': cstr = "<";curpos++; break; + case '>': cstr = ">";curpos++; break; + case '\\': + { + if (single_escape) + c--; + else + cstr="\\"; + break; + } + case 'N': + { + c++; + cstr = scan_number_code( c ); + cplusplus = false; + break; + } +#if 0 + { + if (*++c) c++; // c += 2 + if (sscanf(c, "%d", &i) != 1) // (### FIXME ugly!) + break; + QCString temp; + temp.sprintf( "%d", i ); // Skip over number (### FIXME ugly!) + c += temp.length(); + switch(i) { + case 8: cstr = "\t"; curpos=(curpos+8)&0xfff8; break; + case 34: cstr = """; curpos++; break; + default: cstr = char( i ); curpos++; break; + } + break; + } +#endif + case '\'': cstr = "´";curpos++; break; // groff(7) ### TODO verify + case '`': cstr = "`";curpos++; break; // groff(7) + case '-': cstr = "-";curpos++; break; // groff(7) + case '.': cstr = ".";curpos++; break; // groff(7) + default: cstr = *c; curpos++; break; + } + if (cplusplus) + c++; + return c; +} + +static char *scan_escape(char *c) +{ + QCString cstr; + char* result = scan_escape_direct( c, cstr ); + if ( !skip_escape ) + out_html(cstr); + return result; +} + +class TABLEROW; + +class TABLEITEM { +public: + TABLEITEM(TABLEROW *row); + ~TABLEITEM() { + delete [] contents; + } + void setContents(const char *_contents) { + delete [] contents; + contents = qstrdup(_contents); + } + const char *getContents() const { return contents; } + + void init() { + delete [] contents; + contents = 0; + size = 0; + align = 0; + valign = 0; + colspan = 1; + rowspan = 1; + font = 0; + vleft = 0; + vright = 0; + space = 0; + width = 0; + } + + void copyLayout(const TABLEITEM *orig) { + size = orig->size; + align = orig->align; + valign = orig->valign; + colspan = orig->colspan; + rowspan = orig->rowspan; + font = orig->font; + vleft = orig->vleft; + vright = orig->vright; + space = orig->space; + width = orig->width; + } + +public: + int size,align,valign,colspan,rowspan,font,vleft,vright,space,width; + +private: + char *contents; + TABLEROW *_parent; +}; + +class TABLEROW { + char *test; +public: + TABLEROW() { + test = new char; + items.setAutoDelete(true); + prev = 0; next = 0; + } + ~TABLEROW() { + delete test; + + } + int length() const { return items.count(); } + bool has(int index) { + return (index >= 0) && (index < (int)items.count()); + } + TABLEITEM &at(int index) { + return *items.at(index); + } + + TABLEROW *copyLayout() const; + + void addItem(TABLEITEM *item) { + items.append(item); + } + TABLEROW *prev, *next; + +private: + QPtrList<TABLEITEM> items; +}; + +TABLEITEM::TABLEITEM(TABLEROW *row) : contents(0), _parent(row) { + init(); + _parent->addItem(this); +} + +TABLEROW *TABLEROW::copyLayout() const { + TABLEROW *newrow = new TABLEROW(); + + QPtrListIterator<TABLEITEM> it(items); + for ( ; it.current(); ++it) { + TABLEITEM *newitem = new TABLEITEM(newrow); + newitem->copyLayout(it.current()); + } + return newrow; +} + +static const char *tableopt[]= { "center", "expand", "box", "allbox", + "doublebox", "tab", "linesize", + "delim", NULL }; +static int tableoptl[] = { 6,6,3,6,9,3,8,5,0}; + + +static void clear_table(TABLEROW *table) +{ + TABLEROW *tr1,*tr2; + + tr1=table; + while (tr1->prev) tr1=tr1->prev; + while (tr1) { + tr2=tr1; + tr1=tr1->next; + delete tr2; + } +} + +static char *scan_expression(char *c, int *result); + +static char *scan_format(char *c, TABLEROW **result, int *maxcol) +{ + TABLEROW *layout, *currow; + TABLEITEM *curfield; + int i,j; + if (*result) { + clear_table(*result); + } + layout= currow=new TABLEROW(); + curfield=new TABLEITEM(currow); + while (*c && *c!='.') { + switch (*c) { + case 'C': case 'c': case 'N': case 'n': + case 'R': case 'r': case 'A': case 'a': + case 'L': case 'l': case 'S': case 's': + case '^': case '_': + if (curfield->align) + curfield=new TABLEITEM(currow); + curfield->align=toupper(*c); + c++; + break; + case 'i': case 'I': case 'B': case 'b': + curfield->font = toupper(*c); + c++; + break; + case 'f': case 'F': + c++; + curfield->font = toupper(*c); + c++; + if (!isspace(*c) && *c!='.') c++; + break; + case 't': case 'T': curfield->valign='t'; c++; break; + case 'p': case 'P': + c++; + i=j=0; + if (*c=='+') { j=1; c++; } + if (*c=='-') { j=-1; c++; } + while (isdigit(*c)) i=i*10+(*c++)-'0'; + if (j) curfield->size= i*j; else curfield->size=j-10; + break; + case 'v': case 'V': + case 'w': case 'W': + c=scan_expression(c+2,&curfield->width); + break; + case '|': + if (curfield->align) curfield->vleft++; + else curfield->vright++; + c++; + break; + case 'e': case 'E': + c++; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + i=0; + while (isdigit(*c)) i=i*10+(*c++)-'0'; + curfield->space=i; + break; + case ',': case '\n': + currow->next=new TABLEROW(); + currow->next->prev=currow; + currow=currow->next; + currow->next=NULL; + curfield=new TABLEITEM(currow); + c++; + break; + default: + c++; + break; + } + } + if (*c=='.') while (*c++!='\n'); + *maxcol=0; + currow=layout; + while (currow) { + i=currow->length(); + if (i>*maxcol) *maxcol=i; + currow=currow->next; + } + *result=layout; + return c; +} + +static TABLEROW *next_row(TABLEROW *tr) +{ + if (tr->next) { + tr=tr->next; + if (!tr->next) + return next_row(tr); + return tr; + } else { + tr->next = tr->copyLayout(); + tr->next->prev = tr; + return tr->next; + } +} + +static char itemreset[20]="\\fR\\s0"; + +#define FORWARDCUR do { curfield++; } while (currow->has(curfield) && currow->at(curfield).align=='S'); + +static char *scan_table(char *c) +{ + char *h; + char *g; + int center=0, expand=0, box=0, border=0, linesize=1; + int i,j,maxcol=0, finished=0; + QCString oldfont; + int oldsize,oldfillout; + char itemsep='\t'; + TABLEROW *layout=NULL, *currow; + int curfield = -1; + while (*c++!='\n'); + h=c; + if (*h=='.') return c-1; + oldfont=current_font; + oldsize=current_size; + oldfillout=fillout; + out_html(set_font("R")); + out_html(change_to_size(0)); + if (!fillout) { + fillout=1; + out_html("</PRE>"); + } + while (*h && *h!='\n') h++; + if (h[-1]==';') { + /* scan table options */ + while (c<h) { + while (isspace(*c)) c++; + for (i=0; tableopt[i] && qstrncmp(tableopt[i],c,tableoptl[i]);i++); + c=c+tableoptl[i]; + switch (i) { + case 0: center=1; break; + case 1: expand=1; break; + case 2: box=1; break; + case 3: border=1; break; + case 4: box=2; break; + case 5: while (*c++!='('); itemsep=*c++; break; + case 6: while (*c++!='('); linesize=0; + while (isdigit(*c)) linesize=linesize*10+(*c++)-'0'; + break; + case 7: while (*c!=')') c++; + default: break; + } + c++; + } + c=h+1; + } + /* scan layout */ + c=scan_format(c,&layout, &maxcol); +// currow=layout; + currow=next_row(layout); + curfield=0; + i=0; + while (!finished && *c) { + /* search item */ + h=c; + if ((*c=='_' || *c=='=') && (c[1]==itemsep || c[1]=='\n')) { + if (c[-1]=='\n' && c[1]=='\n') { + if (currow->prev) { + currow->prev->next=new TABLEROW(); + currow->prev->next->next=currow; + currow->prev->next->prev=currow->prev; + currow->prev=currow->prev->next; + } else { + currow->prev=layout=new TABLEROW(); + currow->prev->prev=NULL; + currow->prev->next=currow; + } + TABLEITEM *newitem = new TABLEITEM(currow->prev); + newitem->align=*c; + newitem->colspan=maxcol; + curfield=0; + c=c+2; + } else { + if (currow->has(curfield)) { + currow->at(curfield).align=*c; + FORWARDCUR; + } + if (c[1]=='\n') { + currow=next_row(currow); + curfield=0; + } + c=c+2; + } + } else if (*c=='T' && c[1]=='{') { + h=c+2; + c=strstr(h,"\nT}"); + c++; + *c='\0'; + g=NULL; + scan_troff(h,0,&g); + scan_troff(itemreset, 0, &g); + *c='T'; + c+=3; + if (currow->has(curfield)) { + currow->at(curfield).setContents(g); + FORWARDCUR; + } + delete [] g; + + if (c[-1]=='\n') { + currow=next_row(currow); + curfield=0; + } + } else if (*c=='.' && c[1]=='T' && c[2]=='&' && c[-1]=='\n') { + TABLEROW *hr; + while (*c++!='\n'); + hr=currow; + currow=currow->prev; + hr->prev=NULL; + c=scan_format(c,&hr, &i); + hr->prev=currow; + currow->next=hr; + currow=hr; + next_row(currow); + curfield=0; + } else if (*c=='.' && c[1]=='T' && c[2]=='E' && c[-1]=='\n') { + finished=1; + while (*c++!='\n'); + if (currow->prev) + currow->prev->next=NULL; + currow->prev=NULL; + clear_table(currow); + currow = 0; + } else if (*c=='.' && c[-1]=='\n' && !isdigit(c[1])) { + /* skip troff request inside table (usually only .sp ) */ + while (*c++!='\n'); + } else { + h=c; + while (*c && (*c!=itemsep || c[-1]=='\\') && + (*c!='\n' || c[-1]=='\\')) c++; + i=0; + if (*c==itemsep) {i=1; *c='\n'; } + if (h[0]=='\\' && h[2]=='\n' && + (h[1]=='_' || h[1]=='^')) { + if (currow->has(curfield)) { + currow->at(curfield).align=h[1]; + FORWARDCUR; + } + h=h+3; + } else { + g=NULL; + h=scan_troff(h,1,&g); + scan_troff(itemreset,0, &g); + if (currow->has(curfield)) { + currow->at(curfield).setContents(g); + FORWARDCUR; + } + delete [] g; + } + if (i) *c=itemsep; + c=h; + if (c[-1]=='\n') { + currow=next_row(currow); + curfield=0; + } + } + } + /* calculate colspan and rowspan */ + currow=layout; + while (currow->next) currow=currow->next; + while (currow) { + int ti = 0, ti1 = 0, ti2 = -1; + TABLEROW *prev = currow->prev; + if (!prev) + break; + + while (prev->has(ti1)) { + if (currow->has(ti)) + switch (currow->at(ti).align) { + case 'S': + if (currow->has(ti2)) { + currow->at(ti2).colspan++; + if (currow->at(ti2).rowspan<prev->at(ti1).rowspan) + currow->at(ti2).rowspan=prev->at(ti1).rowspan; + } + break; + case '^': + if (prev->has(ti1)) prev->at(ti1).rowspan++; + default: + if (ti2 < 0) ti2=ti; + else { + do { + ti2++; + } while (currow->has(ti2) && currow->at(ti2).align=='S'); + } + break; + } + ti++; + if (ti1 >= 0) ti1++; + } + currow=currow->prev; + } + /* produce html output */ + if (center) out_html("<CENTER>"); + if (box==2) out_html("<TABLE BORDER><TR><TD>"); + out_html("<TABLE"); + if (box || border) { + out_html(" BORDER"); + if (!border) out_html("><TR><TD><TABLE"); + if (expand) out_html(" WIDTH=\"100%\""); + } + out_html(">\n"); + currow=layout; + while (currow) { + j=0; + out_html("<TR VALIGN=top>"); + curfield=0; + while (currow->has(curfield)) { + if (currow->at(curfield).align!='S' && currow->at(curfield).align!='^') { + out_html("<TD"); + switch (currow->at(curfield).align) { + case 'N': + currow->at(curfield).space+=4; + case 'R': + out_html(" ALIGN=right"); + break; + case 'C': + out_html(" ALIGN=center"); + default: + break; + } + if (!currow->at(curfield).valign && currow->at(curfield).rowspan>1) + out_html(" VALIGN=center"); + if (currow->at(curfield).colspan>1) { + char buf[5]; + out_html(" COLSPAN="); + sprintf(buf, "%i", currow->at(curfield).colspan); + out_html(buf); + } + if (currow->at(curfield).rowspan>1) { + char buf[5]; + out_html(" ROWSPAN="); + sprintf(buf, "%i", currow->at(curfield).rowspan); + out_html(buf); + } + j=j+currow->at(curfield).colspan; + out_html(">"); + if (currow->at(curfield).size) out_html(change_to_size(currow->at(curfield).size)); + if (currow->at(curfield).font) out_html(set_font(currow->at(curfield).font)); + switch (currow->at(curfield).align) { + case '=': out_html("<HR><HR>"); break; + case '_': out_html("<HR>"); break; + default: + out_html(currow->at(curfield).getContents()); + break; + } + if (currow->at(curfield).space) + for (i=0; i<currow->at(curfield).space;i++) out_html(" "); + if (currow->at(curfield).font) out_html(set_font("R")); + if (currow->at(curfield).size) out_html(change_to_size(0)); + if (j>=maxcol && currow->at(curfield).align>'@' && currow->at(curfield).align!='_') + out_html("<BR>"); + out_html("</TD>"); + } + curfield++; + } + out_html("</TR>\n"); + currow=currow->next; + } + + clear_table(layout); + + if (box && !border) out_html("</TABLE>"); + out_html("</TABLE>"); + if (box==2) out_html("</TABLE>"); + if (center) out_html("</CENTER>\n"); + else out_html("\n"); + if (!oldfillout) out_html("<PRE>"); + fillout=oldfillout; + out_html(change_to_size(oldsize)); + out_html(set_font(oldfont)); + return c; +} + +static char *scan_expression( char *c, int *result, const unsigned int numLoop ) +{ + int value=0,value2,sign=1,opex=0; + char oper='c'; + + if (*c=='!') { + c=scan_expression(c+1, &value); + value= (!value); + } else if (*c=='n') { + c++; + value=s_nroff; + } else if (*c=='t') { + c++; + value=1-s_nroff; + } else if (*c=='\'' || *c=='"' || *c<' ' || (*c=='\\' && c[1]=='(')) { + /* ?string1?string2? + ** test if string1 equals string2. + */ + char *st1=NULL, *st2=NULL, *h; + char *tcmp=NULL; + char sep; + sep=*c; + if (sep=='\\') { + tcmp=c; + c=c+3; + } + c++; + h=c; + while (*c!= sep && (!tcmp || qstrncmp(c,tcmp,4))) c++; + *c='\n'; + scan_troff(h, 1, &st1); + *c=sep; + if (tcmp) c=c+3; + c++; + h=c; + while (*c!=sep && (!tcmp || qstrncmp(c,tcmp,4))) c++; + *c='\n'; + scan_troff(h,1,&st2); + *c=sep; + if (!st1 && !st2) value=1; + else if (!st1 || !st2) value=0; + else value=(!qstrcmp(st1, st2)); + delete [] st1; + delete [] st2; + if (tcmp) c=c+3; + c++; + } else { + while (*c && ( !isspace(*c) || ( numLoop > 0 ) ) && *c!=')' && opex >= 0) { + opex=0; + switch (*c) { + case '(': + c = scan_expression( c + 1, &value2, numLoop + 1 ); + value2=sign*value2; + opex=1; + break; + case '.': + case '0': case '1': + case '2': case '3': + case '4': case '5': + case '6': case '7': + case '8': case '9': { + int num=0,denum=1; + value2=0; + while (isdigit(*c)) value2=value2*10+((*c++)-'0'); + if (*c=='.' && isdigit(c[1])) { + c++; + while (isdigit(*c)) { + num=num*10+((*c++)-'0'); + denum=denum*10; + } + } + if (isalpha(*c)) { + /* scale indicator */ + switch (*c) { + case 'i': /* inch -> 10pt */ + value2=value2*10+(num*10+denum/2)/denum; + num=0; + break; + default: + break; + } + c++; + } + value2=value2+(num+denum/2)/denum; + value2=sign*value2; + opex=1; + if (*c=='.') + opex = -1; + + } + break; + case '\\': + c=scan_escape(c+1); + value2=intresult*sign; + if (isalpha(*c)) c++; /* scale indicator */ + opex=1; + break; + case '-': + if (oper) { sign=-1; c++; break; } + case '>': + case '<': + case '+': + case '/': + case '*': + case '%': + case '&': + case '=': + case ':': + if (c[1]=='=') oper=(*c++) +16; else oper=*c; + c++; + break; + default: c++; break; + } + if (opex > 0) { + sign=1; + switch (oper) { + case 'c': value=value2; break; + case '-': value=value-value2; break; + case '+': value=value+value2; break; + case '*': value=value*value2; break; + case '/': if (value2) value=value/value2; break; + case '%': if (value2) value=value%value2; break; + case '<': value=(value<value2); break; + case '>': value=(value>value2); break; + case '>'+16: value=(value>=value2); break; + case '<'+16: value=(value<=value2); break; + case '=': case '='+16: value=(value==value2); break; + case '&': value = (value && value2); break; + case ':': value = (value || value2); break; + default: + { + kdDebug(7107) << "Unknown operator " << char(oper) << endl; + } + } + oper=0; + } + } + if (*c==')') c++; + } + *result=value; + return c; +} + +static char *scan_expression(char *c, int *result) +{ + return scan_expression( c, result, 0 ); +} + +static void trans_char(char *c, char s, char t) +{ + char *sl=c; + int slash=0; + while (*sl!='\n' || slash) { + if (!slash) { + if (*sl==escapesym) + slash=1; + else if (*sl==s) + *sl=t; + } else slash=0; + sl++; + } +} + +// 2004-10-19, patched by Waldo Bastian <bastian@kde.org>: +// Fix handling of lines like: +// .TH FIND 1L \" -*- nroff -*- +// Where \" indicates the start of comment. +// +// The problem is the \" handling in fill_words(), the return value +// indicates the end of the word as well as the end of the line, which makes it +// basically impossible to express that the end of the last word is not the end of +// the line. +// +// I have corrected that by adding an extra parameter 'next_line' that returns a +// pointer to the next line, while the function itself returns a pointer to the end +// of the last word. +static char *fill_words(char *c, char *words[], int *n, bool newline, char **next_line) +{ + char *sl=c; + int slash=0; + int skipspace=0; + *n=0; + words[*n]=sl; + while (*sl && (*sl!='\n' || slash)) { + if (!slash) { + if (*sl=='"') { + if (skipspace && (*(sl+1)=='"')) + *sl++ = '\a'; + else { + *sl='\a'; + skipspace=!skipspace; + } + } else if (*sl==escapesym) { + slash=1; + if (sl[1]=='\n') + *sl='\a'; + } else if ((*sl==' ' || *sl=='\t') && !skipspace) { + if (newline) *sl='\n'; + if (words[*n]!=sl) (*n)++; + words[*n]=sl+1; + } + } else { + if (*sl=='"') { + sl--; + if (newline) *sl='\n'; + if (words[*n]!=sl) (*n)++; + if (next_line) + { + char *eow = sl; + sl++; + while (*sl && *sl !='\n') sl++; + *next_line = sl; + return eow; + } + return sl; + } + slash=0; + } + sl++; + } + if (sl!=words[*n]) (*n)++; + if (next_line) *next_line = sl+1; + return sl; +} + +static const char *abbrev_list[] = { + "GSBG", "Getting Started ", + "SUBG", "Customizing SunOS", + "SHBG", "Basic Troubleshooting", + "SVBG", "SunView User's Guide", + "MMBG", "Mail and Messages", + "DMBG", "Doing More with SunOS", + "UNBG", "Using the Network", + "GDBG", "Games, Demos & Other Pursuits", + "CHANGE", "SunOS 4.1 Release Manual", + "INSTALL", "Installing SunOS 4.1", + "ADMIN", "System and Network Administration", + "SECUR", "Security Features Guide", + "PROM", "PROM User's Manual", + "DIAG", "Sun System Diagnostics", + "SUNDIAG", "Sundiag User's Guide", + "MANPAGES", "SunOS Reference Manual", + "REFMAN", "SunOS Reference Manual", + "SSI", "Sun System Introduction", + "SSO", "System Services Overview", + "TEXT", "Editing Text Files", + "DOCS", "Formatting Documents", + "TROFF", "Using <B>nroff</B> and <B>troff</B>", + "INDEX", "Global Index", + "CPG", "C Programmer's Guide", + "CREF", "C Reference Manual", + "ASSY", "Assembly Language Reference", + "PUL", "Programming Utilities and Libraries", + "DEBUG", "Debugging Tools", + "NETP", "Network Programming", + "DRIVER", "Writing Device Drivers", + "STREAMS", "STREAMS Programming", + "SBDK", "SBus Developer's Kit", + "WDDS", "Writing Device Drivers for the SBus", + "FPOINT", "Floating-Point Programmer's Guide", + "SVPG", "SunView 1 Programmer's Guide", + "SVSPG", "SunView 1 System Programmer's Guide", + "PIXRCT", "Pixrect Reference Manual", + "CGI", "SunCGI Reference Manual", + "CORE", "SunCore Reference Manual", + "4ASSY", "Sun-4 Assembly Language Reference", + "SARCH", "<FONT SIZE=\"-1\">SPARC</FONT> Architecture Manual", + "KR", "The C Programming Language", + NULL, NULL }; + +static const char *lookup_abbrev(char *c) +{ + int i=0; + + if (!c) return ""; + while (abbrev_list[i] && qstrcmp(c,abbrev_list[i])) i=i+2; + if (abbrev_list[i]) return abbrev_list[i+1]; + else return c; +} + +static const char *section_list[] = { +#ifdef Q_OS_SOLARIS + // for Solaris + "1", "User Commands", + "1B", "SunOS/BSD Compatibility Package Commands", + "1b", "SunOS/BSD Compatibility Package Commands", + "1C", "Communication Commands ", + "1c", "Communication Commands", + "1F", "FMLI Commands ", + "1f", "FMLI Commands", + "1G", "Graphics and CAD Commands ", + "1g", "Graphics and CAD Commands ", + "1M", "Maintenance Commands", + "1m", "Maintenance Commands", + "1S", "SunOS Specific Commands", + "1s", "SunOS Specific Commands", + "2", "System Calls", + "3", "C Library Functions", + "3B", "SunOS/BSD Compatibility Library Functions", + "3b", "SunOS/BSD Compatibility Library Functions", + "3C", "C Library Functions", + "3c", "C Library Functions", + "3E", "C Library Functions", + "3e", "C Library Functions", + "3F", "Fortran Library Routines", + "3f", "Fortran Library Routines", + "3G", "C Library Functions", + "3g", "C Library Functions", + "3I", "Wide Character Functions", + "3i", "Wide Character Functions", + "3K", "Kernel VM Library Functions", + "3k", "Kernel VM Library Functions", + "3L", "Lightweight Processes Library", + "3l", "Lightweight Processes Library", + "3M", "Mathematical Library", + "3m", "Mathematical Library", + "3N", "Network Functions", + "3n", "Network Functions", + "3R", "Realtime Library", + "3r", "Realtime Library", + "3S", "Standard I/O Functions", + "3s", "Standard I/O Functions", + "3T", "Threads Library", + "3t", "Threads Library", + "3W", "C Library Functions", + "3w", "C Library Functions", + "3X", "Miscellaneous Library Functions", + "3x", "Miscellaneous Library Functions", + "4", "File Formats", + "4B", "SunOS/BSD Compatibility Package File Formats", + "4b", "SunOS/BSD Compatibility Package File Formats", + "5", "Headers, Tables, and Macros", + "6", "Games and Demos", + "7", "Special Files", + "7B", "SunOS/BSD Compatibility Special Files", + "7b", "SunOS/BSD Compatibility Special Files", + "8", "Maintenance Procedures", + "8C", "Maintenance Procedures", + "8c", "Maintenance Procedures", + "8S", "Maintenance Procedures", + "8s", "Maintenance Procedures", + "9", "DDI and DKI", + "9E", "DDI and DKI Driver Entry Points", + "9e", "DDI and DKI Driver Entry Points", + "9F", "DDI and DKI Kernel Functions", + "9f", "DDI and DKI Kernel Functions", + "9S", "DDI and DKI Data Structures", + "9s", "DDI and DKI Data Structures", + "L", "Local Commands", +#elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + "1", "General Commands", + "2", "System Calls", + "3", "Library Functions", + "4", "Kernel Interfaces", + "5", "File Formats", + "6", "Games", + "7", "Miscellaneous Information", + "8", "System Manager's Manuals", + "9", "Kernel Developer's Manuals", +#else + // Other OS + "1", "User Commands ", + "1C", "User Commands", + "1G", "User Commands", + "1S", "User Commands", + "1V", "User Commands ", + "2", "System Calls", + "2V", "System Calls", + "3", "C Library Functions", + "3C", "Compatibility Functions", + "3F", "Fortran Library Routines", + "3K", "Kernel VM Library Functions", + "3L", "Lightweight Processes Library", + "3M", "Mathematical Library", + "3N", "Network Functions", + "3R", "RPC Services Library", + "3S", "Standard I/O Functions", + "3V", "C Library Functions", + "3X", "Miscellaneous Library Functions", + "4", "Devices and Network Interfaces", + "4F", "Protocol Families", + "4I", "Devices and Network Interfaces", + "4M", "Devices and Network Interfaces", + "4N", "Devices and Network Interfaces", + "4P", "Protocols", + "4S", "Devices and Network Interfaces", + "4V", "Devices and Network Interfaces", + "5", "File Formats", + "5V", "File Formats", + "6", "Games and Demos", + "7", "Environments, Tables, and Troff Macros", + "7V", "Environments, Tables, and Troff Macros", + "8", "Maintenance Commands", + "8C", "Maintenance Commands", + "8S", "Maintenance Commands", + "8V", "Maintenance Commands", + "L", "Local Commands", +#endif + // The defaults + NULL, "Misc. Reference Manual Pages", + NULL, NULL +}; + +static const char *section_name(char *c) +{ + int i=0; + + if (!c) return ""; + while (section_list[i] && qstrcmp(c,section_list[i])) i=i+2; + if (section_list[i+1]) return section_list[i+1]; + else return c; +} + +static char *skip_till_newline(char *c) +{ + int lvl=0; + + while (*c && (*c!='\n' || lvl>0)) { + if (*c=='\\') { + c++; + if (*c=='}') lvl--; else if (*c=='{') lvl++; + } + c++; + } + if (*c) c++; + if (lvl<0 && newline_for_fun) { + newline_for_fun = newline_for_fun+lvl; + if (newline_for_fun<0) newline_for_fun=0; + } + return c; +} + +static bool s_whileloop = false; + +/// Processing the .while request +static void request_while( char*& c, int j, bool mdoc ) +{ + // ### TODO: .break and .continue + kdDebug(7107) << "Entering .while" << endl; + c += j; + char* newline = skip_till_newline( c ); + const char oldchar = *newline; + *newline = 0; + // We store the full .while stuff into a QCString as if it would be a macro + const QCString macro = c ; + kdDebug(7107) << "'Macro' of .while" << endl << macro << endl; + // Prepare for continuing after .while loop end + *newline = oldchar; + c = newline; + // Process -while loop + const bool oldwhileloop = s_whileloop; + s_whileloop = true; + int result = true; // It must be an int due to the call to scan_expression + while ( result ) + { + // Unlike for a normal macro, we have the condition at start, so we do not need to prepend extra bytes + char* liveloop = qstrdup( macro.data() ); + kdDebug(7107) << "Scanning .while condition" << endl; + kdDebug(7101) << "Loop macro " << liveloop << endl; + char* end_expression = scan_expression( liveloop, &result ); + kdDebug(7101) << "After " << end_expression << endl; + if ( result ) + { + kdDebug(7107) << "New .while iteration" << endl; + // The condition is true, so call the .while's content + char* help = end_expression + 1; + while ( *help && ( *help == ' ' || *help == '\t' ) ) + ++help; + if ( ! *help ) + { + // We have a problem, so stop .while + result = false; + break; + } + if ( mdoc ) + scan_troff_mandoc( help, false, 0 ); + else + scan_troff( help, false, 0 ); + } + delete[] liveloop; + } + + // + s_whileloop = oldwhileloop; + kdDebug(7107) << "Ending .while" << endl; +} + +const int max_wordlist = 100; + +/// Processing mixed fonts reqiests like .BI +static void request_mixed_fonts( char*& c, int j, const char* font1, const char* font2, const bool mode, const bool inFMode ) +{ + c += j; + if (*c=='\n') c++; + int words; + char *wordlist[max_wordlist]; + fill_words(c, wordlist, &words, true, &c); + for (int i=0; i<words; i++) + { + if ((mode) || (inFMode)) + { + out_html(" "); + curpos++; + } + wordlist[i][-1]=' '; + out_html( set_font( (i&1) ? font2 : font1 ) ); + scan_troff(wordlist[i],1,NULL); + } + out_html(set_font("R")); + if (mode) + { + out_html(" ]"); + curpos++; + } + out_html(NEWLINE); + if (!fillout) + curpos=0; + else + curpos++; +} + +// Some known missing requests from man(7): +// - see "safe subset": .tr + +// Some known missing requests from mdoc(7): +// - start or end of quotings + +// Some of the requests are from mdoc. +// On Linux see the man pages mdoc(7), mdoc.samples(7) and groff_mdoc(7) +// See also the online man pages of FreeBSD: mdoc(7) + +#define REQ_UNKNOWN -1 +#define REQ_ab 0 +#define REQ_di 1 +#define REQ_ds 2 +#define REQ_as 3 +#define REQ_br 4 +#define REQ_c2 5 +#define REQ_cc 6 +#define REQ_ce 7 +#define REQ_ec 8 +#define REQ_eo 9 +#define REQ_ex 10 +#define REQ_fc 11 +#define REQ_fi 12 +#define REQ_ft 13 // groff(7) "FonT" +#define REQ_el 14 +#define REQ_ie 15 +#define REQ_if 16 +#define REQ_ig 17 +#define REQ_nf 18 +#define REQ_ps 19 +#define REQ_sp 20 +#define REQ_so 21 +#define REQ_ta 22 +#define REQ_ti 23 +#define REQ_tm 24 +#define REQ_B 25 +#define REQ_I 26 +#define REQ_Fd 27 +#define REQ_Fn 28 +#define REQ_Fo 29 +#define REQ_Fc 30 +#define REQ_OP 31 +#define REQ_Ft 32 +#define REQ_Fa 33 +#define REQ_BR 34 +#define REQ_BI 35 +#define REQ_IB 36 +#define REQ_IR 37 +#define REQ_RB 38 +#define REQ_RI 39 +#define REQ_DT 40 +#define REQ_IP 41 // man(7) "Indent Paragraph" +#define REQ_TP 42 +#define REQ_IX 43 +#define REQ_P 44 +#define REQ_LP 45 +#define REQ_PP 46 +#define REQ_HP 47 +#define REQ_PD 48 +#define REQ_Rs 49 +#define REQ_RS 50 +#define REQ_Re 51 +#define REQ_RE 52 +#define REQ_SB 53 +#define REQ_SM 54 +#define REQ_Ss 55 +#define REQ_SS 56 +#define REQ_Sh 57 +#define REQ_SH 58 // man(7) "Sub Header" +#define REQ_Sx 59 +#define REQ_TS 60 +#define REQ_Dt 61 +#define REQ_TH 62 +#define REQ_TX 63 +#define REQ_rm 64 +#define REQ_rn 65 +#define REQ_nx 66 +#define REQ_in 67 +#define REQ_nr 68 // groff(7) "Number Register" +#define REQ_am 69 +#define REQ_de 70 +#define REQ_Bl 71 // mdoc(7) "Begin List" +#define REQ_El 72 // mdoc(7) "End List" +#define REQ_It 73 // mdoc(7) "ITem" +#define REQ_Bk 74 +#define REQ_Ek 75 +#define REQ_Dd 76 +#define REQ_Os 77 // mdoc(7) +#define REQ_Bt 78 +#define REQ_At 79 // mdoc(7) "AT&t" (not parsable, not callable) +#define REQ_Fx 80 // mdoc(7) "Freebsd" (not parsable, not callable) +#define REQ_Nx 81 +#define REQ_Ox 82 +#define REQ_Bx 83 // mdoc(7) "Bsd" +#define REQ_Ux 84 // mdoc(7) "UniX" +#define REQ_Dl 85 +#define REQ_Bd 86 +#define REQ_Ed 87 +#define REQ_Be 88 +#define REQ_Xr 89 // mdoc(7) "eXternal Reference" +#define REQ_Fl 90 // mdoc(7) "FLag" +#define REQ_Pa 91 +#define REQ_Pf 92 +#define REQ_Pp 93 +#define REQ_Dq 94 // mdoc(7) "Double Quote" +#define REQ_Op 95 +#define REQ_Oo 96 +#define REQ_Oc 97 +#define REQ_Pq 98 // mdoc(7) "Parenthese Quote" +#define REQ_Ql 99 +#define REQ_Sq 100 // mdoc(7) "Single Quote" +#define REQ_Ar 101 +#define REQ_Ad 102 +#define REQ_Em 103 // mdoc(7) "EMphasis" +#define REQ_Va 104 +#define REQ_Xc 105 +#define REQ_Nd 106 +#define REQ_Nm 107 +#define REQ_Cd 108 +#define REQ_Cm 109 +#define REQ_Ic 110 +#define REQ_Ms 111 +#define REQ_Or 112 +#define REQ_Sy 113 +#define REQ_Dv 114 +#define REQ_Ev 115 +#define REQ_Fr 116 +#define REQ_Li 117 +#define REQ_No 118 +#define REQ_Ns 119 +#define REQ_Tn 120 +#define REQ_nN 121 +#define REQ_perc_A 122 +#define REQ_perc_D 123 +#define REQ_perc_N 124 +#define REQ_perc_O 125 +#define REQ_perc_P 126 +#define REQ_perc_Q 127 +#define REQ_perc_V 128 +#define REQ_perc_B 129 +#define REQ_perc_J 130 +#define REQ_perc_R 131 +#define REQ_perc_T 132 +#define REQ_An 133 // mdoc(7) "Author Name" +#define REQ_Aq 134 // mdoc(7) "Angle bracket Quote" +#define REQ_Bq 135 // mdoc(7) "Bracket Quote" +#define REQ_Qq 136 // mdoc(7) "straight double Quote" +#define REQ_UR 137 // man(7) "URl" +#define REQ_UE 138 // man(7) "Url End" +#define REQ_UN 139 // man(7) "Url Name" (a.k.a. anchors) +#define REQ_troff 140 // groff(7) "TROFF mode" +#define REQ_nroff 141 // groff(7) "NROFF mode" +#define REQ_als 142 // groff(7) "ALias String" +#define REQ_rr 143 // groff(7) "Remove number Register" +#define REQ_rnn 144 // groff(7) "ReName Number register" +#define REQ_aln 145 // groff(7) "ALias Number register" +#define REQ_shift 146 // groff(7) "SHIFT parameter" +#define REQ_while 147 // groff(7) "WHILE loop" +#define REQ_do 148 // groff(7) "DO command" +#define REQ_Dx 149 // mdoc(7) "DragonFly" macro + +static int get_request(char *req, int len) +{ + static const char *requests[] = { + "ab", "di", "ds", "as", "br", "c2", "cc", "ce", "ec", "eo", "ex", "fc", + "fi", "ft", "el", "ie", "if", "ig", "nf", "ps", "sp", "so", "ta", "ti", + "tm", "B", "I", "Fd", "Fn", "Fo", "Fc", "OP", "Ft", "Fa", "BR", "BI", + "IB", "IR", "RB", "RI", "DT", "IP", "TP", "IX", "P", "LP", "PP", "HP", + "PD", "Rs", "RS", "Re", "RE", "SB", "SM", "Ss", "SS", "Sh", "SH", "Sx", + "TS", "Dt", "TH", "TX", "rm", "rn", "nx", "in", "nr", "am", "de", "Bl", + "El", "It", "Bk", "Ek", "Dd", "Os", "Bt", "At", "Fx", "Nx", "Ox", "Bx", + "Ux", "Dl", "Bd", "Ed", "Be", "Xr", "Fl", "Pa", "Pf", "Pp", "Dq", "Op", + "Oo", "Oc", "Pq", "Ql", "Sq", "Ar", "Ad", "Em", "Va", "Xc", "Nd", "Nm", + "Cd", "Cm", "Ic", "Ms", "Or", "Sy", "Dv", "Ev", "Fr", "Li", "No", "Ns", + "Tn", "nN", "%A", "%D", "%N", "%O", "%P", "%Q", "%V", "%B", "%J", "%R", + "%T", "An", "Aq", "Bq", "Qq", "UR", "UE", "UN", "troff", "nroff", "als", + "rr", "rnn", "aln", "shift", "while", "do", "Dx", 0 }; + int r = 0; + while (requests[r] && qstrncmp(req, requests[r], len)) r++; + return requests[r] ? r : REQ_UNKNOWN; +} + +// &%(#@ c programs !!! +//static int ifelseval=0; +// If/else can be nested! +static QValueStack<int> s_ifelseval; + +// Process a (mdoc) request involving quotes +static char* process_quote(char* c, int j, const char* open, const char* close) +{ + trans_char(c,'"','\a'); + c+=j; + if (*c=='\n') c++; // ### TODO: why? Quote requests cannot be empty! + out_html(open); + c=scan_troff_mandoc(c,1,0); + out_html(close); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + return c; +} + +/** + * Is the char \p ch a puntuaction in sence of mdoc(7) + */ +static bool is_mdoc_punctuation( const char ch ) +{ + if ( ( ch >= '0' && ch <= '9' ) || ( ch >='A' && ch <='Z' ) || ( ch >= 'a' && ch <= 'z' ) ) + return false; + else if ( ch == '.' || ch == ',' || ch == ';' || ch == ':' || ch == '(' || ch == ')' + || ch == '[' || ch == ']' ) + return true; + else + return false; +} + +/** + * Can the char \p c be part of an identifier + * \note For groff, an identifier can consist of nearly all ASCII printable non-white-space characters + * See info:/groff/Identifiers + */ +static bool is_identifier_char( const char c ) +{ + if ( c >= '!' && c <= '[' ) // Include digits and upper case + return true; + else if ( c >= ']' && c <= '~' ) // Include lower case + return true; + else if ( c== '\\' ) + return false; // ### TODO: it should be treated as escape instead! + return false; +} + +static QCString scan_identifier( char*& c ) +{ + char* h = c; // help pointer + // ### TODO Groff seems to eat nearly everything as identifier name (info:/groff/Identifiers) + while ( *h && *h != '\a' && *h != '\n' && is_identifier_char( *h ) ) + ++h; + const char tempchar = *h; + *h = 0; + const QCString name = c; + *h = tempchar; + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: identifier empty!" << endl; + } + c = h; + return name; +} + +static char *scan_request(char *c) +{ + // mdoc(7) stuff + static bool mandoc_synopsis=false; /* True if we are in the synopsis section */ + static bool mandoc_command=false; /* True if this is mdoc(7) page */ + static int mandoc_bd_options; /* Only copes with non-nested Bd's */ + static int function_argument=0; // Number of function argument (.Fo, .Fa, .Fc) + // man(7) stuff + static bool ur_ignore=false; // Has .UR a parameter : (for .UE to know if or not to write </a>) + + int i=0; + bool mode=false; + char *h=0; + char *wordlist[max_wordlist]; + int words; + char *sl; + while (*c==' ' || *c=='\t') c++; // Spaces or tabs allowed between control character and request + if (c[0]=='\n') return c+1; + if (c[0]==escapesym) + { + /* some pages use .\" .\$1 .\} */ + /* .\$1 is too difficult/stuppid */ + if (c[1]=='$') + { + kdDebug(7107) << "Found .\\$" << endl; + c=skip_till_newline(c); // ### TODO + } + else + + c = scan_escape(c+1); + } + else + { + int nlen = 0; + QCString macroName; + while (c[nlen] && (c[nlen] != ' ') && (c[nlen] != '\t') && (c[nlen] != '\n') && (c[nlen] != escapesym)) + { + macroName+=c[nlen]; + nlen++; + } + int j = nlen; + while (c[j] && c[j]==' ' || c[j]=='\t') j++; + /* search macro database of self-defined macros */ + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(macroName); + if (it!=s_stringDefinitionMap.end()) + { + kdDebug(7107) << "CALLING MACRO: " << macroName << endl; + const QCString oldDollarZero = s_dollarZero; // Previous value of $0 + s_dollarZero = macroName; + sl=fill_words(c+j, wordlist, &words, true, &c); + *sl='\0'; + for (i=1;i<words; i++) wordlist[i][-1]='\0'; + for (i=0; i<words; i++) + { + char *h=NULL; + if (mandoc_command) + scan_troff_mandoc(wordlist[i],1,&h); + else + scan_troff(wordlist[i],1,&h); + wordlist[i] = qstrdup(h); + delete [] h; + } + for ( i=words; i<max_wordlist; i++ ) wordlist[i]=NULL; + if ( !(*it).m_output.isEmpty() ) + { + //kdDebug(7107) << "Macro content is: " << endl << (*it).m_output << endl; + const unsigned int length = (*it).m_output.length(); + char* work = new char [length+2]; + work[0] = '\n'; // The macro must start after an end of line to allow a request on first line + qstrncpy(work+1,(*it).m_output.data(),length+1); + const QValueList<char*> oldArgumentList( s_argumentList ); + s_argumentList.clear(); + for ( i = 0 ; i < max_wordlist; i++ ) + { + if (!wordlist[i]) + break; + s_argumentList.push_back( wordlist[i] ); + } + const int onff=newline_for_fun; + if (mandoc_command) + scan_troff_mandoc( work + 1, 0, NULL ); + else + scan_troff( work + 1, 0, NULL); + delete[] work; + newline_for_fun=onff; + s_argumentList = oldArgumentList; + } + for (i=0; i<words; i++) delete [] wordlist[i]; + *sl='\n'; + s_dollarZero = oldDollarZero; + kdDebug(7107) << "ENDING MACRO: " << macroName << endl; + } + else + { + kdDebug(7107) << "REQUEST: " << macroName << endl; + switch (int request = get_request(c, nlen)) + { + case REQ_ab: // groff(7) "ABort" + { + h=c+j; + while (*h && *h !='\n') h++; + *h='\0'; + if (scaninbuff && buffpos) + { + buffer[buffpos]='\0'; + kdDebug(7107) << "ABORT: " << buffer << endl; + } + // ### TODO find a way to display it to the user + kdDebug(7107) << "Aborting: .ab " << (c+j) << endl; + return 0; + break; + } + case REQ_An: // mdoc(7) "Author Name" + { + c+=j; + c=scan_troff_mandoc(c,1,0); + break; + } + case REQ_di: // groff(7) "end current DIversion" + { + kdDebug(7107) << "Start .di" << endl; + c+=j; + if (*c=='\n') + { + ++c; + break; + } + const QCString name ( scan_identifier( c ) ); + while (*c && *c!='\n') c++; + c++; + h=c; + while (*c && qstrncmp(c,".di",3)) while (*c && *c++!='\n'); + *c='\0'; + char* result=0; + scan_troff(h,0,&result); + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); + if (it==s_stringDefinitionMap.end()) + { + StringDefinition def; + def.m_length=0; + def.m_output=result; + s_stringDefinitionMap.insert(name,def); + } + else + { + (*it).m_length=0; + (*it).m_output=result; + } + delete[] result; + if (*c) *c='.'; + c=skip_till_newline(c); + kdDebug(7107) << "end .di" << endl; + break; + } + case REQ_ds: // groff(7) "Define String variable" + mode=true; + case REQ_as: // groff (7) "Append String variable" + { + kdDebug(7107) << "start .ds/.as" << endl; + int oldcurpos=curpos; + c+=j; + const QCString name( scan_identifier( c) ); + if ( name.isEmpty() ) + break; + while (*c && isspace(*c)) c++; + if (*c && *c=='"') c++; + single_escape=true; + curpos=0; + char* result=0; + c=scan_troff(c,1,&result); + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); + if (it==s_stringDefinitionMap.end()) + { + StringDefinition def; + def.m_length=curpos; + def.m_output=result; + s_stringDefinitionMap.insert(name,def); + } + else + { + if (mode) + { // .ds Defining String + (*it).m_length=curpos; + (*it).m_output=result; + } + else + { // .as Appending String + (*it).m_length+=curpos; + (*it).m_output+=result; + } + } + delete[] result; + single_escape=false; + curpos=oldcurpos; + kdDebug(7107) << "end .ds/.as" << endl; + break; + } + case REQ_br: // groff(7) "line BReak" + { + if (still_dd) + out_html("<DD>"); // ### VERIFY (does not look like generating good HTML) + else + out_html("<BR>\n"); + curpos=0; + c=c+j; + if (c[0]==escapesym) c=scan_escape(c+1); + c=skip_till_newline(c); + break; + } + case REQ_c2: // groff(7) "reset non-break Control character" (2 means non-break) + { + c=c+j; + if (*c!='\n') + nobreaksym=*c; + else + nobreaksym='\''; + c=skip_till_newline(c); + break; + } + case REQ_cc: // groff(7) "reset Control Character" + { + c=c+j; + if (*c!='\n') + controlsym=*c; + else + controlsym='.'; + c=skip_till_newline(c); + break; + } + case REQ_ce: // groff (7) "CEnter" + { + c=c+j; + if (*c=='\n') + i=1; + else + { + i=0; + while ('0'<=*c && *c<='9') + { + i=i*10+*c-'0'; + c++; + } + } + c=skip_till_newline(c); + /* center next i lines */ + if (i>0) + { + out_html("<CENTER>\n"); + while (i && *c) + { + char *line=NULL; + c=scan_troff(c,1, &line); + if (line && qstrncmp(line, "<BR>", 4)) + { + out_html(line); + out_html("<BR>\n"); + delete [] line; // ### FIXME: memory leak! + i--; + } + } + out_html("</CENTER>\n"); + curpos=0; + } + break; + } + case REQ_ec: // groff(7) "reset Escape Character" + { + c=c+j; + if (*c!='\n') + escapesym=*c; + else + escapesym='\\'; + break; + c=skip_till_newline(c); + } + case REQ_eo: // groff(7) "turn Escape character Off" + { + escapesym='\0'; + c=skip_till_newline(c); + break; + } + case REQ_ex: // groff(7) "EXit" + { + return 0; + break; + } + case REQ_fc: // groff(7) "set Field and pad Character" + { + c=c+j; + if (*c=='\n') + fieldsym=padsym='\0'; + else + { + fieldsym=c[0]; + padsym=c[1]; + } + c=skip_till_newline(c); + break; + } + case REQ_fi: // groff(7) "FIll" + { + if (!fillout) + { + out_html(set_font("R")); + out_html(change_to_size('0')); + out_html("</PRE>\n"); + } + curpos=0; + fillout=1; + c=skip_till_newline(c); + break; + } + case REQ_ft: // groff(7) "FonT" + { + c += j; + h = skip_till_newline( c ); + const char oldChar = *h; + *h = 0; + const QCString name = c; + // ### TODO: name might contain a variable + if ( name.isEmpty() ) + out_html( set_font( "P" ) ); // Previous font + else + out_html( set_font( name ) ); + *h = oldChar; + c = h; + break; + } + case REQ_el: // groff(7) "ELse" + { + int ifelseval = s_ifelseval.pop(); + /* .el anything : else part of if else */ + if (ifelseval) + { + c=c+j; + c[-1]='\n'; + c=scan_troff(c,1,NULL); + } + else + c=skip_till_newline(c+j); + break; + } + case REQ_ie: // groff(7) "If with Else" + /* .ie c anything : then part of if else */ + case REQ_if: // groff(7) "IF" + { + /* .if c anything + * .if !c anything + * .if N anything + * .if !N anything + * .if 'string1'string2' anything + * .if !'string1'string2' anything + */ + c=c+j; + c=scan_expression(c, &i); + if (request == REQ_ie) + { + int ifelseval=!i; + s_ifelseval.push( ifelseval ); + } + if (i) + { + *c='\n'; + c++; + c=scan_troff(c,1,NULL); + } + else + c=skip_till_newline(c); + break; + } + case REQ_ig: // groff(7) "IGnore" + { + const char *endwith="..\n"; + i=3; + c=c+j; + if (*c!='\n' && *c != '\\') + { + /* Not newline or comment */ + endwith=c-1;i=1; + c[-1]='.'; + while (*c && *c!='\n') c++,i++; + } + c++; + while (*c && qstrncmp(c,endwith,i)) while (*c++!='\n'); + while (*c && *c++!='\n'); + break; + } + case REQ_nf: // groff(7) "No Filling" + { + if (fillout) + { + out_html(set_font("R")); + out_html(change_to_size('0')); + out_html("<PRE>\n"); + } + curpos=0; + fillout=0; + c=skip_till_newline(c); + break; + } + case REQ_ps: // groff(7) "previous Point Size" + { + c=c+j; + if (*c=='\n') + out_html(change_to_size('0')); + else + { + j=0; i=0; + if (*c=='-') + { + j= -1; + c++; + } + else if (*c=='+') + j=1;c++; + c=scan_expression(c, &i); + if (!j) + { + j=1; + if (i>5) i=i-10; + } + out_html(change_to_size(i*j)); + } + c=skip_till_newline(c); + break; + } + case REQ_sp: // groff(7) "SKip one line" + { + c=c+j; + if (fillout) + out_html("<br><br>"); + else + { + out_html(NEWLINE); + } + curpos=0; + c=skip_till_newline(c); + break; + } + case REQ_so: // groff(7) "Include SOurce file" + { + char *buf; + char *name=NULL; + curpos=0; + c=c+j; + if (*c=='/') + h=c; + else + { + h=c-3; + h[0]='.'; + h[1]='.'; + h[2]='/'; + } + while (*c!='\n') c++; + *c='\0'; + scan_troff(h,1, &name); + if (name[3]=='/') + h=name+3; + else + h=name; + /* this works alright, except for section 3 */ + buf=read_man_page(h); + if (!buf) + { + kdDebug(7107) << "Unable to open or read file: .so " << (h) << endl; + out_html("<BLOCKQUOTE>" + "man2html: unable to open or read file.\n"); + out_html(h); + out_html("</BLOCKQUOTE>\n"); + } + else + scan_troff(buf+1,0,NULL); + delete [] buf; + delete [] name; + + *c++='\n'; + break; + } + case REQ_ta: // gorff(7) "set TAbulators" + { + c=c+j; + j=0; + while (*c!='\n') + { + sl=scan_expression(c, &tabstops[j]); + if (j>0 && (*c=='-' || *c=='+')) tabstops[j]+=tabstops[j-1]; + c=sl; + while (*c==' ' || *c=='\t') c++; + j++; + } + maxtstop=j; + curpos=0; + break; + } + case REQ_ti: // groff(7) "Temporary Indent" + { + /*while (itemdepth || dl_set[itemdepth]) { + out_html("</DL>\n"); + if (dl_set[itemdepth]) dl_set[itemdepth]=0; + else itemdepth--; + }*/ + out_html("<BR>\n"); + c=c+j; + c=scan_expression(c, &j); + for (i=0; i<j; i++) out_html(" "); + curpos=j; + c=skip_till_newline(c); + break; + } + case REQ_tm: // groff(7) "TerMinal" ### TODO: what are useful uses for it + { + c=c+j; + h=c; + while (*c!='\n') c++; + *c='\0'; + kdDebug(7107) << ".tm " << (h) << endl; + *c='\n'; + break; + } + case REQ_B: // man(7) "Bold" + mode=1; + case REQ_I: // man(7) "Italic" + { + /* parse one line in a certain font */ + out_html( set_font( mode?"B":"I" ) ); + fill_words(c, wordlist, &words, false, 0); + c=c+j; + if (*c=='\n') c++; + c=scan_troff(c, 1, NULL); + out_html(set_font("R")); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Fd: // mdoc(7) "Function Definition" + { + // Normal text must be printed in bold, punctuation in regular font + c+=j; + if (*c=='\n') c++; // ### TODO: verify + sl=fill_words(c, wordlist, &words, true, &c); + for (i=0; i<words; i++) + { + wordlist[i][-1]=' '; + // ### FIXME In theory, only a single punctuation character is recognized as punctuation + if ( is_mdoc_punctuation ( *wordlist[i] ) ) + out_html( set_font ( "R" ) ); + else + out_html( set_font ( "B" ) ); + scan_troff(wordlist[i],1,NULL); + out_html(" "); + } + // In the mdoc synopsis, there are automatical line breaks (### TODO: before or after?) + if (mandoc_synopsis) + { + out_html("<br>"); + }; + out_html(set_font("R")); + out_html(NEWLINE); + if (!fillout) + curpos=0; + else + curpos++; + break; + } + case REQ_Fn: // mdoc(7) for "Function calls" + { + // brackets and commas have to be inserted automatically + c+=j; + if (*c=='\n') c++; + sl=fill_words(c, wordlist, &words, true, &c); + if ( words ) + { + for (i=0; i<words; i++) + { + wordlist[i][-1]=' '; + if ( i ) + out_html( set_font( "I" ) ); + else + out_html( set_font( "B" ) ); + scan_troff(wordlist[i],1,NULL); + out_html( set_font( "R" ) ); + if (i==0) + { + out_html(" ("); + } + else if (i<words-1) + out_html(", "); + } + out_html(")"); + } + out_html(set_font("R")); + if (mandoc_synopsis) + out_html("<br>"); + out_html(NEWLINE); + if (!fillout) + curpos=0; + else + curpos++; + break; + } + case REQ_Fo: // mdoc(7) "Function definition Opening" + { + char* font[2] = { "B", "R" }; + c+=j; + if (*c=='\n') c++; + char *eol=strchr(c,'\n'); + char *semicolon=strchr(c,';'); + if ((semicolon!=0) && (semicolon<eol)) *semicolon=' '; + + sl=fill_words(c, wordlist, &words, true, &c); + // Normally a .Fo has only one parameter + for (i=0; i<words; i++) + { + wordlist[i][-1]=' '; + out_html(set_font(font[i&1])); + scan_troff(wordlist[i],1,NULL); + if (i==0) + { + out_html(" ("); + } + // ### TODO What should happen if there is more than one argument + // else if (i<words-1) out_html(", "); + } + function_argument=1; // Must be > 0 + out_html(set_font("R")); + out_html(NEWLINE); + if (!fillout) + curpos=0; + else + curpos++; + break; + } + case REQ_Fc:// mdoc(7) "Function definition Close" + { + // .Fc has no parameter + c+=j; + c=skip_till_newline(c); + char* font[2] = { "B", "R" }; + out_html(set_font(font[i&1])); + out_html(")"); + out_html(set_font("R")); + if (mandoc_synopsis) + out_html("<br>"); + out_html(NEWLINE); + if (!fillout) + curpos=0; + else + curpos++; + function_argument=0; // Reset the count variable + break; + } + case REQ_Fa: // mdoc(7) "Function definition argument" + { + char* font[2] = { "B", "R" }; + c+=j; + if (*c=='\n') c++; + sl=fill_words(c, wordlist, &words, true, &c); + out_html(set_font(font[i&1])); + // function_argument==0 means that we had no .Fo before, e.g. in mdoc.samples(7) + if (function_argument > 1) + { + out_html(", "); + curpos+=2; + function_argument++; + } + else if (function_argument==1) + { + // We are only at the first parameter + function_argument++; + } + for (i=0; i<words; i++) + { + wordlist[i][-1]=' '; + scan_troff(wordlist[i],1,NULL); + } + out_html(set_font("R")); + if (!fillout) + curpos=0; + else + curpos++; + break; + } + + case REQ_OP: /* groff manpages use this construction */ + { + /* .OP a b : [ <B>a</B> <I>b</I> ] */ + mode=true; + out_html(set_font("R")); + out_html("["); + curpos++; + request_mixed_fonts( c, j, "B", "I", true, false ); + break; + // Do not break! + } + case REQ_Ft: //perhaps "Function return type" + { + request_mixed_fonts( c, j, "B", "I", false, true ); + break; + } + case REQ_BR: + { + request_mixed_fonts( c, j, "B", "R", false, false ); + break; + } + case REQ_BI: + { + request_mixed_fonts( c, j, "B", "I", false, false ); + break; + } + case REQ_IB: + { + request_mixed_fonts( c, j, "I", "B", false, false ); + break; + } + case REQ_IR: + { + request_mixed_fonts( c, j, "I", "R", false, false ); + break; + } + case REQ_RB: + { + request_mixed_fonts( c, j, "R", "B", false, false ); + break; + } + case REQ_RI: + { + request_mixed_fonts( c, j, "R", "I", false, false ); + break; + } + case REQ_DT: // man(7) "Default Tabulators" + { + for (j=0;j<20; j++) tabstops[j]=(j+1)*8; + maxtstop=20; + c=skip_till_newline(c); + break; + } + case REQ_IP: // man(7) "Ident Paragraph" + { + sl=fill_words(c+j, wordlist, &words, true, &c); + if (!dl_set[itemdepth]) + { + out_html("<DL>\n"); + dl_set[itemdepth]=1; + } + out_html("<DT>"); + if (words) + scan_troff(wordlist[0], 1,NULL); + out_html("<DD>"); + curpos=0; + break; + } + case REQ_TP: // man(7) "hanging Tag Paragraph" + { + if (!dl_set[itemdepth]) + { + out_html("<br><br><DL>\n"); + dl_set[itemdepth]=1; + } + out_html("<DT>"); + c=skip_till_newline(c); + /* somewhere a definition ends with '.TP' */ + if (!*c) + still_dd=true; + else + { + // HACK for proc(5) + while (c[0]=='.' && c[1]=='\\' && c[2]=='\"') + { + // We have a comment, so skip the line + c=skip_till_newline(c); + } + c=scan_troff(c,1,NULL); + out_html("<DD>"); + } + curpos=0; + break; + } + case REQ_IX: // "INdex" ### TODO: where is it defined? + { + /* general index */ + c=skip_till_newline(c); + break; + } + case REQ_P: // man(7) "Paragraph" + case REQ_LP:// man(7) "Paragraph" + case REQ_PP:// man(7) "Paragraph; reset Prevailing indent" + { + if (dl_set[itemdepth]) + { + out_html("</DL>\n"); + dl_set[itemdepth]=0; + } + if (fillout) + out_html("<br><br>\n"); + else + { + out_html(NEWLINE); + } + curpos=0; + c=skip_till_newline(c); + break; + } + case REQ_HP: // man(7) "Hanging indent Paragraph" + { + if (!dl_set[itemdepth]) + { + out_html("<DL>"); + dl_set[itemdepth]=1; + } + out_html("<DT>\n"); + still_dd=true; + c=skip_till_newline(c); + curpos=0; + break; + } + case REQ_PD: // man(7) "Paragraph Distance" + { + c=skip_till_newline(c); + break; + } + case REQ_Rs: // mdoc(7) "Relative margin Start" + case REQ_RS: // man(7) "Relative margin Start" + { + sl=fill_words(c+j, wordlist, &words, true, 0); + j=1; + if (words>0) scan_expression(wordlist[0], &j); + if (j>=0) + { + itemdepth++; + dl_set[itemdepth]=0; + out_html("<DL><DT><DD>"); + c=skip_till_newline(c); + curpos=0; + break; + } + } + case REQ_Re: // mdoc(7) "Relative margin End" + case REQ_RE: // man(7) "Relative margin End" + { + if (itemdepth > 0) + { + if (dl_set[itemdepth]) out_html("</DL>"); + out_html("</DL>\n"); + itemdepth--; + } + c=skip_till_newline(c); + curpos=0; + break; + } + case REQ_SB: // man(7) "Small; Bold" + { + out_html(set_font("B")); + out_html("<small>"); + trans_char(c,'"','\a'); // ### VERIFY + c=scan_troff(c+j, 1, NULL); + out_html("</small>"); + out_html(set_font("R")); + break; + } + case REQ_SM: // man(7) "SMall" + { + c=c+j; + if (*c=='\n') c++; + out_html("<small>"); + trans_char(c,'"','\a'); // ### VERIFY + c=scan_troff(c,1,NULL); + out_html("</small>"); + break; + } + case REQ_Ss: // mdoc(7) "Sub Section" + mandoc_command = 1; + case REQ_SS: // mdoc(7) "Sub Section" + mode=true; + case REQ_Sh: // mdoc(7) "Sub Header" + /* hack for fallthru from above */ + mandoc_command = !mode || mandoc_command; + case REQ_SH: // man(7) "Sub Header" + { + c=c+j; + if (*c=='\n') c++; + while (itemdepth || dl_set[itemdepth]) + { + out_html("</DL>\n"); + if (dl_set[itemdepth]) + dl_set[itemdepth]=0; + else if (itemdepth > 0) + itemdepth--; + } + out_html(set_font("R")); + out_html(change_to_size(0)); + if (!fillout) + { + fillout=1; + out_html("</PRE>"); + } + trans_char(c,'"', '\a'); + if (section) + { + out_html("</div>\n"); + section=0; + } + if (mode) + out_html("\n<H3>"); + else + out_html("\n<H2>"); + mandoc_synopsis = qstrncmp(c, "SYNOPSIS", 8) == 0; + c = mandoc_command ? scan_troff_mandoc(c,1,NULL) : scan_troff(c,1,NULL); + if (mode) + out_html("</H3>\n"); + else + out_html("</H2>\n"); + out_html("<div>\n"); + + section=1; + curpos=0; + break; + } + case REQ_Sx: // mdoc(7) + { + // reference to a section header + out_html(set_font("B")); + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + c=scan_troff(c, 1, NULL); + out_html(set_font("R")); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_TS: // ### TODO where is it defined? (tbl?) + { + c=scan_table(c); + break; + } + case REQ_Dt: /* mdoc(7) */ + mandoc_command = true; + case REQ_TH: // man(7) "Title Header" + { + if (!output_possible) + { + sl = fill_words(c+j, wordlist, &words, true, &c); + // ### TODO: the page should be displayed even if it is "anonymous" (words==0) + if (words>=1) + { + for (i=1; i<words; i++) wordlist[i][-1]='\0'; + *sl='\0'; + for (i=0; i<words; i++) + { + if (wordlist[i][0] == '\007') + wordlist[i]++; + if (wordlist[i][qstrlen(wordlist[i])-1] == '\007') + wordlist[i][qstrlen(wordlist[i])-1] = 0; + } + output_possible=true; + out_html( DOCTYPE"<HTML>\n<HEAD>\n"); +#ifdef SIMPLE_MAN2HTML + // Most English man pages are in ISO-8859-1 + out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n"); +#else + // kio_man transforms from local to UTF-8 + out_html("<meta http-equiv=\"Content-Type\" content=\"text/html; charset="); + out_html(QTextCodec::codecForLocale()->mimeName()); + out_html("\">\n"); +#endif + out_html("<TITLE>"); + out_html(scan_troff(wordlist[0], 0, NULL)); + out_html( " Manpage</TITLE>\n"); + out_html( "<link rel=\"stylesheet\" href=\""); + out_html(htmlPath); + out_html("/kde-default.css\" type=\"text/css\">\n" ); + out_html( "<meta name=\"ROFF Type\" content=\""); + if (mandoc_command) + out_html("mdoc"); + else + out_html("man"); + out_html("\">\n"); + out_html( "</HEAD>\n\n" ); + out_html("<BODY BGCOLOR=\"#FFFFFF\">\n\n" ); + out_html("<div style=\"background-image: url("); + out_html(cssPath); + out_html("/top-middle.png); width: 100%; height: 131pt;\">\n" ); + out_html("<div style=\"position: absolute; right: 0pt;\">\n"); + out_html("<img src=\""); + out_html(htmlPath); + out_html("/top-right-konqueror.png\" style=\"margin: 0pt\" alt=\"Top right\">\n"); + out_html("</div>\n"); + + out_html("<div style=\"position: absolute; left: 0pt;\">\n"); + out_html("<img src=\""); + out_html(htmlPath); + out_html("/top-left.png\" style=\"margin: 0pt\" alt=\"Top left\">\n"); + out_html("</div>\n"); + out_html("<div style=\"position: absolute; top: 25pt; right: 100pt; text-align: right; font-size: xx-large; font-weight: bold; text-shadow: #fff 0pt 0pt 5pt; color: #444\">\n"); + out_html( scan_troff(wordlist[0], 0, NULL ) ); + out_html("</div>\n"); + out_html("</div>\n"); + out_html("<div style=\"margin-left: 5em; margin-right: 5em;\">\n"); + out_html("<h1>" ); + out_html( scan_troff(wordlist[0], 0, NULL ) ); + out_html( "</h1>\n" ); + if (words>1) + { + out_html("Section: " ); + if (!mandoc_command && words>4) + out_html(scan_troff(wordlist[4], 0, NULL) ); + else + out_html(section_name(wordlist[1])); + out_html(" ("); + out_html(scan_troff(wordlist[1], 0, NULL)); + out_html(")\n"); + } + else + { + out_html("Section not specified"); + } + *sl='\n'; + } + } + else + { + kdWarning(7107) << ".TH found but output not possible" << endl; + c=skip_till_newline(c); + } + curpos=0; + break; + } + case REQ_TX: // mdoc(7) + { + sl=fill_words(c+j, wordlist, &words, true, &c); + *sl='\0'; + out_html(set_font("I")); + if (words>1) wordlist[1][-1]='\0'; + const char *c2=lookup_abbrev(wordlist[0]); + curpos+=qstrlen(c2); + out_html(c2); + out_html(set_font("R")); + if (words>1) + out_html(wordlist[1]); + *sl='\n'; + break; + } + case REQ_rm: // groff(7) "ReMove" + /* .rm xx : Remove request, macro or string */ + mode=true; + case REQ_rn: // groff(7) "ReName" + /* .rn xx yy : Rename request, macro or string xx to yy */ + { + kdDebug(7107) << "start .rm/.rn" << endl; + c+=j; + const QCString name( scan_identifier( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl; + break; + } + QCString name2; + if ( !mode ) + { + while (*c && isspace(*c) && *c!='\n') ++c; + name2 = scan_identifier( c ); + if ( name2.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty destination string to rename: " << endl; + break; + } + } + c=skip_till_newline(c); + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); + if (it==s_stringDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find string to rename or remove: " << name << endl; + } + else + { + if (mode) + { + // .rm ReMove + s_stringDefinitionMap.remove(name); // ### QT4: removeAll + } + else + { + // .rn ReName + StringDefinition def=(*it); + s_stringDefinitionMap.remove(name); // ### QT4: removeAll + s_stringDefinitionMap.insert(name2,def); + } + } + kdDebug(7107) << "end .rm/.rn" << endl; + break; + } + case REQ_nx: // ### TODO in man(7) it is "No filling", not "next file" + /* .nx filename : next file. */ + case REQ_in: // groff(7) "INdent" + { + /* .in +-N : Indent */ + c=skip_till_newline(c); + break; + } + case REQ_nr: // groff(7) "Number Register" + { + kdDebug(7107) << "start .nr" << endl; + c += j; + const QCString name( scan_identifier( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty name for register variable" << endl; + break; + } + while ( *c && ( *c==' ' || *c=='\t' ) ) c++; + int sign = 0; + if ( *c && ( *c == '+' || *c == '-' ) ) + { + if ( *c == '+' ) + sign = 1; + else if ( *c == '-' ) + sign = -1; + } + int value = 0; + int increment = 0; + c=scan_expression( c, &value ); + if ( *c && *c!='\n') + { + while ( *c && ( *c==' ' || *c=='\t' ) ) c++; + c=scan_expression( c, &increment ); + } + c = skip_till_newline( c ); + QMap <QCString, NumberDefinition>::iterator it = s_numberDefinitionMap.find( name ); + if ( it == s_numberDefinitionMap.end() ) + { + if ( sign < 1 ) + value = -value; + NumberDefinition def( value, increment ); + s_numberDefinitionMap.insert( name, def ); + } + else + { + if ( sign > 0 ) + (*it).m_value += value; + else if ( sign < 0 ) + (*it).m_value += - value; + else + (*it).m_value = value; + (*it).m_increment = increment; + } + kdDebug(7107) << "end .nr" << endl; + break; + } + case REQ_am: // groff(7) "Append Macro" + /* .am xx yy : append to a macro. */ + /* define or handle as .ig yy */ + mode=true; + case REQ_de: // groff(7) "DEfine macro" + /* .de xx yy : define or redefine macro xx; end at .yy (..) */ + /* define or handle as .ig yy */ + { + kdDebug(7107) << "Start .am/.de" << endl; + c+=j; + char *next_line; + sl = fill_words(c, wordlist, &words, true, &next_line); + char *nameStart = wordlist[0]; + c = nameStart; + while (*c && (*c != ' ') && (*c != '\n')) c++; + *c = '\0'; + const QCString name(nameStart); + + QCString endmacro; + if (words == 1) + { + endmacro=".."; + } + else + { + endmacro='.'; + c = wordlist[1]; + while (*c && (*c != ' ') && (*c != '\n')) + endmacro+=*c++; + } + c = next_line; + sl=c; + const int length=qstrlen(endmacro); + while (*c && qstrncmp(c,endmacro,length)) + c=skip_till_newline(c); + + QCString macro; + while (sl!=c) + { + if (sl[0]=='\\' && sl[1]=='\\') + { + macro+='\\'; + sl++; + } + else + macro+=*sl; + sl++; + } + + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name); + if (it==s_stringDefinitionMap.end()) + { + StringDefinition def; + def.m_length=0; + def.m_output=macro; + s_stringDefinitionMap.insert(name,def); + } + else if (mode) + { + // .am Append Macro + (*it).m_length=0; // It could be formerly a string + if ((*it).m_output.right(1)!='\n') + (*it).m_output+='\n'; + (*it).m_output+=macro; + } + else + { + // .de DEfine macro + (*it).m_length=0; // It could be formerly a string + (*it).m_output=macro; + } + c=skip_till_newline(c); + kdDebug(7107) << "End .am/.de" << endl; + break; + } + case REQ_Bl: // mdoc(7) "Begin List" + { + char list_options[NULL_TERMINATED(MED_STR_MAX)]; + char *nl = strchr(c,'\n'); + c=c+j; + if (dl_set[itemdepth]) + /* These things can nest. */ + itemdepth++; + if (nl) + { + /* Parse list options */ + strlimitcpy(list_options, c, nl - c, MED_STR_MAX); + } + if (strstr(list_options, "-bullet")) + { + /* HTML Unnumbered List */ + dl_set[itemdepth] = BL_BULLET_LIST; + out_html("<UL>\n"); + } + else if (strstr(list_options, "-enum")) + { + /* HTML Ordered List */ + dl_set[itemdepth] = BL_ENUM_LIST; + out_html("<OL>\n"); + } + else + { + /* HTML Descriptive List */ + dl_set[itemdepth] = BL_DESC_LIST; + out_html("<DL>\n"); + } + if (fillout) + out_html("<br><br>\n"); + else + { + out_html(NEWLINE); + } + curpos=0; + c=skip_till_newline(c); + break; + } + case REQ_El: // mdoc(7) "End List" + { + c=c+j; + if (dl_set[itemdepth] & BL_DESC_LIST) + out_html("</DL>\n"); + else if (dl_set[itemdepth] & BL_BULLET_LIST) + out_html("</UL>\n"); + else if (dl_set[itemdepth] & BL_ENUM_LIST) + out_html("</OL>\n"); + dl_set[itemdepth]=0; + if (itemdepth > 0) itemdepth--; + if (fillout) + out_html("<br><br>\n"); + else + { + out_html(NEWLINE); + } + curpos=0; + c=skip_till_newline(c); + break; + } + case REQ_It: // mdoc(7) "list ITem" + { + c=c+j; + if (qstrncmp(c, "Xo", 2) == 0 && isspace(*(c+2))) + c = skip_till_newline(c); + if (dl_set[itemdepth] & BL_DESC_LIST) + { + out_html("<DT>"); + out_html(set_font("B")); + if (*c=='\n') + { + /* Don't allow embedded comms after a newline */ + c++; + c=scan_troff(c,1,NULL); + } + else + { + /* Do allow embedded comms on the same line. */ + c=scan_troff_mandoc(c,1,NULL); + } + out_html(set_font("R")); + out_html(NEWLINE); + out_html("<DD>"); + } + else if (dl_set[itemdepth] & (BL_BULLET_LIST | BL_ENUM_LIST)) + { + out_html("<LI>"); + c=scan_troff_mandoc(c,1,NULL); + out_html(NEWLINE); + } + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Bk: /* mdoc(7) */ + case REQ_Ek: /* mdoc(7) */ + case REQ_Dd: /* mdoc(7) */ + case REQ_Os: // mdoc(7) "Operating System" + { + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + c=scan_troff_mandoc(c, 1, NULL); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Bt: // mdoc(7) "Beta Test" + { + trans_char(c,'"','\a'); + c=c+j; + out_html(" is currently in beta test."); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_At: /* mdoc(7) */ + case REQ_Fx: /* mdoc(7) */ + case REQ_Nx: /* mdoc(7) */ + case REQ_Ox: /* mdoc(7) */ + case REQ_Bx: /* mdoc(7) */ + case REQ_Ux: /* mdoc(7) */ + case REQ_Dx: /* mdoc(7) */ + { + bool parsable=true; + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + if (request==REQ_At) + { + out_html("AT&T UNIX "); + parsable=false; + } + else if (request==REQ_Fx) + { + out_html("FreeBSD "); + parsable=false; + } + else if (request==REQ_Nx) + out_html("NetBSD "); + else if (request==REQ_Ox) + out_html("OpenBSD "); + else if (request==REQ_Bx) + out_html("BSD "); + else if (request==REQ_Ux) + out_html("UNIX "); + else if (request==REQ_Dx) + out_html("DragonFly "); + if (parsable) + c=scan_troff_mandoc(c,1,0); + else + c=scan_troff(c,1,0); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Dl: /* mdoc(7) */ + { + c=c+j; + out_html(NEWLINE); + out_html("<BLOCKQUOTE>"); + if (*c=='\n') c++; + c=scan_troff_mandoc(c, 1, NULL); + out_html("</BLOCKQUOTE>"); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Bd: /* mdoc(7) */ + { /* Seems like a kind of example/literal mode */ + char bd_options[NULL_TERMINATED(MED_STR_MAX)]; + char *nl = strchr(c,'\n'); + c=c+j; + if (nl) + strlimitcpy(bd_options, c, nl - c, MED_STR_MAX); + out_html(NEWLINE); + mandoc_bd_options = 0; /* Remember options for terminating Bl */ + if (strstr(bd_options, "-offset indent")) + { + mandoc_bd_options |= BD_INDENT; + out_html("<BLOCKQUOTE>\n"); + } + if ( strstr(bd_options, "-literal") || strstr(bd_options, "-unfilled")) + { + if (fillout) + { + mandoc_bd_options |= BD_LITERAL; + out_html(set_font("R")); + out_html(change_to_size('0')); + out_html("<PRE>\n"); + } + curpos=0; + fillout=0; + } + c=skip_till_newline(c); + break; + } + case REQ_Ed: /* mdoc(7) */ + { + if (mandoc_bd_options & BD_LITERAL) + { + if (!fillout) + { + out_html(set_font("R")); + out_html(change_to_size('0')); + out_html("</PRE>\n"); + } + } + if (mandoc_bd_options & BD_INDENT) + out_html("</BLOCKQUOTE>\n"); + curpos=0; + fillout=1; + c=skip_till_newline(c); + break; + } + case REQ_Be: /* mdoc(7) */ + { + c=c+j; + if (fillout) + out_html("<br><br>"); + else + { + out_html(NEWLINE); + } + curpos=0; + c=skip_till_newline(c); + break; + } + case REQ_Xr: /* mdoc(7) */ // ### FIXME: it should issue a <a href="man:somewhere(x)"> directly + { + /* Translate xyz 1 to xyz(1) + * Allow for multiple spaces. Allow the section to be missing. + */ + char buff[NULL_TERMINATED(MED_STR_MAX)]; + char *bufptr; + trans_char(c,'"','\a'); + bufptr = buff; + c = c+j; + if (*c == '\n') c++; /* Skip spaces */ + while (isspace(*c) && *c != '\n') c++; + while (isalnum(*c) || *c == '.' || *c == ':' || *c == '_' || *c == '-') + { + /* Copy the xyz part */ + *bufptr = *c; + bufptr++; + if (bufptr >= buff + MED_STR_MAX) break; + c++; + } + while (isspace(*c) && *c != '\n') c++; /* Skip spaces */ + if (isdigit(*c)) + { + /* Convert the number if there is one */ + *bufptr = '('; + bufptr++; + if (bufptr < buff + MED_STR_MAX) + { + while (isalnum(*c)) + { + *bufptr = *c; + bufptr++; + if (bufptr >= buff + MED_STR_MAX) break; + c++; + } + if (bufptr < buff + MED_STR_MAX) + { + *bufptr = ')'; + bufptr++; + } + } + } + while (*c != '\n') + { + /* Copy the remainder */ + if (!isspace(*c)) + { + *bufptr = *c; + bufptr++; + if (bufptr >= buff + MED_STR_MAX) break; + } + c++; + } + *bufptr = '\n'; + bufptr[1] = 0; + scan_troff_mandoc(buff, 1, NULL); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Fl: // mdoc(7) "FLags" + { + trans_char(c,'"','\a'); + c+=j; + sl=fill_words(c, wordlist, &words, true, &c); + out_html(set_font("B")); + if (!words) + { + out_html("-"); // stdin or stdout + } + else + { + for (i=0;i<words;++i) + { + if (ispunct(wordlist[i][0]) && wordlist[i][0]!='-') + { + scan_troff_mandoc(wordlist[i], 1, NULL); + } + else + { + if (i>0) + out_html(" "); // Put a space between flags + out_html("-"); + scan_troff_mandoc(wordlist[i], 1, NULL); + } + } + } + out_html(set_font("R")); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Pa: /* mdoc(7) */ + case REQ_Pf: /* mdoc(7) */ + { + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + c=scan_troff_mandoc(c, 1, NULL); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Pp: /* mdoc(7) */ + { + if (fillout) + out_html("<br><br>\n"); + else + { + out_html(NEWLINE); + } + curpos=0; + c=skip_till_newline(c); + break; + } + case REQ_Aq: // mdoc(7) "Angle bracket Quote" + c=process_quote(c,j,"<",">"); + break; + case REQ_Bq: // mdoc(7) "Bracket Quote" + c=process_quote(c,j,"[","]"); + break; + case REQ_Dq: // mdoc(7) "Double Quote" + c=process_quote(c,j,"“","”"); + break; + case REQ_Pq: // mdoc(7) "Parenthese Quote" + c=process_quote(c,j,"(",")"); + break; + case REQ_Qq: // mdoc(7) "straight double Quote" + c=process_quote(c,j,""","""); + break; + case REQ_Sq: // mdoc(7) "Single Quote" + c=process_quote(c,j,"‘","’"); + break; + case REQ_Op: /* mdoc(7) */ + { + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + out_html(set_font("R")); + out_html("["); + c=scan_troff_mandoc(c, 1, NULL); + out_html(set_font("R")); + out_html("]"); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Oo: /* mdoc(7) */ + { + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + out_html(set_font("R")); + out_html("["); + c=scan_troff_mandoc(c, 1, NULL); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Oc: /* mdoc(7) */ + { + trans_char(c,'"','\a'); + c=c+j; + c=scan_troff_mandoc(c, 1, NULL); + out_html(set_font("R")); + out_html("]"); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Ql: /* mdoc(7) */ + { + /* Single quote first word in the line */ + char *sp; + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + sp = c; + do + { + /* Find first whitespace after the + * first word that isn't a mandoc macro + */ + while (*sp && isspace(*sp)) sp++; + while (*sp && !isspace(*sp)) sp++; + } while (*sp && isupper(*(sp-2)) && islower(*(sp-1))); + + /* Use a newline to mark the end of text to + * be quoted + */ + if (*sp) *sp = '\n'; + out_html("`"); /* Quote the text */ + c=scan_troff_mandoc(c, 1, NULL); + out_html("'"); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Ar: /* mdoc(7) */ + { + /* parse one line in italics */ + out_html(set_font("I")); + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') + { + /* An empty Ar means "file ..." */ + out_html("file ..."); + } + else + c=scan_troff_mandoc(c, 1, NULL); + out_html(set_font("R")); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Em: /* mdoc(7) */ + { + out_html("<em>"); + trans_char(c,'"','\a'); + c+=j; + if (*c=='\n') c++; + c=scan_troff_mandoc(c, 1, NULL); + out_html("</em>"); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Ad: /* mdoc(7) */ + case REQ_Va: /* mdoc(7) */ + case REQ_Xc: /* mdoc(7) */ + { + /* parse one line in italics */ + out_html(set_font("I")); + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + c=scan_troff_mandoc(c, 1, NULL); + out_html(set_font("R")); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Nd: /* mdoc(7) */ + { + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + out_html(" - "); + c=scan_troff_mandoc(c, 1, NULL); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Nm: // mdoc(7) "Name Macro" ### FIXME + { + static char mandoc_name[NULL_TERMINATED(SMALL_STR_MAX)] = ""; // ### TODO Use QCString + trans_char(c,'"','\a'); + c=c+j; + + if (mandoc_synopsis && mandoc_name_count) + { + /* Break lines only in the Synopsis. + * The Synopsis section seems to be treated + * as a special case - Bummer! + */ + out_html("<BR>"); + } + else if (!mandoc_name_count) + { + const char *nextbreak = strchr(c, '\n'); + const char *nextspace = strchr(c, ' '); + if (nextspace < nextbreak) + nextbreak = nextspace; + + if (nextbreak) + { + /* Remember the name for later. */ + strlimitcpy(mandoc_name, c, nextbreak - c, SMALL_STR_MAX); + } + } + mandoc_name_count++; + + out_html(set_font("B")); + // ### FIXME: fill_words must be used + while (*c == ' '|| *c == '\t') c++; + if ((tolower(*c) >= 'a' && tolower(*c) <= 'z' ) || (*c >= '0' && *c <= '9')) + { + // alphanumeric argument + c=scan_troff_mandoc(c, 1, NULL); + out_html(set_font("R")); + out_html(NEWLINE); + } + else + { + /* If Nm has no argument, use one from an earlier + * Nm command that did have one. Hope there aren't + * too many commands that do this. + */ + out_html(mandoc_name); + out_html(set_font("R")); + } + + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_Cd: /* mdoc(7) */ + case REQ_Cm: /* mdoc(7) */ + case REQ_Ic: /* mdoc(7) */ + case REQ_Ms: /* mdoc(7) */ + case REQ_Or: /* mdoc(7) */ + case REQ_Sy: /* mdoc(7) */ + { + /* parse one line in bold */ + out_html(set_font("B")); + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + c=scan_troff_mandoc(c, 1, NULL); + out_html(set_font("R")); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + // ### FIXME: punctuation is handled badly! + case REQ_Dv: /* mdoc(7) */ + case REQ_Ev: /* mdoc(7) */ + case REQ_Fr: /* mdoc(7) */ + case REQ_Li: /* mdoc(7) */ + case REQ_No: /* mdoc(7) */ + case REQ_Ns: /* mdoc(7) */ + case REQ_Tn: /* mdoc(7) */ + case REQ_nN: /* mdoc(7) */ + { + trans_char(c,'"','\a'); + c=c+j; + if (*c=='\n') c++; + out_html(set_font("B")); + c=scan_troff_mandoc(c, 1, NULL); + out_html(set_font("R")); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_perc_A: /* mdoc(7) biblio stuff */ + case REQ_perc_D: + case REQ_perc_N: + case REQ_perc_O: + case REQ_perc_P: + case REQ_perc_Q: + case REQ_perc_V: + { + c=c+j; + if (*c=='\n') c++; + c=scan_troff(c, 1, NULL); /* Don't allow embedded mandoc coms */ + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_perc_B: + case REQ_perc_J: + case REQ_perc_R: + case REQ_perc_T: + { + c=c+j; + out_html(set_font("I")); + if (*c=='\n') c++; + c=scan_troff(c, 1, NULL); /* Don't allow embedded mandoc coms */ + out_html(set_font("R")); + if (fillout) + curpos++; + else + curpos=0; + break; + } + case REQ_UR: // ### FIXME man(7) "URl" + { + ignore_links=true; + c+=j; + char* newc; + h=fill_words(c, wordlist, &words, false, &newc); + *h=0; + if (words>0) + { + h=wordlist[0]; + // A parameter : means that we do not want an URL, not here and not until .UE + ur_ignore=(!qstrcmp(h,":")); + } + else + { + // We cannot find the URL, assume : + ur_ignore=true; + h=0; + } + if (!ur_ignore && words>0) + { + out_html("<a href=\""); + out_html(h); + out_html("\">"); + } + c=newc; // Go to next line + break; + } + case REQ_UE: // ### FIXME man(7) "Url End" + { + c+=j; + c = skip_till_newline(c); + if (!ur_ignore) + { + out_html("</a>"); + } + ur_ignore=false; + ignore_links=false; + break; + } + case REQ_UN: // ### FIXME man(7) "Url Named anchor" + { + c+=j; + char* newc; + h=fill_words(c, wordlist, &words, false, &newc); + *h=0; + if (words>0) + { + h=wordlist[0]; + out_html("<a name=\">"); + out_html(h); + out_html("\" id=\""); + out_html(h); + out_html("\"></a>"); + } + c=newc; + break; + } + case REQ_nroff: // groff(7) "NROFF mode" + mode = true; + case REQ_troff: // groff(7) "TROFF mode" + { + s_nroff = mode; + c+=j; + c = skip_till_newline(c); + } + case REQ_als: // groff(7) "ALias String" + { + /* + * Note an alias is supposed to be something like a hard link + * However to make it simplier, we only copy the string. + */ + // Be careful: unlike .rn, the destination is first, origin is second + kdDebug(7107) << "start .als" << endl; + c+=j; + const QCString name ( scan_identifier( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty destination string to alias" << endl; + break; + } + while (*c && isspace(*c) && *c!='\n') ++c; + const QCString name2 ( scan_identifier ( c ) ); + if ( name2.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty origin string to alias" << endl; + break; + } + kdDebug(7107) << "Alias " << name2 << " to " << name << endl; + c=skip_till_newline(c); + if ( name == name2 ) + { + kdDebug(7107) << "EXCEPTION: same origin and destination string to alias: " << name << endl; + break; + } + // Second parametr is origin (unlike in .rn) + QMap<QCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(name2); + if (it==s_stringDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find string to make alias: " << name2 << endl; + } + else + { + StringDefinition def=(*it); + s_stringDefinitionMap.insert(name,def); + } + kdDebug(7107) << "end .als" << endl; + break; + } + case REQ_rr: // groff(7) "Remove number Register" + { + kdDebug(7107) << "start .rr" << endl; + c += j; + const QCString name ( scan_identifier( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl; + break; + } + c = skip_till_newline( c ); + QMap <QCString, NumberDefinition>::iterator it = s_numberDefinitionMap.find( name ); + if ( it == s_numberDefinitionMap.end() ) + { + kdDebug(7107) << "EXCEPTION: trying to remove inexistant number register: " << endl; + } + else + { + s_numberDefinitionMap.remove( name ); + } + kdDebug(7107) << "end .rr" << endl; + break; + } + case REQ_rnn: // groff(7) "ReName Number register" + { + kdDebug(7107) << "start .rnn" << endl; + c+=j; + const QCString name ( scan_identifier ( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty origin to remove/rename number register" << endl; + break; + } + while (*c && isspace(*c) && *c!='\n') ++c; + const QCString name2 ( scan_identifier ( c ) ); + if ( name2.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty destination to rename number register " << endl; + break; + } + c = skip_till_newline( c ); + QMap<QCString,NumberDefinition>::iterator it=s_numberDefinitionMap.find(name); + if (it==s_numberDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find number register to rename: " << name << endl; + } + else + { + NumberDefinition def=(*it); + s_numberDefinitionMap.remove(name); // ### QT4: removeAll + s_numberDefinitionMap.insert(name2,def); + } + kdDebug(7107) << "end .rnn" << endl; + break; + } + case REQ_aln: // groff(7) "ALias Number Register" + { + /* + * Note an alias is supposed to be something like a hard link + * However to make it simplier, we only copy the string. + */ + // Be careful: unlike .rnn, the destination is first, origin is second + kdDebug(7107) << "start .aln" << endl; + c+=j; + const QCString name ( scan_identifier( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty destination number register to alias" << endl; + break; + } + while (*c && isspace(*c) && *c!='\n') ++c; + const QCString name2 ( scan_identifier( c ) ); + if ( name2.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty origin number register to alias" << endl; + break; + } + kdDebug(7107) << "Alias " << name2 << " to " << name << endl; + c = skip_till_newline( c ); + if ( name == name2 ) + { + kdDebug(7107) << "EXCEPTION: same origin and destination number register to alias: " << name << endl; + break; + } + // Second parametr is origin (unlike in .rnn) + QMap<QCString,NumberDefinition>::iterator it=s_numberDefinitionMap.find(name2); + if (it==s_numberDefinitionMap.end()) + { + kdDebug(7107) << "EXCEPTION: cannot find string to make alias: " << name2 << endl; + } + else + { + NumberDefinition def=(*it); + s_numberDefinitionMap.insert(name,def); + } + kdDebug(7107) << "end .aln" << endl; + break; + } + case REQ_shift: // groff(7) "SHIFT parameter" + { + c+=j; + h=c; + while (*h && *h!='\n' && isdigit(*h) ) ++h; + const char tempchar = *h; + *h = 0; + const QCString number = c; + *h = tempchar; + c = skip_till_newline( h ); + unsigned int result = 1; // Numbers of shifts to do + if ( !number.isEmpty() ) + { + bool ok = false; + result = number.toUInt(&ok); + if ( !ok || result < 1 ) + result = 1; + } + for ( unsigned int num = 0; num < result; ++num ) + { + if ( !s_argumentList.isEmpty() ) + s_argumentList.pop_front(); + } + break; + } + case REQ_while: // groff(7) "WHILE loop" + { + request_while( c, j, mandoc_command ); + break; + } + case REQ_do: // groff(7) "DO command" + { + // HACK: we just replace do by a \n and a . + *c = '\n'; + c++; + *c = '.'; + // The . will be treated as next character + break; + } + default: + { + if (mandoc_command && + ((isupper(*c) && islower(*(c+1))) + || (islower(*c) && isupper(*(c+1)))) ) + { + /* Let through any mdoc(7) commands that haven't + * been delt with. + * I don't want to miss anything out of the text. + */ + char buf[4] = { c[0], c[1], ' ', 0 }; + out_html(buf); /* Print the command (it might just be text). */ + c=c+j; + trans_char(c,'"','\a'); + if (*c=='\n') c++; + out_html(set_font("R")); + c=scan_troff(c, 1, NULL); + out_html(NEWLINE); + if (fillout) + curpos++; + else + curpos=0; + } + else + c=skip_till_newline(c); + break; + } + } + } + } + if (fillout) + { + out_html(NEWLINE); + curpos++; + } + return c; +} + +static int contained_tab=0; +static bool mandoc_line=false; /* Signals whether to look for embedded mandoc + * commands. + */ + +static char *scan_troff(char *c, bool san, char **result) +{ /* san : stop at newline */ + char *h; + char intbuff[NULL_TERMINATED(MED_STR_MAX)]; + int ibp=0; +#define FLUSHIBP if (ibp) { intbuff[ibp]=0; out_html(intbuff); ibp=0; } + char *exbuffer; + int exbuffpos, exbuffmax, exnewline_for_fun; + bool exscaninbuff; + int usenbsp=0; + + exbuffer=buffer; + exbuffpos=buffpos; + exbuffmax=buffmax; + exnewline_for_fun=newline_for_fun; + exscaninbuff=scaninbuff; + newline_for_fun=0; + if (result) { + if (*result) { + buffer=*result; + buffpos=qstrlen(buffer); + buffmax=buffpos; + } else { + buffer = stralloc(LARGE_STR_MAX); + buffpos=0; + buffmax=LARGE_STR_MAX; + } + scaninbuff=true; + } + h=c; // ### FIXME below are too many tests that may go before the posiiton of c + /* start scanning */ + + // ### VERIFY: a dot must be at first position, we cannot add newlines or it would allow spaces before a dot + while (*h == ' ') + { +#if 1 + ++h; +#else + *h++ = '\n'; +#endif + } + + while (h && *h && (!san || newline_for_fun || *h!='\n')) { + + if (*h==escapesym) { + h++; + FLUSHIBP; + h = scan_escape(h); + } else if (*h==controlsym && h[-1]=='\n') { + h++; + FLUSHIBP; + h = scan_request(h); + if (h && san && h[-1]=='\n') h--; + } else if (mandoc_line + && ((*(h-1)) && (isspace(*(h-1)) || (*(h-1))=='\n')) + && *(h) && isupper(*(h)) + && *(h+1) && islower(*(h+1)) + && *(h+2) && isspace(*(h+2))) { + // mdoc(7) embedded command eg ".It Fl Ar arg1 Fl Ar arg2" + FLUSHIBP; + h = scan_request(h); + if (san && h[-1]=='\n') h--; + } else if (*h==nobreaksym && h[-1]=='\n') { + h++; + FLUSHIBP; + h = scan_request(h); + if (san && h[-1]=='\n') h--; + } else { + /* int mx; */ + if (still_dd && isalnum(*h) && h[-1]=='\n') { + /* sometimes a .HP request is not followed by a .br request */ + FLUSHIBP; + out_html("<DD>"); + curpos=0; + still_dd=false; + } + switch (*h) { + case '&': + intbuff[ibp++]='&'; + intbuff[ibp++]='a'; + intbuff[ibp++]='m'; + intbuff[ibp++]='p'; + intbuff[ibp++]=';'; + curpos++; + break; + case '<': + intbuff[ibp++]='&'; + intbuff[ibp++]='l'; + intbuff[ibp++]='t'; + intbuff[ibp++]=';'; + curpos++; + break; + case '>': + intbuff[ibp++]='&'; + intbuff[ibp++]='g'; + intbuff[ibp++]='t'; + intbuff[ibp++]=';'; + curpos++; + break; + case '"': + intbuff[ibp++]='&'; + intbuff[ibp++]='q'; + intbuff[ibp++]='u'; + intbuff[ibp++]='o'; + intbuff[ibp++]='t'; + intbuff[ibp++]=';'; + curpos++; + break; + case '\n': + if (h != c && h[-1]=='\n' && fillout) { + intbuff[ibp++]='<'; + intbuff[ibp++]='P'; + intbuff[ibp++]='>'; + } + if (contained_tab && fillout) { + intbuff[ibp++]='<'; + intbuff[ibp++]='B'; + intbuff[ibp++]='R'; + intbuff[ibp++]='>'; + } + contained_tab=0; + curpos=0; + usenbsp=0; + intbuff[ibp++]='\n'; + break; + case '\t': + { + int curtab=0; + contained_tab=1; + FLUSHIBP; + /* like a typewriter, not like TeX */ + tabstops[19]=curpos+1; + while (curtab<maxtstop && tabstops[curtab]<=curpos) + curtab++; + if (curtab<maxtstop) { + if (!fillout) { + while (curpos<tabstops[curtab]) { + intbuff[ibp++]=' '; + if (ibp>480) { FLUSHIBP; } + curpos++; + } + } else { + out_html("<TT>"); + while (curpos<tabstops[curtab]) { + out_html(" "); + curpos++; + } + out_html("</TT>"); + } + } + } + break; + default: + if (*h==' ' && (h[-1]=='\n' || usenbsp)) { + FLUSHIBP; + if (!usenbsp && fillout) { + out_html("<BR>"); + curpos=0; + } + usenbsp=fillout; + if (usenbsp) out_html(" "); else intbuff[ibp++]=' '; + } else if (*h>31 && *h<127) intbuff[ibp++]=*h; + else if (((unsigned char)(*h))>127) { + intbuff[ibp++]=*h; + } + curpos++; + break; + } + if (ibp > (MED_STR_MAX - 20)) FLUSHIBP; + h++; + } + } + FLUSHIBP; + if (buffer) buffer[buffpos]='\0'; + if (san && h && *h) h++; + newline_for_fun=exnewline_for_fun; + if (result) { + *result = buffer; + buffer=exbuffer; + buffpos=exbuffpos; + buffmax=exbuffmax; + scaninbuff=exscaninbuff; + } + + return h; +} + + +static char *scan_troff_mandoc(char *c, bool san, char **result) +{ + char *ret; + char *end = c; + bool oldval = mandoc_line; + mandoc_line = true; + while (*end && *end != '\n') { + end++; + } + + if (end > c + 2 + && ispunct(*(end - 1)) + && isspace(*(end - 2)) && *(end - 2) != '\n') { + /* Don't format lonely punctuation E.g. in "xyz ," format + * the xyz and then append the comma removing the space. + */ + *(end - 2) = '\n'; + ret = scan_troff(c, san, result); + *(end - 2) = *(end - 1); + *(end - 1) = ' '; + } + else { + ret = scan_troff(c, san, result); + } + mandoc_line = oldval; + return ret; +} + +// Entry point +void scan_man_page(const char *man_page) +{ + if (!man_page) + return; + + kdDebug(7107) << "Start scanning man page" << endl; + + // ## Do more init + // Unlike man2html, we actually call this several times, hence the need to + // properly cleanup all those static vars + s_ifelseval.clear(); + + s_characterDefinitionMap.clear(); + InitCharacterDefinitions(); + + s_stringDefinitionMap.clear(); + InitStringDefinitions(); + + s_numberDefinitionMap.clear(); + InitNumberDefinitions(); + + s_argumentList.clear(); + + section = 0; + + s_dollarZero = ""; // No macro called yet! + + output_possible = false; + int strLength = qstrlen(man_page); + char *buf = new char[strLength + 2]; + qstrcpy(buf+1, man_page); + buf[0] = '\n'; + + kdDebug(7107) << "Parse man page" << endl; + + scan_troff(buf+1,0,NULL); + + kdDebug(7107) << "Man page parsed!" << endl; + + while (itemdepth || dl_set[itemdepth]) { + out_html("</DL>\n"); + if (dl_set[itemdepth]) dl_set[itemdepth]=0; + else if (itemdepth > 0) itemdepth--; + } + + out_html(set_font("R")); + out_html(change_to_size(0)); + if (!fillout) { + fillout=1; + out_html("</PRE>"); + } + out_html(NEWLINE); + + if (section) { + output_real("<div style=\"margin-left: 2cm\">\n"); + section = 0; + } + + if (output_possible) { + output_real("</div>\n"); + output_real("<div class=\"bannerBottom\" style=\"background-image: url("); + output_real(cssPath); + output_real("/bottom-middle.png); background-repeat: x-repeat; width: 100%; height: 100px; bottom:0pt;\">\n"); + output_real("<div class=\"bannerBottomLeft\">\n"); + output_real("<img src=\""); + output_real(cssPath); + output_real("/bottom-left.png\" style=\"margin: 0pt;\" alt=\"Bottom left of the banner\">\n"); + output_real("</div>\n"); + output_real("<div class=\"bannerBottomRight\">\n"); + output_real("<img src=\""); + output_real(cssPath); + output_real("/bottom-right.png\" style=\"margin: 0pt\" alt=\"Bottom right of the banner\">\n"); + output_real("</div>\n"); + output_real("</div>\n"); + + output_real("</BODY>\n</HTML>\n"); + } + delete [] buf; + + // Release memory + s_characterDefinitionMap.clear(); + s_stringDefinitionMap.clear(); + s_numberDefinitionMap.clear(); + s_argumentList.clear(); + + // reinit static variables for reuse + delete [] buffer; + buffer = 0; + + escapesym='\\'; + nobreaksym='\''; + controlsym='.'; + fieldsym=0; + padsym=0; + + buffpos=0; + buffmax=0; + scaninbuff=false; + itemdepth=0; + for (int i = 0; i < 20; i++) + dl_set[i] = 0; + still_dd=false; + for (int i = 0; i < 12; i++) + tabstops[i] = (i+1)*8; + maxtstop=12; + curpos=0; + + mandoc_name_count = 0; +} + +#ifdef SIMPLE_MAN2HTML +void output_real(const char *insert) +{ + cout << insert; +} + +char *read_man_page(const char *filename) +{ + int man_pipe = 0; + char *man_buf = NULL; + + FILE *man_stream = NULL; + struct stat stbuf; + size_t buf_size; + if (stat(filename, &stbuf) == -1) { + std::cerr << "read_man_page: can't find " << filename << endl; + return NULL; + } + if (!S_ISREG(stbuf.st_mode)) { + std::cerr << "read_man_page: no file " << filename << endl; + return NULL; + } + buf_size = stbuf.st_size; + man_buf = stralloc(buf_size+5); + man_pipe = 0; + man_stream = fopen(filename, "r"); + if (man_stream) { + man_buf[0] = '\n'; + if (fread(man_buf+1, 1, buf_size, man_stream) == buf_size) { + man_buf[buf_size] = '\n'; + man_buf[buf_size + 1] = man_buf[buf_size + 2] = '\0'; + } + else { + man_buf = NULL; + } + fclose(man_stream); + } + return man_buf; +} + +int main(int argc, char **argv) +{ + htmlPath = "."; + cssPath = "."; + if (argc < 2) { + std::cerr << "call: " << argv[0] << " <filename>\n"; + return 1; + } + if (chdir(argv[1])) { + char *buf = read_man_page(argv[1]); + if (buf) { + scan_man_page(buf); + delete [] buf; + } + } else { + DIR *dir = opendir("."); + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + cerr << "converting " << ent->d_name << endl; + char *buf = read_man_page(ent->d_name); + if (buf) { + scan_man_page(buf); + delete [] buf; + } + } + closedir(dir); + } + return 0; +} + + +#endif diff --git a/kioslave/man/man2html.h b/kioslave/man/man2html.h new file mode 100644 index 000000000..ab672c4e5 --- /dev/null +++ b/kioslave/man/man2html.h @@ -0,0 +1,34 @@ +/** + * \file man2html.h + * + * \note Despite that this file is installed publically, it should not be included + * \todo ### KDE4: make this file private + * + */ + +#include <qcstring.h> + +/** call this with the buffer you have */ +void scan_man_page(const char *man_page); + +/** + * Set the paths to KDE resources + * + * \param htmlPath Path to the KDE resources, encoded for HTML + * \param cssPath Path to the KDE resources, encoded for CSS + * \since 3.5 + * + */ +extern void setResourcePath(const QCString& _htmlPath, const QCString& _cssPath); + +/** implement this somewhere. It will be called + with HTML contents +*/ +extern void output_real(const char *insert); + +/** + * called for requested man pages. filename can be a + * relative path! Return NULL on errors. The returned + * char array is freed by man2html + */ +extern char *read_man_page(const char *filename); diff --git a/kioslave/media/Makefile.am b/kioslave/media/Makefile.am new file mode 100644 index 000000000..e727fc4a3 --- /dev/null +++ b/kioslave/media/Makefile.am @@ -0,0 +1,36 @@ +if include_media_halbackend +PROPSDLGPLUGINDIR = propsdlgplugin +endif + +SUBDIRS = libmediacommon . mediamanager medianotifier mounthelper \ + kfile-plugin kcmodule mimetypes services $(PROPSDLGPLUGINDIR) + +INCLUDES = -I$(srcdir)/libmediacommon $(all_includes) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kio_media.la + +kio_media_la_SOURCES = dummy.cpp +kio_media_la_LIBADD = libkiomedia.la libmediacommon/libmediacommon.la $(LIB_KIO) +kio_media_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined + +dummy.cpp: + echo > dummy.cpp + +kde_services_DATA = media.protocol + +noinst_LTLIBRARIES = libkiomedia.la +libkiomedia_la_SOURCES = kio_media.cpp mediaimpl.cpp mediaimpl.skel + +check_PROGRAMS = testmedia +testmedia_SOURCES = testmedia.cpp +testmedia_LDADD = libkiomedia.la $(LIB_KIO) +testmedia_LDFLAGS = $(all_libraries) + +## TODO in unsermake: TESTS = testmedia +check: testmedia + ./testmedia + +messages: rc.cpp + $(EXTRACTRC) propsdlgplugin/propertiespagegui.ui >> rc.cpp + $(XGETTEXT) *.cpp *.h kfile-plugin/*.cpp libmediacommon/*.cpp mediamanager/*.cpp mounthelper/*.cpp propsdlgplugin/*.cpp -o $(podir)/kio_media.pot diff --git a/kioslave/media/configure.in.in b/kioslave/media/configure.in.in new file mode 100644 index 000000000..81c8f2640 --- /dev/null +++ b/kioslave/media/configure.in.in @@ -0,0 +1,184 @@ +########### Check for linux/cdrom.h + +AC_CHECK_HEADER(linux/cdrom.h, +[ + AC_DEFINE_UNQUOTED([COMPILE_LINUXCDPOLLING], 1, [media linux cd polling compilation]) + LINUXCDPOLLING=yes + AC_SUBST(LINUXCDPOLLING) +]) + + +AC_ARG_WITH(hal,AC_HELP_STRING([--with-hal],[Enable HAL support [default=check]]),[hal_test="$withval"],[hal_test="yes"]) + +if test "x$hal_test" = "xyes" ; then + +########### Check for the HAL + AC_MSG_CHECKING(for the HAL) + + hal_inc=NOTFOUND + hal_lib=NOTFOUND + hal=NOTFOUND + + search_incs="$kde_includes $kde_extra_includes /usr/include /usr/include/hal /usr/local/include /usr/local/include/hal" + AC_FIND_FILE(libhal.h libhal-storage.h, $search_incs, hal_incdir) + + if test -r $hal_incdir/libhal.h && test -r $hal_incdir/libhal-storage.h && grep LibHalVolume $hal_incdir/libhal-storage.h > /dev/null 2>&1; then + HAL_INCS="-I$hal_incdir" + hal_inc=FOUND + fi + + search_libs="$kde_libraries $kde_extra_libs /usr/lib$kdelibsuff /usr/local/lib$kdelibsuff" + AC_FIND_FILE(libhal.so libhal-storage.so, $search_libs, hal_libdir) + + if test -r $hal_libdir/libhal.so && test -r $hal_libdir/libhal-storage.so ; then + HAL_LIBS="-L$hal_libdir -lhal -lhal-storage" + hal_lib=FOUND + fi + + if test "$hal_inc" != FOUND || test "$hal_lib" != FOUND; then + KDE_PKG_CHECK_MODULES( HAL, hal > 0.5, [ HAL_INCS=$HAL_CFLAGS; hal_inc=FOUND; hal_lib=FOUND; ] , AC_MSG_RESULT(Nothing found on PKG_CONFIG_PATH) ) + fi + + if test "$hal_inc" = FOUND && test "$hal_lib" = FOUND ; then + AC_MSG_RESULT(headers $HAL_INCS libraries $HAL_LIBS) + hal=FOUND + else + AC_MSG_RESULT(searched but not found) + fi + + + AC_SUBST(HAL_INCS) + AC_SUBST(HAL_LIBS) + + +########### Check for DBus + + AC_MSG_CHECKING(for DBus) + + dbus_inc=NOTFOUND + dbus_lib=NOTFOUND + dbus=NOTFOUND + + search_incs="$kde_includes $kde_extra_includes /usr/include /usr/include/dbus-1.0 /usr/local/include /usr/local/include/dbus-1.0" + AC_FIND_FILE(dbus/dbus.h, $search_incs, dbus_incdir) + + search_incs_arch_deps="$kde_includes $kde_extra_includes /usr/lib$kdelibsuff/dbus-1.0/include /usr/local/lib$kdelibsuff/dbus-1.0/include" + AC_FIND_FILE(dbus/dbus-arch-deps.h, $search_incs_arch_deps, dbus_incdir_arch_deps) + + if test -r $dbus_incdir/dbus/dbus.h && test -r $dbus_incdir_arch_deps/dbus/dbus-arch-deps.h ; then + DBUS_INCS="-I$dbus_incdir -I$dbus_incdir_arch_deps" + dbus_inc=FOUND + fi + + search_libs="$kde_libraries $kde_extra_libs /usr/lib$kdelibsuff /usr/local/lib$kdelibsuff" + AC_FIND_FILE(libdbus-1.so, $search_libs, dbus_libdir) + + if test -r $dbus_libdir/libdbus-1.so ; then + DBUS_LIBS="-L$dbus_libdir -ldbus-1" + dbus_lib=FOUND + fi + + if test $dbus_inc != FOUND || test $dbus_lib != FOUND ; then + KDE_PKG_CHECK_MODULES( DBUS, "dbus-1", [ DBUS_INCS=$DBUS_CFLAGS; dbus_inc=FOUND; dbus_lib=FOUND; ] , AC_MSG_RESULT( Nothing found on PKG_CONFIG_PATH ) ) + fi + + dbus_bus_var=`pkg-config --variable=system_bus_default_address dbus-1 2>/dev/null` + if test -z "$dbus_bus_var"; then + dbus_bus_var="unix:path=/var/run/dbus/system_bus_socket" + fi + AC_DEFINE_UNQUOTED(DBUS_SYSTEM_BUS, "$dbus_bus_var", [Define the unix domain path for dbus system bus]) + + if test $dbus_inc = FOUND && test $dbus_lib = FOUND ; then + AC_MSG_RESULT(headers $DBUS_INCS libraries $DBUS_LIBS) + dbus=FOUND + else + AC_MSG_RESULT(searched but not found) + fi + + AC_SUBST(DBUS_INCS) + AC_SUBST(DBUS_LIBS) + +########### Check for DBus-Qt3 bindings + + AC_MSG_CHECKING(for DBus-Qt3 bindings) + + dbusqt_inc=NOTFOUND + dbusqt_lib=NOTFOUND + dbusqt=NOTFOUND + + search_incs="$kde_includes $kde_extra_includes /usr/include /usr/include/dbus-1.0 /usr/local/include /usr/local/include/dbus-1.0" + AC_FIND_FILE(dbus/connection.h, $search_incs, dbusqt_incdir) + + if test -r $dbusqt_incdir/dbus/connection.h ; then + have_qt_patch=0 + grep dbus_connection_setup_with_qt_main $dbusqt_incdir/dbus/connection.h \ + > /dev/null 2>&1 && have_qt_patch=1 + if test $have_qt_patch = 1 ; then + DBUSQT_INCS="-I$dbusqt_incdir" + dbusqt_inc=FOUND + fi + fi + + search_libs="$kde_libraries $kde_extra_libs /usr/lib$kdelibsuff /usr/local/lib$kdelibsuff" + AC_FIND_FILE(libdbus-qt-1.so, $search_libs, dbusqt_libdir) + + if test -r $dbusqt_libdir/libdbus-qt-1.so ; then + DBUSQT_LIBS="-L$dbusqt_libdir -ldbus-qt-1" + dbusqt_lib=FOUND + fi + + if test $dbusqt_inc != FOUND || test $dbusqt_lib != FOUND ; then + + search_incs="`pkg-config --cflags dbus-1 |sed 's/-I//g'`" + AC_FIND_FILE(dbus/connection.h, $search_incs, dbusqt_incdir) + if test -r $dbusqt_incdir/dbus/connection.h ; then + have_qt_patch=0 + grep dbus_connection_setup_with_qt_main $dbusqt_incdir/dbus/connection.h \ + > /dev/null 2>&1 && have_qt_patch=1 + if test $have_qt_patch = 1 ; then + DBUSQT_INCS="-I$dbusqt_incdir" + dbusqt_inc=FOUND + fi + fi + + search_libs="`pkg-config --libs dbus-1 --libs-only-L | sed 's/-L//g'`" + AC_FIND_FILE(libdbus-qt-1.so, $search_libs, dbusqt_libdir) + + if test -r $dbusqt_libdir/libdbus-qt-1.so ; then + DBUSQT_LIBS="-L$dbusqt_libdir -ldbus-qt-1" + dbusqt_lib=FOUND + fi + + fi + + + if test $dbusqt_inc = FOUND && test $dbusqt_lib = FOUND ; then + AC_MSG_RESULT(headers $dbusqt_incdir libraries $dbusqt_libdir) + dbusqt=FOUND + else + AC_MSG_RESULT(searched but not found) + fi + + AC_SUBST(DBUSQT_INCS) + AC_SUBST(DBUSQT_LIBS) +fi + +########### Check if media HAL backend sould be compiled + +AC_MSG_CHECKING(if the HAL backend for media:/ should be compiled) + +HALBACKEND=no +if test "x$hal" = "xFOUND" && test "x$dbus" = "xFOUND" && test "x$dbusqt" = "xFOUND" ; then + AC_DEFINE_UNQUOTED([COMPILE_HALBACKEND],1, [media HAL backend compilation]) + HALBACKEND=yes + AC_SUBST(HALBACKEND) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +AM_CONDITIONAL(include_media_linuxcdpolling, test "$LINUXCDPOLLING" = "yes") +AM_CONDITIONAL(include_media_halbackend, test "$HALBACKEND" = yes) + +AC_CHECK_FUNCS(statvfs) + diff --git a/kioslave/media/contrib/README b/kioslave/media/contrib/README new file mode 100644 index 000000000..8f6041578 --- /dev/null +++ b/kioslave/media/contrib/README @@ -0,0 +1,18 @@ +Those scripts are an example on how to allow media:/ to use +hotplugging events if you don't use HAL. They're targeting +"Linux 2.6 + hotplug + udev" platforms, but we can surely make +something equivalent for Linux 2.4, FreeBSD... + +They are just examples, the packagers will surely develop their +own version (I'm currently using them on Debian Sid, Linux 2.6.9 +though). + +1) mediamanager_usbstorage.dev +This file only needs to be copied to /etc/dev.d/default + +2) usbcam +This one is a bit more complicated to install. +The file must be copied into /etc/hotplug/usb and the file +/etc/hotplug/usb/usbcam.usermap must exists (you can use +libgphoto2's print-usb-usermap command to create it) + diff --git a/kioslave/media/contrib/mediamanager_usbstorage.dev b/kioslave/media/contrib/mediamanager_usbstorage.dev new file mode 100755 index 000000000..d12a3e687 --- /dev/null +++ b/kioslave/media/contrib/mediamanager_usbstorage.dev @@ -0,0 +1,108 @@ +#!/bin/sh +# /etc/dev.d/default/mediamanager_usbstorage.dev +# Notify all KDE sessions (thanks to the mediamanager) that a new +# usb_storage device appeared or disappeared +# + +# to debug this script, uncomment the next line and see /tmp/mediamanager_usbstorage.debug after execution +#DEBUG=1 + +# exit immediately if /usr/bin/ is not yet available (during boot if /usr is a separate partition) +/bin/ls -d /usr/bin/ >/dev/null 2>&1 || exit + +DEBUGOUT=/tmp/mediamanager_usbstorage.debug.$$ +if [ "$DEBUG" = "1" -a -z "$2" ]; then + echo "executing $0 $@" > $DEBUGOUT + echo "with the following environment variables:" >> $DEBUGOUT + env >> $DEBUGOUT + echo "----" >> $DEBUGOUT + sh -x $0 $@ debug >> $DEBUGOUT 2>&1 + exit +fi + +# we only manage block devices +if [ "$1" != "block" ]; then exit; fi + +# we only manage usb_storage devices +if [ "$ACTION" = "add" ]; then + device="`ls /sys$DEVPATH/../device/../../../ 2> /dev/NULL | grep ':' | head -1`" + if [ -z "$device" -o ! -e /sys/bus/usb/drivers/usb-storage/$device ]; then + # The behavior is not the same for every kernel it seems. + # Testing the driver/ directory just in case. + device="`ls /sys$DEVPATH/../device/../../../driver 2> /dev/NULL | grep ':' | head -1`" + if [ -z "$device" -o ! -e /sys/bus/usb/drivers/usb-storage/$device ]; then + exit + fi + fi +fi + +# functions for syslog +LOGGER="logger -t `basename $0`[$$] -p user.notice" +write_syslog () { + echo ${@} | $LOGGER +} + +# be sure the drivers are loaded +/sbin/modprobe -q usb_storage +/sbin/modprobe -q vfat + +# create the FSH required /media directory +# See: http://www.pathname.com/fhs/pub/fhs-2.3.html#MEDIAMOUNTPOINT +MNT=media +if [ ! -d /$MNT ]; then + mkdir /$MNT + write_syslog "Created the /$MNT directory" +fi + +# we need DEVPATH, DEVNAME and ACTION, so we warn the user that executes this script by hand +if [ -z "$DEVPATH" -a -z "$DEVNAME" -a -z "$ACTION" ]; then + echo + echo "This script must be called by udevd because it needs the following environment variables: DEVPATH, DEVNAME, ACTION" + echo "So you must copy this script as /etc/dev.d/default/updfstab-2.6.dev and set it executable" + echo "See: http://www.kernel.org/pub/linux/utils/kernel/hotplug/RFC-dev.d" + echo + exit +fi + +# if $DEVPATH/device exists, we are a device, not a partition, so exit +if [ -d /sys${DEVPATH}/device ]; then exit; fi + +dcop_users="`ps aux | grep dcopserver | grep -v grep | awk '{print $1}' | sort | uniq`" + +# if the current device is being added +if [ "$ACTION" = "add" ]; then + # get partition information + partition="/sys${DEVPATH}/../device/../../.." + # We check twice again... marvelous random kernel behaviour changes... + if [ -e $partition/product ]; then + product="`cat $partition/product`" + else + product="`cat $partition/../product`" + fi + if [ -e $partition/manufacturer ]; then + manufacturer="`cat $partition/manufacturer`" + else + manufacturer="`cat $partition/../manufacturer`" + fi + + write_syslog "Invoking dcop..." + write_syslog "kded mediamanager removablePlug $DEVNAME \"$manufacturer $product\"" + + method="kded mediamanager removablePlug" + for user in $dcop_users ; do + dcop --user $user --all-sessions $method $DEVNAME "$manufacturer $product" + done + +elif [ "$ACTION" = "remove" ]; then + write_syslog "Invoking dcop..." + write_syslog "kded mediamanager removableUnplug $DEVNAME" + + method="kded mediamanager removableUnplug" + for user in $dcop_users ; do + dcop --user $user --all-sessions $method $DEVNAME + done + + umount $DEVNAME +fi + + diff --git a/kioslave/media/contrib/usbcam b/kioslave/media/contrib/usbcam new file mode 100755 index 000000000..85158d96b --- /dev/null +++ b/kioslave/media/contrib/usbcam @@ -0,0 +1,82 @@ +#!/bin/sh +# +# /etc/hotplug/usb/usbcam +# +# Set up newly plugged in USB camera +# Notify all KDE sessions (thanks to the mediamanager) that a +# new camera appeared or disappeared + +# to debug this script, uncomment the next line and see /tmp/usbcam.debug after execution +#DEBUG=1 + +# exit immediately if /usr/bin/ is not yet available (during boot if /usr is a separate partition) +/bin/ls -d /usr/bin/ >/dev/null 2>&1 || exit + +GROUP=camera + +if [ "${ACTION}" = "add" ] && [ -f "${DEVICE}" ] +then + chmod a-rwx "${DEVICE}" + chgrp "${GROUP}" "${DEVICE}" + chmod ug+rw "${DEVICE}" +fi + + +DEBUGOUT=/tmp/usbcam.debug.$$ +if [ "$DEBUG" = "1" -a -z "$2" ]; then + echo "executing $0 $@" > $DEBUGOUT + echo "with the following environment variables:" >> $DEBUGOUT + env >> $DEBUGOUT + echo "----" >> $DEBUGOUT + sh -x $0 $@ debug >> $DEBUGOUT 2>&1 + exit +fi + +# functions for syslog +LOGGER="logger -t `basename $0`[$$] -p user.notice" +write_syslog () { + echo ${@} | $LOGGER +} + +if [ -z "$REMOVER" ]; then + write_syslog "No remover found" + exit +fi + +dcop_users="`ps aux | grep dcopserver | grep -v grep | awk '{print $1}' | sort | uniq`" + +# if the current device is being added +if [ "$ACTION" = "add" ]; then + write_syslog "Copying remover..." + cp /etc/hotplug/usb/usbcam $REMOVER + chmod +x $REMOVER + # get camera information + camera="/sys${DEVPATH}/.." + if [ -e $camera/product ]; then product="`cat $camera/product`"; fi + if [ -e $camera/manufacturer ]; then manufacturer="`cat $camera/manufacturer`"; fi + + write_syslog "Invoking dcop..." + write_syslog "kded mediamanager removableCamera $DEVICE \"$manufacturer $product\"" + + method="kded mediamanager removablePlug" + for user in $dcop_users ; do + dcop --user $user --all-sessions $method $DEVICE "$manufacturer $product" + done + + method="kded mediamanager removableCamera" + for user in $dcop_users ; do + dcop --user $user --all-sessions $method $DEVICE + done + +elif [ "$ACTION" = "remove" ]; then + write_syslog "Invoking dcop..." + write_syslog "kded mediamanager removableUnplug $DEVICE" + + method="kded mediamanager removableUnplug" + for user in $dcop_users ; do + dcop --user $user --all-sessions $method $DEVICE + done + +fi + + diff --git a/kioslave/media/dummy.cpp b/kioslave/media/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/kioslave/media/dummy.cpp @@ -0,0 +1 @@ + diff --git a/kioslave/media/kcmodule/Makefile.am b/kioslave/media/kcmodule/Makefile.am new file mode 100644 index 000000000..72f9f385c --- /dev/null +++ b/kioslave/media/kcmodule/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES = -I$(srcdir)/../libmediacommon -I../libmediacommon $(all_includes) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kcm_media.la +kcm_media_la_SOURCES = notifiermodule.cpp notifiermoduleview.ui \ + serviceconfigdialog.cpp serviceview.ui \ + mimetypelistboxitem.cpp \ + managermodule.cpp managermoduleview.ui \ + main.cpp + +kcm_media_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) +kcm_media_la_LIBADD = ../libmediacommon/libmediacommon.la $(LIB_KIO) + +noinst_HEADERS = notifiermodule.h managermodule.h \ + serviceconfigdialog.h mimetypelistboxitem.h \ + main.h + +xdg_apps_DATA = media.desktop + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kcmmedia.pot diff --git a/kioslave/media/kcmodule/main.cpp b/kioslave/media/kcmodule/main.cpp new file mode 100644 index 000000000..e431e95f8 --- /dev/null +++ b/kioslave/media/kcmodule/main.cpp @@ -0,0 +1,103 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "main.h" + +#include <qtabwidget.h> +#include <qlayout.h> + +#include <klocale.h> +#include <kaboutdata.h> +#include <kdialog.h> + +#include <kgenericfactory.h> + +#include "notifiermodule.h" +#include "managermodule.h" + + +typedef KGenericFactory<MediaModule, QWidget> MediaFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_media, MediaFactory( "kcmmedia" ) ) + + +MediaModule::MediaModule( QWidget *parent, const char *name, const QStringList& ) + : KCModule(MediaFactory::instance(), parent, name ) +{ + KGlobal::locale()->insertCatalogue("kio_media"); + QVBoxLayout *layout = new QVBoxLayout( this, 0, KDialog::spacingHint() ); + QTabWidget *tab = new QTabWidget( this ); + + layout->addWidget( tab ); + + + + m_notifierModule = new NotifierModule( this, "notifier" ); + tab->addTab( m_notifierModule, i18n( "&Notifications" ) ); + connect( m_notifierModule, SIGNAL( changed( bool ) ), + this, SLOT( moduleChanged( bool ) ) ); + + m_managerModule = new ManagerModule( this, "manager" ); + tab->addTab( m_managerModule, i18n( "&Advanced" ) ); + connect( m_managerModule, SIGNAL( changed( bool ) ), + this, SLOT( moduleChanged( bool ) ) ); + + + + KAboutData * about = new KAboutData("kcmmedia", + I18N_NOOP("Storage Media"), + "0.6", + I18N_NOOP("Storage Media Control Panel Module"), + KAboutData::License_GPL_V2, + I18N_NOOP("(c) 2005 Jean-Remy Falleri")); + about->addAuthor("Jean-Remy Falleri", I18N_NOOP("Maintainer"), "jr.falleri@laposte.net"); + about->addAuthor("Kevin Ottens", 0, "ervin ipsquad net"); + about->addCredit("Achim Bohnet", I18N_NOOP("Help for the application design")); + + setAboutData( about ); +} + +void MediaModule::load() +{ + m_notifierModule->load(); + m_managerModule->load(); +} + +void MediaModule::save() +{ + m_notifierModule->save(); + m_managerModule->save(); +} + +void MediaModule::defaults() +{ + m_notifierModule->defaults(); + m_managerModule->defaults(); +} + +void MediaModule::moduleChanged( bool state ) +{ + emit changed( state ); +} + +QString MediaModule::quickHelp() const +{ + return i18n("FIXME : Write me..."); +} + +#include "main.moc" diff --git a/kioslave/media/kcmodule/main.h b/kioslave/media/kcmodule/main.h new file mode 100644 index 000000000..1ab909527 --- /dev/null +++ b/kioslave/media/kcmodule/main.h @@ -0,0 +1,45 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MAIN_H_ +#define _MAIN_H_ + +#include <kcmodule.h> + +class MediaModule : public KCModule +{ + Q_OBJECT + +public: + MediaModule( QWidget *parent, const char *name, const QStringList& ); + + virtual void load(); + virtual void save(); + virtual void defaults(); + virtual QString quickHelp() const; + +protected slots: + void moduleChanged( bool state ); + +private: + KCModule *m_notifierModule; + KCModule *m_managerModule; +}; + +#endif diff --git a/kioslave/media/kcmodule/managermodule.cpp b/kioslave/media/kcmodule/managermodule.cpp new file mode 100644 index 000000000..e5f493d27 --- /dev/null +++ b/kioslave/media/kcmodule/managermodule.cpp @@ -0,0 +1,74 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 <config.h> + +#include "managermodule.h" + +#include <klocale.h> +#include <dcopref.h> +#include <qcheckbox.h> +#include <kdirnotify_stub.h> + +#include "managermoduleview.h" +#include "mediamanagersettings.h" + +ManagerModule::ManagerModule( QWidget* parent, const char* name ) + : KCModule( parent, name ) +{ + ManagerModuleView *view = new ManagerModuleView( this ); + + addConfig( MediaManagerSettings::self(), view ); + +#ifndef COMPILE_HALBACKEND + QString hal_text = view->kcfg_HalBackendEnabled->text(); + hal_text += " ("+i18n("No support for HAL on this system")+")"; + view->kcfg_HalBackendEnabled->setText( hal_text ); +#endif + view->kcfg_HalBackendEnabled->setEnabled( false ); + +#ifndef COMPILE_LINUXCDPOLLING + QString poll_text = view->kcfg_CdPollingEnabled->text(); + poll_text += " ("+i18n("No support for CD polling on this system")+")"; + view->kcfg_CdPollingEnabled->setText( poll_text ); +#endif + view->kcfg_CdPollingEnabled->setEnabled( false ); + + load(); +} + +void ManagerModule::save() +{ + KCModule::save(); + + //Well... reloadBackends is buggy with HAL, it seems to be linked + //to a bug in the unmaintained Qt3 DBUS binding ;-/ + //DCOPRef mediamanager( "kded", "mediamanager" ); + //DCOPReply reply = mediamanager.call( "reloadBackends" ); + + // So we use this hack instead... + DCOPRef kded( "kded", "kded" ); + kded.call( "unloadModule", "mediamanager" ); + kded.call( "loadModule", "mediamanager" ); + + KDirNotify_stub notifier( "*", "*" ); + notifier.FilesAdded( "media:/" ); +} + + +#include "managermodule.moc" diff --git a/kioslave/media/kcmodule/managermodule.h b/kioslave/media/kcmodule/managermodule.h new file mode 100644 index 000000000..7fa3d649a --- /dev/null +++ b/kioslave/media/kcmodule/managermodule.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MANAGERMODULE_H_ +#define _MANAGERMODULE_H_ + +#include <kcmodule.h> + +class ManagerModule : public KCModule +{ + Q_OBJECT + +public: + ManagerModule( QWidget* parent = 0, const char* name = 0); + + void save(); +}; + +#endif diff --git a/kioslave/media/kcmodule/managermoduleview.ui b/kioslave/media/kcmodule/managermoduleview.ui new file mode 100644 index 000000000..598718389 --- /dev/null +++ b/kioslave/media/kcmodule/managermoduleview.ui @@ -0,0 +1,72 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>ManagerModuleView</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ManagerModuleView</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>480</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_HalBackendEnabled</cstring> + </property> + <property name="text"> + <string>Enable HAL backend</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Select this if you want to enable the Hardware Abstraction Layer (http://hal.freedesktop.org/wiki/Software/hal) support.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_CdPollingEnabled</cstring> + </property> + <property name="text"> + <string>Enable CD polling</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Select this to enable the CD polling.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>kcfg_AutostartEnabled</cstring> + </property> + <property name="text"> + <string>Enable medium application autostart after mount</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Select this if you want to enable application autostart after mounting a device.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>360</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kioslave/media/kcmodule/media.desktop b/kioslave/media/kcmodule/media.desktop new file mode 100644 index 000000000..c49945253 --- /dev/null +++ b/kioslave/media/kcmodule/media.desktop @@ -0,0 +1,197 @@ +[Desktop Entry] +Type=Application +#DocPath= +Icon=system +Exec=kcmshell media + + +X-KDE-Library=media +X-KDE-FactoryName=media +X-KDE-ParentApp=kcontrol + +Name=Storage Media +Name[af]=Stoor Media +Name[ar]=وسائط التخزين +Name[be]=ÐоÑьбіты +Name[bg]=СъхранÑващи уÑтройÑтва +Name[bn]=সà§à¦Ÿà§‹à¦°à§‡à¦œ মিডিয়া +Name[bs]=UreÄ‘aji za smjeÅ¡taj podataka +Name[ca]=Suports d'emmagatzematge +Name[cs]=Úložná zaÅ™Ãzenà +Name[csb]=Zôpisowné media +Name[da]=Opbevaringsmedie +Name[de]=Speichermedien +Name[el]=ΣυσκευÎÏ‚ αποθήκευσης +Name[eo]=Enmemoriga Medio +Name[es]=Dispositivos de almacenamiento +Name[et]=Andmekandjad +Name[eu]=Biltegiratze-euskarria +Name[fa]=رسانۀ ذخیره‌گاه +Name[fi]=Tallennusmedia +Name[fr]=Support de stockage +Name[fy]=Opslachapparaten +Name[ga]=Meán Stórais +Name[gl]=Medios de armacenaxe +Name[he]=×”×ª×§× ×™× +Name[hi]=à¤à¤‚डार मीडिया +Name[hr]=Mediji za pohranjivanje +Name[hu]=Tárolóeszközök +Name[is]=Geymslumiðlar +Name[it]=Dispositivi di archiviazione +Name[ja]=記憶メディア +Name[ka]=მáƒáƒœáƒáƒªáƒ”მთრშენáƒáƒ®áƒ•áƒ˜áƒ¡ მáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘ები +Name[kk]=Жинақтаушы құрылғылар +Name[km]=ឧបករណáŸâ€‹áž•áŸ’ទុក +Name[lt]=Saugojimo įrenginiai +Name[lv]=Datu nesÄ“js +Name[mk]=Медиуми за податоци +Name[ms]=Media Storan +Name[nb]=Lagringsenheter +Name[nds]=Spiekermedien +Name[ne]=à¤à¤£à¥à¤¡à¤¾à¤°à¤£ मिडिया +Name[nl]=Opslagapparaten +Name[nn]=Lagringsmedium +Name[pa]=ਸਟੋਰੇਜ਼ ਮੀਡਿਆ +Name[pl]=UrzÄ…dzenia przechowywania danych +Name[pt]=Dispositivos de Armazenamento +Name[pt_BR]=MÃdia de Armazenamento +Name[ro]=Mediu de stocare +Name[ru]=УÑтройÑтва Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… +Name[rw]=Uburyo bwo Kubika +Name[se]=Vurkenmedia +Name[sk]=Zálohovacie médiá +Name[sl]=Nosilci za shranjevanje +Name[sr]=Складишни медијуми +Name[sr@Latn]=SkladiÅ¡ni medijumi +Name[sv]=Lagringsmedia +Name[ta]=சேகரிபà¯à®ªà¯ ஊடகம௠+Name[tg]=Захирагоҳи маълумот +Name[th]=สื่à¸à¹€à¸à¹‡à¸šà¸‚้à¸à¸¡à¸¹à¸¥ +Name[tr]=Depolama Ortamı +Name[tt]=Saqlawlı Media +Name[uk]=ПриÑтрої Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— +Name[uz]=Saqlash uskunalari +Name[uz@cyrillic]=Сақлаш уÑкуналари +Name[vi]=á»” chứa Dữ liệu +Name[wa]=Sopoirts di wÃ¥rdaedje +Name[zh_CN]=å˜å‚¨ä»‹è´¨ +Name[zh_TW]=儲å˜åª’é«” + +Comment=Configure Storage Media +Comment[af]=Stel Stoor Media op +Comment[ar]=إعداد وسائط التخزين +Comment[be]=ÐаÑтаўленні ноÑьбітаў +Comment[bg]=ÐаÑтройване на уÑтройÑтвата за Ñъхранение на Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ +Comment[bn]=সà§à¦Ÿà§‹à¦°à§‡à¦œ মিডিয়া কনফিগার করà§à¦¨ +Comment[bs]=Podesite prikaz ureÄ‘aja +Comment[ca]=Configura els suports d'emmagatzematge +Comment[cs]=Nastavenà úložných médià +Comment[csb]=Kònfigùracëjô zôpisownëch mediów +Comment[da]=Opsætning af opbevarelsesmedie +Comment[de]=Speichermedien einrichten +Comment[el]=ΡÏθμιση μÎσων αποθήκευσης +Comment[eo]=Agordo de enmemoriga medio +Comment[es]=Configuración de las medios de almacenamiento +Comment[et]=Salvestusandmekandjate seadistused +Comment[eu]=Konfiguratu biltegiratze-euskarria +Comment[fa]=پیکربندی رسانۀ ذخیره‌گاه +Comment[fi]=Aseta tallennusmedia +Comment[fr]=Configurer le média de stockage +Comment[fy]=Opslachmedia ynstelle +Comment[ga]=Cumraigh Meáin Stórála +Comment[gl]=Configurar Medios de Armacenaxe +Comment[he]=×©×™× ×•×™ הגדרות מדיות ×חסון +Comment[hr]=Konfiguriranje medija za pohranu +Comment[hu]=A tárolóeszközök beállÃtása +Comment[is]=Stillingar geymslumiðla +Comment[it]=Configura dispositivi di archiviazione +Comment[ja]=記憶メディアã®è¨å®š +Comment[ka]=შენáƒáƒ®áƒ•áƒ˜áƒ¡ მედიის გáƒáƒ›áƒáƒ თვრ+Comment[kk]=Жинақтаушыларды баптау +Comment[km]=កំណážáŸ‹â€‹ážšáž…នាសម្ពáŸáž“្ធ​ឧបករណáŸâ€‹áž•áŸ’ទុក +Comment[lt]=KonfigÅ«ruoti saugojimo įrenginius +Comment[mk]=Конфигурирајте ги медиумите за податоци +Comment[nb]=Sett opp lagringsmedier +Comment[nds]=Spiekermedien instellen +Comment[ne]=à¤à¤£à¥à¤¡à¤¾à¤°à¤£ मिडिया कनà¥à¤«à¤¿à¤—र गरà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Opslagmedia instellen +Comment[nn]=Set opp lagringsmedium +Comment[pa]=ਸਟੋਰੇਜ਼ ਮੀਡਿਆ ਸੰਰਚਨਾ +Comment[pl]=Konfiguracja noÅ›ników danych +Comment[pt]=Configurar os Suportes de Armazenamento +Comment[pt_BR]=Configura as mÃdias de armazenamento +Comment[ro]=Configurează mediile de stocare +Comment[ru]=ÐаÑтройка подключаемых уÑтройÑтв Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ +Comment[se]=Heivet vurkenmediaid +Comment[sk]=Nastavenie zálohovacÃch médià +Comment[sl]=Nastavitve nosilcev za shranjevanje +Comment[sr]=Подешавање медијума за Ñкладиштење +Comment[sr@Latn]=PodeÅ¡avanje medijuma za skladiÅ¡tenje +Comment[sv]=Anpassa lagringsmedia +Comment[th]=ตั้งค่าสื่à¸à¸šà¸±à¸™à¸—ึภ+Comment[tr]=Depolama Aygıtlarını Yapılandır +Comment[tt]=Saqlaw Cıhazların Caylaw +Comment[uk]=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ñтроїв Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— +Comment[uz]=Saqlash uskunalarni moslash +Comment[uz@cyrillic]=Сақлаш уÑкуналарни моÑлаш +Comment[vi]=Cấu hình á»” lÆ°u trữ Dữ liệu +Comment[wa]=Apontiaedjes sopoirts di wÃ¥rdaedje +Comment[zh_CN]=é…ç½®å˜å‚¨ä»‹è´¨ +Comment[zh_TW]=è¨å®šå„²å˜åª’é«” + +Keywords=storage,media,usb,cdrom,device +Keywords[ar]=تخزين,وسائط,usb,قرص مدمج cdrom,جهاز +Keywords[be]=ÐоÑьбіт,Прылада,storage,media,usb,cdrom,device +Keywords[bg]=Ñъхранение, информациÑ, компактдиÑк, уÑтройÑтво, данни, storage, media, usb, cdrom, device +Keywords[bs]=storage,media,usb,cdrom,device,ureÄ‘aji,mediji +Keywords[ca]=emmagatzematge,suport,usb,cdrom,dispositiu +Keywords[cs]=úložiÅ¡tÄ›,média,USB,CDROM,zaÅ™Ãzenà +Keywords[csb]=pòdôwczi,trzëmanié pòdôwków,zôpisowné media,usb,cdrom,ùrzÄ…dzenié,nëk +Keywords[da]=opbevaring,medie,usb,cdrom,enhed +Keywords[de]=Speicher,Medien,Medium,USB,CD-Rom,cdrom,Gerät +Keywords[el]=αποθήκευση,μÎσο,usb,cdrom,συσκευή +Keywords[eo]=memorigilo,medio,usb,lumdisko,aparato +Keywords[es]=almacenamiento,medios,usb,cdrom,dispositivo +Keywords[et]=salvestamine,andmekandja,usb,cd,seade +Keywords[eu]=biltegiratzea,euskarriak,usb,cdrom,gailua +Keywords[fa]=ذخیره‌گاه، رسانه، گذرگاه سریال جهانی، دیسک Ùشرده، دستگاه +Keywords[fi]=varasto,media,usb,cdrom,laite +Keywords[fr]=stockage,media,média,medium,usb,cdrom,périphérique +Keywords[fy]=storage,opslach,media,usb,cd-rom,kompakt-skiif,device,apparaat,mp3-speler,usb-stick,geheugenkaart +Keywords[ga]=stóráil,stóras,meáin,meán,usb,cdrom,dlúthdhiosca,gléas +Keywords[gl]=armacenaxe,medios,usb,cdrom,dispositivo +Keywords[he]=storage,media,usb,cdrom,device,מדיה,התקן,סידירו×,תקליטור +Keywords[hr]=storage,media,usb,cdrom,device,pohrana,snimanje,mediji,ureÄ‘aj +Keywords[hu]=tároló,adathordozó,USB,CD-ROM,eszköz +Keywords[is]=geymsla,miðill,usb,cdrom,tæki +Keywords[it]=storage,media,usb,cdrom,dispositivi,dvd,penna usb,memory stick,stick +Keywords[ja]=記憶,メディア,usb,cdrom,デãƒã‚¤ã‚¹ +Keywords[km]=ឧបករណáŸâ€‹áž•áŸ’ទុក, usb,ស៊ីឌីរ៉ូម,ឧបករណ០+Keywords[lt]=storage,media,usb,cdrom,device,saugojimas,media,įrenginiai,usb +Keywords[mk]=storage,media,usb,cdrom,device,медиуми,уÑб,цдром,уред +Keywords[nb]=lagring,media,usb,CD-spiller,CD-ROM,enheter,minnepinner,harddisker,HD,eksterne harddisker,zip-disker +Keywords[nds]=Spieker,Medien,USB,CDROM,Reedschap +Keywords[ne]=à¤à¤£à¥à¤¡à¤¾à¤°à¤£, मिडिया,usb,cdrom, यनà¥à¤¤à¥à¤° +Keywords[nl]=storage,opslag,media,usb,cd-rom,device,apparaat,mp3-speler,usb-stick,geheugenkaart +Keywords[nn]=lagring,media,usb,CD-spiller,CD-ROM,einingar,minnepinnar,harddiskar,HD,eksterne harddiskar,zip-diskar +Keywords[pa]=ਸਟੋਰੇਜ਼,ਮੀਡਿਆ,ਜੰਤਰ,usb,cdrom +Keywords[pl]=dane,przechowywanie danych,noÅ›nik danych,noÅ›niki danych,usb,cdrom,urzÄ…dzenie +Keywords[pt]=armazenamento,discos,usb,cdrom,dispositivo +Keywords[pt_BR]=armazenamento,mÃdia,usb,cd-rom,device,dispositivo +Keywords[ro]=stocare,mediu,usb,cdrom,dispozitiv +Keywords[ru]=storage,media,usb,cdrom,device,уÑтройÑтво Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ +Keywords[sl]=shranjevanje,nosilec,usb,cdrom,naprava +Keywords[sr]=storage,media,usb,cdrom,device,Ñкладиштење,медијум,уређај +Keywords[sr@Latn]=storage,media,usb,cdrom,device,skladiÅ¡tenje,medijum,ureÄ‘aj +Keywords[sv]=lagring,media,usb,cdrom,enhet +Keywords[th]=ที่เà¸à¹‡à¸šà¸‚้à¸à¸¡à¸¹à¸¥,สื่à¸,ยูเà¸à¸ªà¸šà¸µ,,ซีดีรà¸à¸¡,à¸à¸¸à¸›à¸à¸£à¸“์ +Keywords[tr]=depolama,ortam,usb,cdrom,device +Keywords[uk]=зберіганнÑ,ноÑій,медіа,usb,cdrom,приÑтрій +Keywords[uz]=saqlash uskunasi,usb,cdrom,kompakt-disk +Keywords[uz@cyrillic]=Ñақлаш уÑкунаÑи,usb,cdrom,компакт-диÑк +Keywords[vi]=lÆ°u trữ,ổ,usb,cdrom,thiết bị +Keywords[wa]=wÃ¥rdaedje,media,usb,cdrom,device,éndjin +Keywords[zh_CN]=storage,media,usb,cdrom,device,å˜å‚¨,介质,设备 +Keywords[zh_TW]=storage,media,usb,cdrom,device,儲å˜,媒體,光碟機,è£ç½® + +Categories=Qt;KDE;X-KDE-settings-peripherals; diff --git a/kioslave/media/kcmodule/mimetypelistboxitem.cpp b/kioslave/media/kcmodule/mimetypelistboxitem.cpp new file mode 100644 index 000000000..86460ed88 --- /dev/null +++ b/kioslave/media/kcmodule/mimetypelistboxitem.cpp @@ -0,0 +1,35 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "mimetypelistboxitem.h" + +#include <kmimetype.h> + +MimetypeListBoxItem::MimetypeListBoxItem(const QString &mimetype, QListBox *parent) + : QListBoxText(parent), m_mimetype(mimetype) +{ + KMimeType::Ptr mime = KMimeType::mimeType( mimetype ); + setText( mime->comment() ); +} + +const QString &MimetypeListBoxItem::mimetype() const +{ + return m_mimetype; +} + diff --git a/kioslave/media/kcmodule/mimetypelistboxitem.h b/kioslave/media/kcmodule/mimetypelistboxitem.h new file mode 100644 index 000000000..fee215ecf --- /dev/null +++ b/kioslave/media/kcmodule/mimetypelistboxitem.h @@ -0,0 +1,37 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MIMETYPELISTBOXITEM_H_ +#define _MIMETYPELISTBOXITEM_H_ + +#include <qlistbox.h> +#include <qstring.h> + +class MimetypeListBoxItem : public QListBoxText +{ +public: + MimetypeListBoxItem(const QString &mimetype, QListBox *parent); + + const QString &mimetype() const; + +private: + QString m_mimetype; +}; + +#endif diff --git a/kioslave/media/kcmodule/notifiermodule.cpp b/kioslave/media/kcmodule/notifiermodule.cpp new file mode 100644 index 000000000..015d7396a --- /dev/null +++ b/kioslave/media/kcmodule/notifiermodule.cpp @@ -0,0 +1,230 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "notifiermodule.h" + +#include <klocale.h> + +#include <qlayout.h> +#include <kcombobox.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "notifiersettings.h" +#include "serviceconfigdialog.h" +#include "actionlistboxitem.h" +#include "mimetypelistboxitem.h" + +NotifierModule::NotifierModule(QWidget *parent, const char *name) + : KCModule(parent, name) +{ + QBoxLayout *layout = new QVBoxLayout( this, 0, KDialog::spacingHint() ); + + m_view = new NotifierModuleView( this ); + layout->addWidget( m_view ); + + m_view->addButton->setGuiItem( KStdGuiItem::add() ); + m_view->editButton->setGuiItem( KStdGuiItem::properties() ); + m_view->deleteButton->setGuiItem( KStdGuiItem::del() ); + + m_view->mimetypesCombo->insertItem( i18n("All Mime Types") ); + + QStringList mimetypes = m_settings.supportedMimetypes(); + + QStringList::iterator it = mimetypes.begin(); + QStringList::iterator end = mimetypes.end(); + + for ( ; it!=end; ++it ) + { + new MimetypeListBoxItem( *it, m_view->mimetypesCombo->listBox() ); + } + + updateListBox(); + + connect( m_view->mimetypesCombo, SIGNAL( activated(int) ), + this, SLOT( slotMimeTypeChanged(int) ) ); + connect( m_view->actionsList, SIGNAL( selectionChanged(QListBoxItem*) ), + this, SLOT( slotActionSelected(QListBoxItem*) ) ); + connect( m_view->addButton, SIGNAL( clicked() ), + this, SLOT( slotAdd() ) ); + connect( m_view->editButton, SIGNAL( clicked() ), + this, SLOT( slotEdit() ) ); + connect( m_view->deleteButton, SIGNAL( clicked() ), + this, SLOT( slotDelete() ) ); + connect( m_view->toggleAutoButton, SIGNAL( clicked() ), + this, SLOT( slotToggleAuto() ) ); +} + +NotifierModule::~NotifierModule() +{ +} + +void NotifierModule::load() +{ + m_settings.reload(); + slotMimeTypeChanged( m_view->mimetypesCombo->currentItem() ); +} + +void NotifierModule::save() +{ + m_settings.save(); +} + +void NotifierModule::defaults() +{ + m_settings.clearAutoActions(); + slotMimeTypeChanged( m_view->mimetypesCombo->currentItem() ); +} + +void NotifierModule::updateListBox() +{ + m_view->actionsList->clear(); + slotActionSelected( 0L ); + + QValueList<NotifierAction*> services; + if ( m_mimetype.isEmpty() ) + { + services = m_settings.actions(); + } + else + { + services = m_settings.actionsForMimetype( m_mimetype ); + } + + QValueList<NotifierAction*>::iterator it; + + for ( it = services.begin(); it != services.end(); ++it ) + { + new ActionListBoxItem( *it, m_mimetype, m_view->actionsList ); + } +} + +void NotifierModule::slotActionSelected(QListBoxItem *item) +{ + NotifierAction *action = 0L; + + if ( item!=0L ) + { + ActionListBoxItem *action_item + = static_cast<ActionListBoxItem*>(item); + action = action_item->action(); + } + + bool isWritable = action!=0L && action->isWritable(); + m_view->deleteButton->setEnabled( isWritable ); + m_view->editButton->setEnabled( isWritable ); + m_view->addButton->setEnabled( TRUE ); + m_view->toggleAutoButton->setEnabled( action!=0L && !m_mimetype.isEmpty() ); +} + +void NotifierModule::slotMimeTypeChanged(int index) +{ + if ( index == 0 ) + { + m_mimetype = QString(); + } + else + { + QListBoxItem *item = m_view->mimetypesCombo->listBox()->item( index ); + MimetypeListBoxItem *mime_item + = static_cast<MimetypeListBoxItem*>( item ); + m_mimetype = mime_item->mimetype(); + } + + updateListBox(); +} + +void NotifierModule::slotAdd() +{ + NotifierServiceAction *action = new NotifierServiceAction(); + ServiceConfigDialog dialog(action, m_settings.supportedMimetypes(), this); + + int value = dialog.exec(); + + if ( value == QDialog::Accepted ) + { + m_settings.addAction( action ); + updateListBox(); + emit changed( true ); + } + else + { + delete action; + } +} + +void NotifierModule::slotEdit() +{ + ActionListBoxItem *action_item + = static_cast<ActionListBoxItem*>(m_view->actionsList->selectedItem()); + + NotifierServiceAction * action = dynamic_cast<NotifierServiceAction*>( action_item->action() ); + if ( action ) + { + ServiceConfigDialog dialog(action, m_settings.supportedMimetypes(), this); + + int value = dialog.exec(); + + if ( value == QDialog::Accepted ) + { + updateListBox(); + emit changed( true ); + } + } +} + +void NotifierModule::slotDelete() +{ + ActionListBoxItem *action_item + = static_cast<ActionListBoxItem*>(m_view->actionsList->selectedItem()); + + NotifierServiceAction *action; + action = dynamic_cast<NotifierServiceAction*>( action_item->action() ); + if ( action ) + { + m_settings.deleteAction( action ); + updateListBox(); + emit changed( true ); + } +} + +void NotifierModule::slotToggleAuto() +{ + ActionListBoxItem *action_item + = static_cast<ActionListBoxItem*>( m_view->actionsList->selectedItem() ); + NotifierAction *action = action_item->action(); + + int index = m_view->actionsList->index( action_item ); + + if ( action->autoMimetypes().contains( m_mimetype ) ) + { + m_settings.resetAutoAction( m_mimetype ); + } + else + { + m_settings.setAutoAction( m_mimetype, action ); + } + + updateListBox(); + emit changed( true ); + + m_view->actionsList->setSelected( index, true ); +} + +#include "notifiermodule.moc" diff --git a/kioslave/media/kcmodule/notifiermodule.h b/kioslave/media/kcmodule/notifiermodule.h new file mode 100644 index 000000000..6c71df51f --- /dev/null +++ b/kioslave/media/kcmodule/notifiermodule.h @@ -0,0 +1,57 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _NOTIFIERMODULE_H_ +#define _NOTIFIERMODULE_H_ + +#include <kcmodule.h> + +#include "notifiersettings.h" +#include "notifiermoduleview.h" + +class NotifierModule : public KCModule +{ + Q_OBJECT + +public: + NotifierModule( QWidget* parent = 0, const char* name = 0); + ~NotifierModule(); + + void load(); + void save(); + void defaults(); + +private slots: + void slotAdd(); + void slotDelete(); + void slotEdit(); + void slotToggleAuto(); + + void slotActionSelected( QListBoxItem * item ); + void slotMimeTypeChanged( int index ); + +private: + void updateListBox(); + + QString m_mimetype; + NotifierSettings m_settings; + NotifierModuleView *m_view; +}; + +#endif diff --git a/kioslave/media/kcmodule/notifiermoduleview.ui b/kioslave/media/kcmodule/notifiermoduleview.ui new file mode 100644 index 000000000..87edc0f36 --- /dev/null +++ b/kioslave/media/kcmodule/notifiermoduleview.ui @@ -0,0 +1,171 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>NotifierModuleView</class> +<widget class="QWidget"> + <property name="name"> + <cstring>NotifierModuleView</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>480</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>mediumType</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Medium types:</string> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>mimetypesCombo</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>Here is the list of the available types of medium which can be monitored. You can filter the available actions by selecting a type of medium. If you want to see all the actions, select "All Mime Types".</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>addButton</cstring> + </property> + <property name="text"> + <string>&Add...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click here to add an action.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>deleteButton</cstring> + </property> + <property name="text"> + <string>&Delete</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click here to delete the selected action if possible.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>editButton</cstring> + </property> + <property name="text"> + <string>&Edit...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click here to edit the selected action if possible.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>toggleAutoButton</cstring> + </property> + <property name="text"> + <string>&Toggle as Auto Action</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click here to perform this action automatically on detection of the selected medium type (this option is disabled when "All Mime Types" is selected).</string> + </property> + </widget> + </vbox> + </widget> + <spacer row="1" column="1"> + <property name="name"> + <cstring>buttonSpacer</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>70</width> + <height>101</height> + </size> + </property> + </spacer> + <widget class="KListBox" row="0" column="0" rowspan="2" colspan="1"> + <property name="name"> + <cstring>actionsList</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Here is the list of the available actions. You can modify them by using the buttons on your right.</string> + </property> + </widget> + </grid> + </widget> + </vbox> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kcombobox.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klistbox.h</includehint> +</includehints> +</UI> diff --git a/kioslave/media/kcmodule/serviceconfigdialog.cpp b/kioslave/media/kcmodule/serviceconfigdialog.cpp new file mode 100644 index 000000000..1a127d12b --- /dev/null +++ b/kioslave/media/kcmodule/serviceconfigdialog.cpp @@ -0,0 +1,151 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "serviceconfigdialog.h" + +#include <klocale.h> +#include <klineedit.h> +#include <kactionselector.h> +#include <kicondialog.h> +#include <qlistbox.h> +#include <kservice.h> +#include <kopenwith.h> +#include <kpushbutton.h> +#include <kiconloader.h> +#include <qpixmap.h> +#include <qiconset.h> + + +#include "mimetypelistboxitem.h" + +ServiceConfigDialog::ServiceConfigDialog(NotifierServiceAction *action, + const QStringList &mimetypesList, + QWidget* parent, const char* name) + : KDialogBase(parent, name, true, i18n("Edit Service"), Ok|Cancel, Ok, true), + m_action(action) +{ + m_view = new ServiceView(this); + + m_view->iconButton->setIcon( m_action->iconName() ); + m_view->labelEdit->setText( m_action->label() ); + m_view->commandEdit->setText( m_action->service().m_strExec ); + + QIconSet iconSet = SmallIconSet("configure"); + QPixmap pixMap = iconSet.pixmap( QIconSet::Small, QIconSet::Normal ); + m_view->commandButton->setIconSet( iconSet ); + m_view->commandButton->setFixedSize( pixMap.width()+8, pixMap.height()+8 ); + + m_iconChanged = false; + + QStringList all_mimetypes = mimetypesList; + QStringList action_mimetypes = action->mimetypes(); + + QStringList::iterator it = all_mimetypes.begin(); + QStringList::iterator end = all_mimetypes.end(); + + for ( ; it!=end; ++it ) + { + QListBox *list; + + if ( action_mimetypes.contains( *it ) ) + { + list = m_view->mimetypesSelector->selectedListBox(); + } + else + { + list = m_view->mimetypesSelector->availableListBox(); + } + + new MimetypeListBoxItem( *it, list ); + } + + setMainWidget(m_view); + setCaption( m_action->label() ); + + connect( m_view->iconButton, SIGNAL( iconChanged(QString) ), + this, SLOT( slotIconChanged() ) ); + connect( m_view->commandButton, SIGNAL( clicked() ), + this, SLOT( slotCommand() ) ); +} + +bool operator==( KDEDesktopMimeType::Service s1, KDEDesktopMimeType::Service s2 ) +{ + return ( s1.m_strName==s2.m_strName ) + && ( s1.m_strIcon==s2.m_strIcon ) + && ( s1.m_strExec==s2.m_strExec ); +} + +bool operator!=( KDEDesktopMimeType::Service s1, KDEDesktopMimeType::Service s2 ) +{ + return !( s1==s2 ); +} + +void ServiceConfigDialog::slotOk() +{ + KDEDesktopMimeType::Service service; + service.m_strName = m_view->labelEdit->text(); + service.m_strIcon = m_view->iconButton->icon(); + service.m_strExec = m_view->commandEdit->text(); + + QStringList mimetypes; + + uint list_count = m_view->mimetypesSelector->selectedListBox()->count(); + for( uint i=0; i < list_count; ++i ) + { + QListBoxItem *item = m_view->mimetypesSelector->selectedListBox()->item(i); + MimetypeListBoxItem *mime_item = static_cast<MimetypeListBoxItem*>( item ); + mimetypes.append( mime_item->mimetype() ); + } + + if ( service!=m_action->service() || mimetypes!=m_action->mimetypes() ) + { + m_action->setService( service ); + m_action->setMimetypes( mimetypes ); + accept(); + } + else + { + reject(); + } +} + +void ServiceConfigDialog::slotIconChanged() +{ + m_iconChanged = true; +} + +void ServiceConfigDialog::slotCommand() +{ + KOpenWithDlg d(this); + int value = d.exec(); + if ( value == QDialog::Accepted ) + { + KService::Ptr service = d.service(); + if ( service != 0L ) + { + m_view->commandEdit->setText( service->exec() ); + if ( m_iconChanged == false ) + { + m_view->iconButton->setIcon( service->icon() ); + } + } + } +} + +#include "serviceconfigdialog.moc" diff --git a/kioslave/media/kcmodule/serviceconfigdialog.h b/kioslave/media/kcmodule/serviceconfigdialog.h new file mode 100644 index 000000000..090a978d4 --- /dev/null +++ b/kioslave/media/kcmodule/serviceconfigdialog.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _SERVICECONFIGDIALOG_H_ +#define _SERVICECONFIGDIALOG_H_ + +#include <kdialogbase.h> + +#include "notifierserviceaction.h" +#include "serviceview.h" + +class ServiceConfigDialog : public KDialogBase +{ + Q_OBJECT + +public: + ServiceConfigDialog(NotifierServiceAction *action, + const QStringList &mimetypesList, + QWidget* parent = 0, const char* name = 0); + +public slots: + void slotOk(); + void slotIconChanged(); + void slotCommand(); + +private: + ServiceView *m_view; + NotifierServiceAction *m_action; + bool m_iconChanged; +}; + +#endif diff --git a/kioslave/media/kcmodule/serviceview.ui b/kioslave/media/kcmodule/serviceview.ui new file mode 100644 index 000000000..938a9bf89 --- /dev/null +++ b/kioslave/media/kcmodule/serviceview.ui @@ -0,0 +1,248 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>ServiceView</class> +<widget class="QWidget"> + <property name="name"> + <cstring>ServiceView</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>664</width> + <height>503</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>640</width> + <height>480</height> + </size> + </property> + <property name="caption"> + <string>Edit Service</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>groupBox1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Edit Service</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KIconButton"> + <property name="name"> + <cstring>iconButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>40</width> + <height>40</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>40</height> + </size> + </property> + <property name="text"> + <string></string> + </property> + <property name="iconSize"> + <number>32</number> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer4</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KLineEdit"> + <property name="name"> + <cstring>labelEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer5</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>21</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="KActionSelector"> + <property name="name"> + <cstring>mimetypesSelector</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="availableLabel"> + <string>Available &medium types:</string> + </property> + <property name="selectedLabel"> + <string>Displa&y service for:</string> + </property> + <property name="showUpDownButtons"> + <bool>false</bool> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KActiveLabel"> + <property name="name"> + <cstring>m_lbCommmand</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Command:</string> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>commandEdit</cstring> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>commandButton</cstring> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </grid> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kicondialog.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kactionselector.h</includehint> + <includehint>kactivelabel.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kioslave/media/kfile-plugin/Makefile.am b/kioslave/media/kfile-plugin/Makefile.am new file mode 100644 index 000000000..7f694e28b --- /dev/null +++ b/kioslave/media/kfile-plugin/Makefile.am @@ -0,0 +1,13 @@ +kde_module_LTLIBRARIES = kfile_media.la + +kfile_media_la_SOURCES = kfilemediaplugin.cpp +kfile_media_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) -module $(KDE_PLUGIN) +kfile_media_la_LIBADD = $(LIB_KIO) ../libmediacommon/libmediacommon.la + +kdelnkdir = $(kde_servicesdir) +kde_services_DATA = kfile_media.desktop + +INCLUDES = -I$(srcdir)/../libmediacommon $(all_includes) + +METASOURCES = AUTO +noinst_HEADERS = kfilemediaplugin.h diff --git a/kioslave/media/kfile-plugin/kfile_media.desktop b/kioslave/media/kfile-plugin/kfile_media.desktop new file mode 100644 index 000000000..474ab12b2 --- /dev/null +++ b/kioslave/media/kfile-plugin/kfile_media.desktop @@ -0,0 +1,75 @@ +[Desktop Entry] +Type=Service +Name=Medium Information +Name[af]=Medium Informasie +Name[ar]=معلومات عن الوسيط +Name[az]=Mediyum MÉ™'lumatı +Name[be]=Ð†Ð½Ñ„Ð°Ñ€Ð¼Ð°Ñ†Ñ‹Ñ Ð°Ð± ноÑьбітах +Name[bg]=Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° уÑтройÑтвата +Name[bn]=মিডিয়া তথà§à¦¯ +Name[bs]=Informacije o mediju +Name[ca]=Informació de suports +Name[cs]=Informace o médiu +Name[csb]=Wëdowiédzô ò zôpisownëch mediach +Name[da]=Medieinformation +Name[de]=Medium-Information +Name[el]=ΠληÏοφοÏίες μÎσου +Name[eo]=datumportilaj informoj +Name[es]=Información de recursos +Name[et]=Andmekandja info +Name[eu]=Media informazioa +Name[fa]=اطلاعات رسانه +Name[fi]=Tallenteiden tiedot +Name[fr]=Informations sur le média +Name[fy]=Mediumynformaasje +Name[ga]=Eolas faoin Mheán +Name[gl]=Información do Meio +Name[he]=מידע ×ודות המדיה +Name[hi]=माधà¥à¤¯à¤® जानकारी +Name[hr]=Podaci o mediju +Name[hu]=Adathordozó-jellemzÅ‘k +Name[is]=Upplýsingar um miðil +Name[it]=Informazioni supporto +Name[ja]=ãƒ¡ãƒ‡ã‚£ã‚¢æƒ…å ± +Name[ka]=ცნáƒáƒ‘ები მáƒáƒ¢áƒáƒ ებლის შესáƒáƒ®áƒ”ბ +Name[kk]=ТаÑушының мәліметі +Name[km]=áž–áŸážáŸŒáž˜áž¶áž“​ឧបករណ០+Name[ko]=메모리 ì •ë³´ +Name[lt]=Laikmenos informacija +Name[lv]=Datu nesÄ“ju informÄcija +Name[mk]=Информации за ноÑач +Name[ms]=Maklumat Medium +Name[mt]=Informazzjoni tal-apparat +Name[nb]=Medieinformasjon +Name[nds]=Medium-Informatschoon +Name[ne]=माधà¥à¤¯à¤® सूचना +Name[nl]=Mediuminformatie +Name[nn]=Medieinformasjon +Name[pa]=ਮਾਧਿਅਮ ਜਾਣਕਾਰੀ +Name[pl]=Informacje o noÅ›nikach danych +Name[pt]=Informação do Dispositivo +Name[pt_BR]=Informações sobre a MÃdia +Name[ro]=InformaÈ›ii mediu +Name[ru]=Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ диÑке +Name[rw]=Amakuru y'igitangazamakuru +Name[se]=MediumdieÄ‘ut +Name[sk]=Informácie o médiu +Name[sl]=Informacije o mediju +Name[sr]=Информације о медијуму +Name[sr@Latn]=Informacije o medijumu +Name[sv]=Mediainformation +Name[ta]=சாதனத௠தகவல௠+Name[te]=మాధà±à°¯à°® సమాచారం +Name[th]=ข้à¸à¸¡à¸¹à¸¥à¸‚à¸à¸‡à¸ªà¸·à¹ˆà¸ +Name[tr]=Ortam Bilgisi +Name[tt]=Media Turında +Name[uk]=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ ноÑій +Name[uz]=Saqlash uskuna haqida maʼlumot +Name[uz@cyrillic]=Сақлаш уÑкуна ҳақида маълумот +Name[vi]=Thông tin vá» á»” lÆ°u trữ +Name[wa]=InformÃ¥cions sol mediom +Name[zh_CN]=ä»‹è´¨ä¿¡æ¯ +Name[zh_TW]=媒體資訊 +ServiceTypes=KFilePlugin +X-KDE-Library=kfile_media +MimeType=media/audiocd;media/hdd_mounted;media/blankcd;media/hdd_unmounted;media/blankdvd;media/cdrom_mounted;media/cdrom_unmounted;media/cdwriter_mounted;media/nfs_mounted;media/cdwriter_unmounted;media/nfs_unmounted;media/removable_mounted;media/dvd_mounted;media/removable_unmounted;media/dvd_unmounted;media/smb_mounted;media/dvdvideo;media/smb_unmounted;media/floppy5_mounted;media/svcd;media/floppy5_unmounted;media/vcd;media/floppy_mounted;media/zip_mounted;media/floppy_unmounted;media/zip_unmounted;media/gphoto2camera;media/camera_mounted;media/camera_unmounted diff --git a/kioslave/media/kfile-plugin/kfilemediaplugin.cpp b/kioslave/media/kfile-plugin/kfilemediaplugin.cpp new file mode 100644 index 000000000..c91dbf21a --- /dev/null +++ b/kioslave/media/kfile-plugin/kfilemediaplugin.cpp @@ -0,0 +1,197 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "config.h" +#include "kfilemediaplugin.h" + +#include <kgenericfactory.h> + +#include <dcopref.h> + +#include <qpixmap.h> +#include <qpainter.h> +#include <qstyle.h> +#include <qapplication.h> +#include <qfile.h> + +#ifdef HAVE_STATVFS +# include <sys/statvfs.h> +#else +# include <sys/mount.h> +# define statvfs statfs +# define f_frsize f_bsize +#endif + +typedef KGenericFactory<KFileMediaPlugin> KFileMediaPluginFactory; +K_EXPORT_COMPONENT_FACTORY(kfile_media, KFileMediaPluginFactory("kio_media")) + +KFileMediaPlugin::KFileMediaPlugin(QObject *parent, const char *name, + const QStringList& args) + : KFilePlugin(parent, name, args) +{ + addMimeType( "media/audiocd" ); + addMimeType( "media/hdd_mounted" ); + addMimeType( "media/blankcd" ); + addMimeType( "media/hdd_unmounted" ); + addMimeType( "media/blankdvd" ); + addMimeType( "media/cdrom_mounted" ); + addMimeType( "media/cdrom_unmounted" ); + addMimeType( "media/cdwriter_mounted" ); + addMimeType( "media/nfs_mounted" ); + addMimeType( "media/cdwriter_unmounted" ); + addMimeType( "media/nfs_unmounted" ); + addMimeType( "media/removable_mounted" ); + addMimeType( "media/dvd_mounted" ); + addMimeType( "media/removable_unmounted" ); + addMimeType( "media/dvd_unmounted" ); + addMimeType( "media/smb_mounted" ); + addMimeType( "media/dvdvideo" ); + addMimeType( "media/smb_unmounted" ); + addMimeType( "media/floppy5_mounted" ); + addMimeType( "media/svcd" ); + addMimeType( "media/floppy5_unmounted" ); + addMimeType( "media/vcd" ); + addMimeType( "media/floppy_mounted" ); + addMimeType( "media/zip_mounted" ); + addMimeType( "media/floppy_unmounted" ); + addMimeType( "media/zip_unmounted" ); + addMimeType( "media/gphoto2camera" ); + addMimeType( "media/camera_mounted" ); + addMimeType( "media/camera_unmounted" ); +} + +bool KFileMediaPlugin::readInfo(KFileMetaInfo &info, uint /*what*/) +{ + const Medium medium = askMedium(info); + + kdDebug() << "KFileMediaPlugin::readInfo " << medium.id() << endl; + + if (medium.id().isNull()) return false; + + QString mount_point = medium.mountPoint(); + KURL base_url = medium.prettyBaseURL(); + QString device_node = medium.deviceNode(); + + KFileMetaInfoGroup group = appendGroup(info, "mediumInfo"); + + if (base_url.isValid()) + { + appendItem(group, "baseURL", base_url.prettyURL()); + } + + if (!device_node.isEmpty()) + { + appendItem(group, "deviceNode", device_node); + } + + if (!mount_point.isEmpty() && medium.isMounted()) + { + m_total = 0; + m_used = 0; + m_free = 0; + + struct statvfs vfs; + memset(&vfs, 0, sizeof(vfs)); + + if ( ::statvfs(QFile::encodeName(mount_point), &vfs) != -1 ) + { + m_total = static_cast<KIO::filesize_t>(vfs.f_blocks) * static_cast<KIO::filesize_t>(vfs.f_frsize); + m_free = static_cast<KIO::filesize_t>(vfs.f_bavail) * static_cast<KIO::filesize_t>(vfs.f_frsize); + m_used = m_total - m_free; + + int percent = 0; + int length = 0; + + if (m_total != 0) + { + percent = 100 * m_used / m_total; + length = 150 * m_used / m_total; + } + + appendItem(group, "free", m_free); + appendItem(group, "used", m_used); + appendItem(group, "total", m_total); + + group = appendGroup(info, "mediumSummary"); + + appendItem(group, "percent", QString("%1%").arg(percent)); + + QPixmap bar(150, 20); + QPainter p(&bar); + + p.fillRect(0, 0, length, 20, Qt::red); + p.fillRect(length, 0, 150-length, 20, Qt::green); + + QColorGroup cg = QApplication::palette().active(); + + QApplication::style().drawPrimitive(QStyle::PE_Panel, &p, + QRect(0, 0, 150, 20), cg, + QStyle::Style_Sunken); + + appendItem( group, "thumbnail", bar ); + } + } + + return true; +} + +const Medium KFileMediaPlugin::askMedium(KFileMetaInfo &info) +{ + DCOPRef mediamanager("kded", "mediamanager"); + kdDebug() << "properties " << info.url() << endl; + DCOPReply reply = mediamanager.call( "properties", info.url().url() ); + + if ( !reply.isValid() ) + { + return Medium(QString::null, QString::null); + } + + return Medium::create(reply); +} + +void KFileMediaPlugin::addMimeType(const char *mimeType) +{ + KFileMimeTypeInfo *info = addMimeTypeInfo( mimeType ); + + KFileMimeTypeInfo::GroupInfo *group + = addGroupInfo(info, "mediumInfo", i18n("Medium Information")); + + KFileMimeTypeInfo::ItemInfo *item + = addItemInfo(group, "free", i18n("Free"), QVariant::ULongLong); + setUnit(item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "used", i18n("Used"), QVariant::ULongLong); + setUnit(item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "total", i18n("Total"), QVariant::ULongLong); + setUnit(item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "baseURL", i18n("Base URL"), QVariant::String); + item = addItemInfo(group, "mountPoint", i18n("Mount Point"), QVariant::String); + item = addItemInfo(group, "deviceNode", i18n("Device Node"), QVariant::String); + + group = addGroupInfo(info, "mediumSummary", i18n("Medium Summary")); + + item = addItemInfo(group, "percent", i18n("Usage"), QVariant::String); + + item = addItemInfo( group, "thumbnail", i18n("Bar Graph"), QVariant::Image ); + setHint( item, KFileMimeTypeInfo::Thumbnail ); +} + +#include "kfilemediaplugin.moc" diff --git a/kioslave/media/kfile-plugin/kfilemediaplugin.h b/kioslave/media/kfile-plugin/kfilemediaplugin.h new file mode 100644 index 000000000..666f75df3 --- /dev/null +++ b/kioslave/media/kfile-plugin/kfilemediaplugin.h @@ -0,0 +1,47 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _KFILE_MEDIA_H_ +#define _KFILE_MEDIA_H_ + +#include <kfilemetainfo.h> +#include <kurl.h> +#include <kio/global.h> + +#include "medium.h" + +class KFileMediaPlugin : public KFilePlugin +{ +Q_OBJECT +public: + KFileMediaPlugin(QObject *parent, const char *name, + const QStringList &args); + + bool readInfo(KFileMetaInfo &info, uint what = KFileMetaInfo::Fastest); + +private: + void addMimeType(const char *mimeType); + const Medium askMedium(KFileMetaInfo &info); + + KIO::filesize_t m_total; + KIO::filesize_t m_used; + KIO::filesize_t m_free; +}; + +#endif diff --git a/kioslave/media/kio_media.cpp b/kioslave/media/kio_media.cpp new file mode 100644 index 000000000..b9283592c --- /dev/null +++ b/kioslave/media/kio_media.cpp @@ -0,0 +1,275 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <stdlib.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <kcmdlineargs.h> + +#include <qeventloop.h> + +#include "kio_media.h" + + +static const KCmdLineOptions options[] = +{ + { "+protocol", I18N_NOOP( "Protocol name" ), 0 }, + { "+pool", I18N_NOOP( "Socket name" ), 0 }, + { "+app", I18N_NOOP( "Socket name" ), 0 }, + KCmdLineLastOption +}; + +extern "C" { + int KDE_EXPORT kdemain( int argc, char **argv ) + { + // KApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + KCmdLineArgs::init(argc, argv, "kio_media", 0, 0, 0, 0); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + MediaProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +MediaProtocol::MediaProtocol(const QCString &protocol, + const QCString &pool, const QCString &app) + : ForwardingSlaveBase(protocol, pool, app) +{ + connect( &m_impl, SIGNAL( warning( const QString & ) ), + this, SLOT( slotWarning( const QString & ) ) ); +} + +MediaProtocol::~MediaProtocol() +{ +} + +bool MediaProtocol::rewriteURL(const KURL &url, KURL &newUrl) +{ + QString name, path; + + if ( !m_impl.parseURL(url, name, path) ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return false; + } + + + if ( !m_impl.realURL(name, path, newUrl) ) + { + error( m_impl.lastErrorCode(), m_impl.lastErrorMessage() ); + return false; + } + + return true; +} + +void MediaProtocol::put(const KURL &url, int permissions, + bool overwrite, bool resume) +{ + kdDebug(1219) << "MediaProtocol::put: " << url << endl; + + QString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( ok && path.isEmpty() ) + { + error(KIO::ERR_CANNOT_OPEN_FOR_WRITING, url.prettyURL()); + } + else + { + ForwardingSlaveBase::put(url, permissions, overwrite, resume); + } +} + +void MediaProtocol::rename(const KURL &src, const KURL &dest, bool overwrite) +{ + kdDebug(1219) << "MediaProtocol::rename: " << src << ", " << dest << ", " + << overwrite << endl; + + QString src_name, src_path; + bool ok = m_impl.parseURL(src, src_name, src_path); + QString dest_name, dest_path; + ok &= m_impl.parseURL(dest, dest_name, dest_path); + + if ( ok && src_path.isEmpty() && dest_path.isEmpty() + && src.protocol() == "media" && dest.protocol() == "media" ) + { + if (!m_impl.setUserLabel(src_name, dest_name)) + { + error(m_impl.lastErrorCode(), m_impl.lastErrorMessage()); + } + else + { + finished(); + } + } + else + { + ForwardingSlaveBase::rename(src, dest, overwrite); + } +} + +void MediaProtocol::mkdir(const KURL &url, int permissions) +{ + kdDebug(1219) << "MediaProtocol::mkdir: " << url << endl; + + QString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( ok && path.isEmpty() ) + { + error(KIO::ERR_COULD_NOT_MKDIR, url.prettyURL()); + } + else + { + ForwardingSlaveBase::mkdir(url, permissions); + } +} + +void MediaProtocol::del(const KURL &url, bool isFile) +{ + kdDebug(1219) << "MediaProtocol::del: " << url << endl; + + QString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( ok && path.isEmpty() ) + { + error(KIO::ERR_CANNOT_DELETE, url.prettyURL()); + } + else + { + ForwardingSlaveBase::del(url, isFile); + } +} + +void MediaProtocol::stat(const KURL &url) +{ + kdDebug(1219) << "MediaProtocol::stat: " << url << endl; + QString path = url.path(); + if( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + KIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + QString name; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if( path.isEmpty() ) + { + KIO::UDSEntry entry; + + if ( m_impl.statMedium(name, entry) + || m_impl.statMediumByLabel(name, entry) ) + { + statEntry(entry); + finished(); + } + else + { + error(KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + } + else + { + ForwardingSlaveBase::stat(url); + } +} + +void MediaProtocol::listDir(const KURL &url) +{ + kdDebug(1219) << "MediaProtocol::listDir: " << url << endl; + + if ( url.path().length() <= 1 ) + { + listRoot(); + return; + } + + QString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + ForwardingSlaveBase::listDir(url); +} + +void MediaProtocol::listRoot() +{ + KIO::UDSEntry entry; + + KIO::UDSEntryList media_entries; + bool ok = m_impl.listMedia(media_entries); + + if (!ok) + { + error( m_impl.lastErrorCode(), m_impl.lastErrorMessage() ); + return; + } + + totalSize(media_entries.count()+1); + + m_impl.createTopLevelEntry(entry); + listEntry(entry, false); + + KIO::UDSEntryListIterator it = media_entries.begin(); + KIO::UDSEntryListIterator end = media_entries.end(); + + for(; it!=end; ++it) + { + listEntry(*it, false); + } + + entry.clear(); + listEntry(entry, true); + + finished(); +} + +void MediaProtocol::slotWarning( const QString &msg ) +{ + warning( msg ); +} + +#include "kio_media.moc" diff --git a/kioslave/media/kio_media.h b/kioslave/media/kio_media.h new file mode 100644 index 000000000..55b3ae670 --- /dev/null +++ b/kioslave/media/kio_media.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _KIO_MEDIA_H_ +#define _KIO_MEDIA_H_ + +#include <kio/forwardingslavebase.h> + +#include "mediaimpl.h" + +class MediaProtocol : public KIO::ForwardingSlaveBase +{ +Q_OBJECT +public: + MediaProtocol(const QCString &protocol, const QCString &pool, + const QCString &app); + virtual ~MediaProtocol(); + + virtual bool rewriteURL(const KURL &url, KURL &newUrl); + + virtual void put(const KURL &url, int permissions, + bool overwrite, bool resume); + virtual void rename(const KURL &src, const KURL &dest, bool overwrite); + virtual void mkdir(const KURL &url, int permissions); + virtual void del(const KURL &url, bool isFile); + virtual void stat(const KURL &url); + virtual void listDir(const KURL &url); + +private slots: + void slotWarning( const QString &msg ); + +private: + void listRoot(); + + MediaImpl m_impl; +}; + +#endif diff --git a/kioslave/media/libmediacommon/Makefile.am b/kioslave/media/libmediacommon/Makefile.am new file mode 100644 index 000000000..dd27d265a --- /dev/null +++ b/kioslave/media/libmediacommon/Makefile.am @@ -0,0 +1,11 @@ +INCLUDES = $(all_includes) +METASOURCES = AUTO + +noinst_LTLIBRARIES = libmediacommon.la +libmediacommon_la_SOURCES = medium.cpp actionlistboxitem.cpp \ + notifieraction.cpp notifierserviceaction.cpp \ + notifiernothingaction.cpp notifieropenaction.cpp \ + notifiersettings.cpp mediamanagersettings.kcfgc + +kde_kcfg_DATA = mediamanagersettings.kcfg + diff --git a/kioslave/media/libmediacommon/actionlistboxitem.cpp b/kioslave/media/libmediacommon/actionlistboxitem.cpp new file mode 100644 index 000000000..3551857c1 --- /dev/null +++ b/kioslave/media/libmediacommon/actionlistboxitem.cpp @@ -0,0 +1,47 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "actionlistboxitem.h" + +#include <klocale.h> + +#include <qpixmap.h> + +ActionListBoxItem::ActionListBoxItem(NotifierAction *action, QString mimetype, QListBox *parent) + : QListBoxPixmap(parent, action->pixmap()), + m_action(action) +{ + QString text = m_action->label(); + + if ( m_action->autoMimetypes().contains( mimetype ) ) + { + text += " (" + i18n( "Auto Action" ) + ")"; + } + + setText( text ); +} + +ActionListBoxItem::~ActionListBoxItem() +{ +} + +NotifierAction *ActionListBoxItem::action() const +{ + return m_action; +} diff --git a/kioslave/media/libmediacommon/actionlistboxitem.h b/kioslave/media/libmediacommon/actionlistboxitem.h new file mode 100644 index 000000000..cdd8a3ff1 --- /dev/null +++ b/kioslave/media/libmediacommon/actionlistboxitem.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _ACTIONLISTBOXITEM_H_ +#define _ACTIONLISTBOXITEM_H_ + +#include <qlistbox.h> +#include <qstring.h> + +#include "notifieraction.h" + +class ActionListBoxItem : public QListBoxPixmap +{ +public: + ActionListBoxItem(NotifierAction *action, QString mimetype, QListBox *parent); + ~ActionListBoxItem(); + + NotifierAction *action() const; + +private: + NotifierAction *m_action; +}; + +#endif diff --git a/kioslave/media/libmediacommon/mediamanagersettings.kcfg b/kioslave/media/libmediacommon/mediamanagersettings.kcfg new file mode 100644 index 000000000..4c9615ade --- /dev/null +++ b/kioslave/media/libmediacommon/mediamanagersettings.kcfg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE kcfg SYSTEM "http://www.kde.org/standards/kcfg/1.0/kcfg.dtd"> + +<kcfg> + <kcfgfile name="mediamanagerrc"/> + <group name="Global"> + <entry name="HalBackendEnabled" type="Bool"> + <label>Enable HAL backend</label> + <whatsthis>When HAL (Hardware Abstraction Layer) support is enabled, KDE will use it to gather information on the storage media available in your system.</whatsthis> + <default>true</default> + </entry> + <entry name="CdPollingEnabled" type="Bool"> + <label>Enable CD polling</label> + <whatsthis>Allows KDE to poll CD-Rom or DVD-Rom drives itself in order to detect medium insert.</whatsthis> + <default>true</default> + </entry> + <entry name="AutostartEnabled" type="Bool"> + <label>Enable medium application autostart after mount</label> + <whatsthis>Allows KDE to autostart application after a medium mount if it contains an Autostart or an Autoopen file.</whatsthis> + <default>true</default> + </entry> + </group> +</kcfg> diff --git a/kioslave/media/libmediacommon/mediamanagersettings.kcfgc b/kioslave/media/libmediacommon/mediamanagersettings.kcfgc new file mode 100644 index 000000000..bbc58d92e --- /dev/null +++ b/kioslave/media/libmediacommon/mediamanagersettings.kcfgc @@ -0,0 +1,4 @@ +File=mediamanagersettings.kcfg +ClassName=MediaManagerSettings +Singleton=true +Mutators=true diff --git a/kioslave/media/libmediacommon/medium.cpp b/kioslave/media/libmediacommon/medium.cpp new file mode 100644 index 000000000..5767a6c9a --- /dev/null +++ b/kioslave/media/libmediacommon/medium.cpp @@ -0,0 +1,228 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "medium.h" + +#include <kconfig.h> +#include <klocale.h> + +const QString Medium::SEPARATOR = "---"; + +Medium::Medium(const QString &id, const QString &name) +{ + m_properties+= id; /* ID */ + m_properties+= name; /* NAME */ + m_properties+= name; /* LABEL */ + m_properties+= QString::null; /* USER_LABEL */ + + m_properties+= "false"; /* MOUNTABLE */ + m_properties+= QString::null; /* DEVICE_NODE */ + m_properties+= QString::null; /* MOUNT_POINT */ + m_properties+= QString::null; /* FS_TYPE */ + m_properties+= "false"; /* MOUNTED */ + m_properties+= QString::null; /* BASE_URL */ + m_properties+= QString::null; /* MIME_TYPE */ + m_properties+= QString::null; /* ICON_NAME */ + + loadUserLabel(); + + m_halmounted = false; +} + +Medium::Medium() +{ + m_properties+= QString::null; /* ID */ + m_properties+= QString::null; /* NAME */ + m_properties+= QString::null; /* LABEL */ + m_properties+= QString::null; /* USER_LABEL */ + + m_properties+= QString::null; /* MOUNTABLE */ + m_properties+= QString::null; /* DEVICE_NODE */ + m_properties+= QString::null; /* MOUNT_POINT */ + m_properties+= QString::null; /* FS_TYPE */ + m_properties+= QString::null; /* MOUNTED */ + m_properties+= QString::null; /* BASE_URL */ + m_properties+= QString::null; /* MIME_TYPE */ + m_properties+= QString::null; /* ICON_NAME */ + + m_halmounted = false; +} + +const Medium Medium::create(const QStringList &properties) +{ + Medium m; + + if ( properties.size() >= PROPERTIES_COUNT ) + { + m.m_properties[ID] = properties[ID]; + m.m_properties[NAME] = properties[NAME]; + m.m_properties[LABEL] = properties[LABEL]; + m.m_properties[USER_LABEL] = properties[USER_LABEL]; + + m.m_properties[MOUNTABLE] = properties[MOUNTABLE]; + m.m_properties[DEVICE_NODE] = properties[DEVICE_NODE]; + m.m_properties[MOUNT_POINT] = properties[MOUNT_POINT]; + m.m_properties[FS_TYPE] = properties[FS_TYPE]; + m.m_properties[MOUNTED] = properties[MOUNTED]; + m.m_properties[BASE_URL] = properties[BASE_URL]; + m.m_properties[MIME_TYPE] = properties[MIME_TYPE]; + m.m_properties[ICON_NAME] = properties[ICON_NAME]; + } + + return m; +} + +Medium::List Medium::createList(const QStringList &properties) +{ + List l; + + if ( properties.size() % (PROPERTIES_COUNT+1) == 0) + { + int media_count = properties.size()/(PROPERTIES_COUNT+1); + + QStringList props = properties; + + for(int i=0; i<media_count; i++) + { + const Medium m = create(props); + l.append(m); + + QStringList::iterator first = props.begin(); + QStringList::iterator last = props.find(SEPARATOR); + ++last; + props.erase(first, last); + } + } + + return l; +} + + +void Medium::setName(const QString &name) +{ + m_properties[NAME] = name; +} + +void Medium::setLabel(const QString &label) +{ + m_properties[LABEL] = label; +} + +void Medium::setUserLabel(const QString &label) +{ + KConfig cfg("mediamanagerrc"); + cfg.setGroup("UserLabels"); + + QString entry_name = m_properties[ID]; + + if ( label.isNull() ) + { + cfg.deleteEntry(entry_name); + } + else + { + cfg.writeEntry(entry_name, label); + } + + m_properties[USER_LABEL] = label; +} + +void Medium::loadUserLabel() +{ + KConfig cfg("mediamanagerrc"); + cfg.setGroup("UserLabels"); + + QString entry_name = m_properties[ID]; + + if ( cfg.hasKey(entry_name) ) + { + m_properties[USER_LABEL] = cfg.readEntry(entry_name); + } + else + { + m_properties[USER_LABEL] = QString::null; + } +} + + +bool Medium::mountableState(bool mounted) +{ + if ( m_properties[DEVICE_NODE].isEmpty() + || ( mounted && m_properties[MOUNT_POINT].isEmpty() ) ) + { + return false; + } + + m_properties[MOUNTABLE] = "true"; + m_properties[MOUNTED] = ( mounted ? "true" : "false" ); + + return true; +} + +void Medium::mountableState(const QString &deviceNode, + const QString &mountPoint, + const QString &fsType, bool mounted) +{ + m_properties[MOUNTABLE] = "true"; + m_properties[DEVICE_NODE] = deviceNode; + m_properties[MOUNT_POINT] = mountPoint; + m_properties[FS_TYPE] = fsType; + m_properties[MOUNTED] = ( mounted ? "true" : "false" ); +} + +void Medium::unmountableState(const QString &baseURL) +{ + m_properties[MOUNTABLE] = "false"; + m_properties[BASE_URL] = baseURL; +} + +void Medium::setMimeType(const QString &mimeType) +{ + m_properties[MIME_TYPE] = mimeType; +} + +void Medium::setIconName(const QString &iconName) +{ + m_properties[ICON_NAME] = iconName; +} + +bool Medium::needMounting() const +{ + return isMountable() && !isMounted(); +} + +KURL Medium::prettyBaseURL() const +{ + if ( !baseURL().isEmpty() ) + return baseURL(); + + return KURL( mountPoint() ); +} + +QString Medium::prettyLabel() const +{ + if ( !userLabel().isEmpty() ) + { + return userLabel(); + } + else + { + return label(); + } +} + diff --git a/kioslave/media/libmediacommon/medium.h b/kioslave/media/libmediacommon/medium.h new file mode 100644 index 000000000..f94545e8e --- /dev/null +++ b/kioslave/media/libmediacommon/medium.h @@ -0,0 +1,110 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MEDIUM_H_ +#define _MEDIUM_H_ + +#include <qstring.h> +#include <qstringlist.h> +#include <kurl.h> +#include <qmap.h> + +class Medium +{ +public: + typedef QValueList<const Medium> List; + + static const uint ID = 0; + static const uint NAME = 1; + static const uint LABEL = 2; + static const uint USER_LABEL = 3; + static const uint MOUNTABLE = 4; + static const uint DEVICE_NODE = 5; + static const uint MOUNT_POINT = 6; + static const uint FS_TYPE = 7; + static const uint MOUNTED = 8; + static const uint BASE_URL = 9; + static const uint MIME_TYPE = 10; + static const uint ICON_NAME = 11; + static const uint PROPERTIES_COUNT = 12; + static const QString SEPARATOR; + + Medium(const QString &id, const QString &name); + static const Medium create(const QStringList &properties); + static List createList(const QStringList &properties); + + const QStringList &properties() const { return m_properties; } + + QString id() const { return m_properties[ID]; } + QString name() const { return m_properties[NAME]; } + QString label() const { return m_properties[LABEL]; } + QString userLabel() const { return m_properties[USER_LABEL]; } + bool isMountable() const { return m_properties[MOUNTABLE]=="true"; } + QString deviceNode() const { return m_properties[DEVICE_NODE]; } + QString mountPoint() const { return m_properties[MOUNT_POINT]; } + QString fsType() const { return m_properties[FS_TYPE]; } + bool isMounted() const { return m_properties[MOUNTED]=="true"; } + QString baseURL() const { return m_properties[BASE_URL]; } + QString mimeType() const { return m_properties[MIME_TYPE]; } + QString iconName() const { return m_properties[ICON_NAME]; } + + bool needMounting() const; + KURL prettyBaseURL() const; + QString prettyLabel() const; + + void setName(const QString &name); + void setLabel(const QString &label); + void setUserLabel(const QString &label); + + bool mountableState(bool mounted); + void mountableState(const QString &deviceNode, + const QString &mountPoint, + const QString &fsType, bool mounted); + void unmountableState(const QString &baseURL = QString::null); + + void setMimeType(const QString &mimeType); + void setIconName(const QString &iconName); + void setHalMounted(bool flag) const { m_halmounted = flag; } + bool halMounted() const { return m_halmounted; } + +private: + Medium(); + void loadUserLabel(); + + QStringList m_properties; + mutable bool m_halmounted; + +friend class QValueListNode<const Medium>; +}; + +namespace MediaManagerUtils { + static inline QMap<QString,QString> splitOptions(const QStringList & options) + { + QMap<QString,QString> valids; + + for (QStringList::ConstIterator it = options.begin(); it != options.end(); ++it) + { + QString key = (*it).left((*it).find('=')); + QString value = (*it).mid((*it).find('=') + 1); + valids[key] = value; + } + return valids; + } +} + +#endif diff --git a/kioslave/media/libmediacommon/notifieraction.cpp b/kioslave/media/libmediacommon/notifieraction.cpp new file mode 100644 index 000000000..3eea682d4 --- /dev/null +++ b/kioslave/media/libmediacommon/notifieraction.cpp @@ -0,0 +1,97 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "notifieraction.h" + +#include <qfile.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kicontheme.h> + +NotifierAction::NotifierAction() +{ +} + +NotifierAction::~NotifierAction() +{ +} + +void NotifierAction::setIconName(const QString &iconName) +{ + m_iconName = iconName; +} + +void NotifierAction::setLabel(const QString &label) +{ + m_label = label; +} + +QString NotifierAction::iconName() const +{ + return m_iconName; +} + +QPixmap NotifierAction::pixmap() const +{ + QFile f( m_iconName ); + + if ( f.exists() ) + { + return QPixmap( m_iconName ); + } + else + { + QString path = KGlobal::iconLoader()->iconPath( m_iconName, -32 ); + return QPixmap( path ); + } +} + +QString NotifierAction::label() const +{ + return m_label; +} + +void NotifierAction::addAutoMimetype( const QString &mimetype ) +{ + if ( !m_autoMimetypes.contains( mimetype ) ) + { + m_autoMimetypes.append( mimetype ); + } +} + +void NotifierAction::removeAutoMimetype( const QString &mimetype ) +{ + m_autoMimetypes.remove( mimetype ); +} + +QStringList NotifierAction::autoMimetypes() +{ + return m_autoMimetypes; +} + +bool NotifierAction::isWritable() const +{ + return false; +} + +bool NotifierAction::supportsMimetype(const QString &/*mimetype*/) const +{ + return true; +} + diff --git a/kioslave/media/libmediacommon/notifieraction.h b/kioslave/media/libmediacommon/notifieraction.h new file mode 100644 index 000000000..028ce7433 --- /dev/null +++ b/kioslave/media/libmediacommon/notifieraction.h @@ -0,0 +1,60 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _NOTIFIERACTION_H_ +#define _NOTIFIERACTION_H_ + +#include <kfileitem.h> +#include <qstring.h> +#include <qpixmap.h> + +class NotifierSettings; + +class NotifierAction +{ +public: + NotifierAction(); + virtual ~NotifierAction(); + + virtual QString label() const; + virtual QString iconName() const; + virtual void setLabel( const QString &label ); + virtual void setIconName( const QString &icon ); + + QPixmap pixmap() const; + + QStringList autoMimetypes(); + + virtual QString id() const = 0; + virtual bool isWritable() const; + virtual bool supportsMimetype( const QString &mimetype ) const; + virtual void execute( KFileItem &medium ) = 0; + +private: + void addAutoMimetype( const QString &mimetype ); + void removeAutoMimetype( const QString &mimetype ); + + QString m_label; + QString m_iconName; + QStringList m_autoMimetypes; + + friend class NotifierSettings; +}; + +#endif diff --git a/kioslave/media/libmediacommon/notifiernothingaction.cpp b/kioslave/media/libmediacommon/notifiernothingaction.cpp new file mode 100644 index 000000000..adf7fcf04 --- /dev/null +++ b/kioslave/media/libmediacommon/notifiernothingaction.cpp @@ -0,0 +1,39 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "notifiernothingaction.h" + +#include <klocale.h> + +NotifierNothingAction::NotifierNothingAction() + : NotifierAction() +{ + setIconName("button_cancel"); + setLabel(i18n("Do Nothing")); +} + +QString NotifierNothingAction::id() const +{ + return "#NothinAction"; +} + +void NotifierNothingAction::execute(KFileItem &/*medium*/) +{ +} + diff --git a/kioslave/media/libmediacommon/notifiernothingaction.h b/kioslave/media/libmediacommon/notifiernothingaction.h new file mode 100644 index 000000000..6fd564f61 --- /dev/null +++ b/kioslave/media/libmediacommon/notifiernothingaction.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _NOTIFIERNOTHINGACTION_H_ +#define _NOTIFIERNOTHINGACTION_H_ + +#include "notifieraction.h" + +class NotifierNothingAction : public NotifierAction +{ +public: + NotifierNothingAction(); + virtual QString id() const; + virtual void execute(KFileItem &medium); +}; + +#endif + diff --git a/kioslave/media/libmediacommon/notifieropenaction.cpp b/kioslave/media/libmediacommon/notifieropenaction.cpp new file mode 100644 index 000000000..82db14b97 --- /dev/null +++ b/kioslave/media/libmediacommon/notifieropenaction.cpp @@ -0,0 +1,45 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "notifieropenaction.h" + +#include <klocale.h> + +NotifierOpenAction::NotifierOpenAction() + : NotifierAction() +{ + setIconName("window_new"); + setLabel(i18n("Open in New Window")); +} + +QString NotifierOpenAction::id() const +{ + return "#OpenAction"; +} + +void NotifierOpenAction::execute(KFileItem &medium) +{ + medium.run(); +} + +bool NotifierOpenAction::supportsMimetype( const QString &mimetype ) const +{ + return !mimetype.contains( "blank" ); +} + diff --git a/kioslave/media/libmediacommon/notifieropenaction.h b/kioslave/media/libmediacommon/notifieropenaction.h new file mode 100644 index 000000000..3239e5ca2 --- /dev/null +++ b/kioslave/media/libmediacommon/notifieropenaction.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _NOTIFIEROPENACTION_H_ +#define _NOTIFIEROPENACTION_H_ + +#include "notifieraction.h" + +class NotifierOpenAction : public NotifierAction +{ +public: + NotifierOpenAction(); + virtual QString id() const; + virtual void execute(KFileItem &medium); + bool supportsMimetype( const QString &mimetype ) const; +}; + +#endif diff --git a/kioslave/media/libmediacommon/notifierserviceaction.cpp b/kioslave/media/libmediacommon/notifierserviceaction.cpp new file mode 100644 index 000000000..ee2401945 --- /dev/null +++ b/kioslave/media/libmediacommon/notifierserviceaction.cpp @@ -0,0 +1,166 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "notifierserviceaction.h" + +#include <qdir.h> +#include <qfile.h> +#include <qfileinfo.h> +#include <kstddirs.h> +#include <kdesktopfile.h> +#include <klocale.h> + +NotifierServiceAction::NotifierServiceAction() + : NotifierAction() +{ + NotifierAction::setIconName("button_cancel"); + NotifierAction::setLabel(i18n("Unknown")); + + m_service.m_strName = "New Service"; + m_service.m_strIcon = "button_cancel"; + m_service.m_strExec = "konqueror %u"; +} + +QString NotifierServiceAction::id() const +{ + if (m_filePath.isEmpty() || m_service.m_strName.isEmpty()) + { + return QString(); + } + else + { + return "#Service:"+m_filePath; + } +} + +void NotifierServiceAction::setIconName( const QString &icon ) +{ + m_service.m_strIcon = icon; + NotifierAction::setIconName( icon ); +} + +void NotifierServiceAction::setLabel( const QString &label ) +{ + m_service.m_strName = label; + NotifierAction::setLabel( label ); + + updateFilePath(); +} + +void NotifierServiceAction::execute(KFileItem &medium) +{ + KURL::List urls = KURL::List( medium.url() ); + KDEDesktopMimeType::executeService( urls, m_service ); +} + +void NotifierServiceAction::setService(KDEDesktopMimeType::Service service) +{ + NotifierAction::setIconName( service.m_strIcon ); + NotifierAction::setLabel( service.m_strName ); + + m_service = service; + + updateFilePath(); +} + +KDEDesktopMimeType::Service NotifierServiceAction::service() const +{ + return m_service; +} + +void NotifierServiceAction::setFilePath(const QString &filePath) +{ + m_filePath = filePath; +} + +QString NotifierServiceAction::filePath() const +{ + return m_filePath; +} + +void NotifierServiceAction::updateFilePath() +{ + if ( !m_filePath.isEmpty() ) return; + + QString action_name = m_service.m_strName; + action_name.replace( " ", "_" ); + + QDir actions_dir( locateLocal( "data", "konqueror/servicemenus/", true ) ); + + QString filename = actions_dir.absFilePath( action_name + ".desktop" ); + + int counter = 1; + while ( QFile::exists( filename ) ) + { + filename = actions_dir.absFilePath( action_name + + QString::number( counter ) + + ".desktop" ); + counter++; + } + + m_filePath = filename; +} + +void NotifierServiceAction::setMimetypes(const QStringList &mimetypes) +{ + m_mimetypes = mimetypes; +} + +QStringList NotifierServiceAction::mimetypes() +{ + return m_mimetypes; +} + +bool NotifierServiceAction::isWritable() const +{ + QFileInfo info( m_filePath ); + + if ( info.exists() ) + { + return info.isWritable(); + } + else + { + info = QFileInfo( info.dirPath() ); + return info.isWritable(); + } +} + +bool NotifierServiceAction::supportsMimetype(const QString &mimetype) const +{ + return m_mimetypes.contains(mimetype); +} + +void NotifierServiceAction::save() const +{ + QFile::remove( m_filePath ); + KDesktopFile desktopFile(m_filePath); + + desktopFile.setGroup(QString("Desktop Action ") + m_service.m_strName); + desktopFile.writeEntry(QString("Icon"), m_service.m_strIcon); + desktopFile.writeEntry(QString("Name"), m_service.m_strName); + desktopFile.writeEntry(QString("Exec"), m_service.m_strExec); + + desktopFile.setDesktopGroup(); + + desktopFile.writeEntry(QString("ServiceTypes"), m_mimetypes, ","); + desktopFile.writeEntry(QString("Actions"), + QStringList(m_service.m_strName),";"); +} + diff --git a/kioslave/media/libmediacommon/notifierserviceaction.h b/kioslave/media/libmediacommon/notifierserviceaction.h new file mode 100644 index 000000000..4d61a6b74 --- /dev/null +++ b/kioslave/media/libmediacommon/notifierserviceaction.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _NOTIFIERSERVICEACTION_H_ +#define _NOTIFIERSERVICEACTION_H_ + +#include <kmimetype.h> +#include <qstring.h> + +#include "notifieraction.h" + +class NotifierServiceAction : public NotifierAction +{ +public: + NotifierServiceAction(); + virtual QString id() const; + virtual void execute(KFileItem &item); + + virtual void setIconName( const QString &icon ); + virtual void setLabel( const QString &label ); + + void setService(KDEDesktopMimeType::Service service); + KDEDesktopMimeType::Service service() const; + + void setFilePath(const QString &filePath); + QString filePath() const; + + void setMimetypes(const QStringList &mimetypes); + QStringList mimetypes(); + + virtual bool isWritable() const; + virtual bool supportsMimetype(const QString &mimetype) const; + + void save() const; + +private: + void updateFilePath(); + + KDEDesktopMimeType::Service m_service; + QString m_filePath; + QStringList m_mimetypes; +}; + +#endif + diff --git a/kioslave/media/libmediacommon/notifiersettings.cpp b/kioslave/media/libmediacommon/notifiersettings.cpp new file mode 100644 index 000000000..c7ad616a5 --- /dev/null +++ b/kioslave/media/libmediacommon/notifiersettings.cpp @@ -0,0 +1,379 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "notifiersettings.h" + +#include <kglobal.h> +#include <kdesktopfile.h> +#include <kstandarddirs.h> +#include <qdir.h> +#include <qfile.h> + +#include "notifieropenaction.h" +#include "notifiernothingaction.h" + + +NotifierSettings::NotifierSettings() +{ + m_supportedMimetypes.append( "media/removable_unmounted" ); + m_supportedMimetypes.append( "media/removable_mounted" ); + m_supportedMimetypes.append( "media/camera_unmounted" ); + m_supportedMimetypes.append( "media/camera_mounted" ); + m_supportedMimetypes.append( "media/gphoto2camera" ); + m_supportedMimetypes.append( "media/cdrom_unmounted" ); + m_supportedMimetypes.append( "media/cdrom_mounted" ); + m_supportedMimetypes.append( "media/dvd_unmounted" ); + m_supportedMimetypes.append( "media/dvd_mounted" ); + m_supportedMimetypes.append( "media/cdwriter_unmounted" ); + m_supportedMimetypes.append( "media/cdwriter_mounted" ); + m_supportedMimetypes.append( "media/blankcd" ); + m_supportedMimetypes.append( "media/blankdvd" ); + m_supportedMimetypes.append( "media/audiocd" ); + m_supportedMimetypes.append( "media/dvdvideo" ); + m_supportedMimetypes.append( "media/vcd" ); + m_supportedMimetypes.append( "media/svcd" ); + + reload(); +} + +NotifierSettings::~NotifierSettings() +{ + while ( !m_actions.isEmpty() ) + { + NotifierAction *a = m_actions.first(); + m_actions.remove( a ); + delete a; + } + + while ( !m_deletedActions.isEmpty() ) + { + NotifierServiceAction *a = m_deletedActions.first(); + m_deletedActions.remove( a ); + delete a; + } +} + +QValueList<NotifierAction*> NotifierSettings::actions() +{ + return m_actions; +} + +const QStringList &NotifierSettings::supportedMimetypes() +{ + return m_supportedMimetypes; +} + +QValueList<NotifierAction*> NotifierSettings::actionsForMimetype( const QString &mimetype ) +{ + QValueList<NotifierAction*> result; + + QValueList<NotifierAction*>::iterator it = m_actions.begin(); + QValueList<NotifierAction*>::iterator end = m_actions.end(); + + for ( ; it!=end; ++it ) + { + if ( (*it)->supportsMimetype( mimetype ) ) + { + result.append( *it ); + } + } + + return result; +} + +bool NotifierSettings::addAction( NotifierServiceAction *action ) +{ + if ( !m_idMap.contains( action->id() ) ) + { + m_actions.insert( --m_actions.end(), action ); + m_idMap[ action->id() ] = action; + return true; + } + return false; +} + +bool NotifierSettings::deleteAction( NotifierServiceAction *action ) +{ + if ( action->isWritable() ) + { + m_actions.remove( action ); + m_idMap.remove( action->id() ); + m_deletedActions.append( action ); + + QStringList auto_mimetypes = action->autoMimetypes(); + QStringList::iterator it = auto_mimetypes.begin(); + QStringList::iterator end = auto_mimetypes.end(); + + for ( ; it!=end; ++it ) + { + action->removeAutoMimetype( *it ); + m_autoMimetypesMap.remove( *it ); + } + + return true; + } + return false; +} + +void NotifierSettings::setAutoAction( const QString &mimetype, NotifierAction *action ) +{ + resetAutoAction( mimetype ); + m_autoMimetypesMap[mimetype] = action; + action->addAutoMimetype( mimetype ); +} + + +void NotifierSettings::resetAutoAction( const QString &mimetype ) +{ + if ( m_autoMimetypesMap.contains( mimetype ) ) + { + NotifierAction *action = m_autoMimetypesMap[mimetype]; + action->removeAutoMimetype( mimetype ); + m_autoMimetypesMap.remove(mimetype); + } +} + +void NotifierSettings::clearAutoActions() +{ + QMap<QString,NotifierAction*>::iterator it = m_autoMimetypesMap.begin(); + QMap<QString,NotifierAction*>::iterator end = m_autoMimetypesMap.end(); + + for ( ; it!=end; ++it ) + { + NotifierAction *action = it.data(); + QString mimetype = it.key(); + + if ( action ) + action->removeAutoMimetype( mimetype ); + m_autoMimetypesMap[mimetype] = 0L; + } +} + +NotifierAction *NotifierSettings::autoActionForMimetype( const QString &mimetype ) +{ + if ( m_autoMimetypesMap.contains( mimetype ) ) + { + return m_autoMimetypesMap[mimetype]; + } + else + { + return 0L; + } +} + +void NotifierSettings::reload() +{ + while ( !m_actions.isEmpty() ) + { + NotifierAction *a = m_actions.first(); + m_actions.remove( a ); + delete a; + } + + while ( !m_deletedActions.isEmpty() ) + { + NotifierServiceAction *a = m_deletedActions.first(); + m_deletedActions.remove( a ); + delete a; + } + + m_idMap.clear(); + m_autoMimetypesMap.clear(); + + NotifierOpenAction *open = new NotifierOpenAction(); + m_actions.append( open ); + m_idMap[ open->id() ] = open; + + QValueList<NotifierServiceAction*> services = listServices(); + + QValueList<NotifierServiceAction*>::iterator serv_it = services.begin(); + QValueList<NotifierServiceAction*>::iterator serv_end = services.end(); + + for ( ; serv_it!=serv_end; ++serv_it ) + { + m_actions.append( *serv_it ); + m_idMap[ (*serv_it)->id() ] = *serv_it; + } + + NotifierNothingAction *nothing = new NotifierNothingAction(); + m_actions.append( nothing ); + m_idMap[ nothing->id() ] = nothing; + + KConfig config( "medianotifierrc", true ); + QMap<QString,QString> auto_actions_map = config.entryMap( "Auto Actions" ); + + QMap<QString,QString>::iterator auto_it = auto_actions_map.begin(); + QMap<QString,QString>::iterator auto_end = auto_actions_map.end(); + + for ( ; auto_it!=auto_end; ++auto_it ) + { + QString mime = auto_it.key(); + QString action_id = auto_it.data(); + + if ( m_idMap.contains( action_id ) ) + { + setAutoAction( mime, m_idMap[action_id] ); + } + else + { + config.deleteEntry( mime ); + } + } +} +void NotifierSettings::save() +{ + QValueList<NotifierAction*>::iterator act_it = m_actions.begin(); + QValueList<NotifierAction*>::iterator act_end = m_actions.end(); + + for ( ; act_it!=act_end; ++act_it ) + { + NotifierServiceAction *service; + if ( ( service=dynamic_cast<NotifierServiceAction*>( *act_it ) ) + && service->isWritable() ) + { + service->save(); + } + } + + while ( !m_deletedActions.isEmpty() ) + { + NotifierServiceAction *a = m_deletedActions.first(); + m_deletedActions.remove( a ); + QFile::remove( a->filePath() ); + delete a; + } + + KSimpleConfig config( "medianotifierrc" ); + config.setGroup( "Auto Actions" ); + + QMap<QString,NotifierAction*>::iterator auto_it = m_autoMimetypesMap.begin(); + QMap<QString,NotifierAction*>::iterator auto_end = m_autoMimetypesMap.end(); + + for ( ; auto_it!=auto_end; ++auto_it ) + { + if ( auto_it.data()!=0L ) + { + config.writeEntry( auto_it.key(), auto_it.data()->id() ); + } + else + { + config.deleteEntry( auto_it.key() ); + } + } +} + +QValueList<NotifierServiceAction*> NotifierSettings::loadActions( KDesktopFile &desktop ) const +{ + desktop.setDesktopGroup(); + + QValueList<NotifierServiceAction*> services; + + const QString filename = desktop.fileName(); + const QStringList mimetypes = desktop.readListEntry( "ServiceTypes" ); + + QValueList<KDEDesktopMimeType::Service> type_services + = KDEDesktopMimeType::userDefinedServices(filename, true); + + QValueList<KDEDesktopMimeType::Service>::iterator service_it = type_services.begin(); + QValueList<KDEDesktopMimeType::Service>::iterator service_end = type_services.end(); + for (; service_it!=service_end; ++service_it) + { + NotifierServiceAction *service_action + = new NotifierServiceAction(); + + service_action->setService( *service_it ); + service_action->setFilePath( filename ); + service_action->setMimetypes( mimetypes ); + + services += service_action; + } + + return services; +} + + +bool NotifierSettings::shouldLoadActions( KDesktopFile &desktop, const QString &mimetype ) const +{ + desktop.setDesktopGroup(); + + if ( desktop.hasKey( "Actions" ) + && desktop.hasKey( "ServiceTypes" ) + && !desktop.readBoolEntry( "X-KDE-MediaNotifierHide", false ) ) + { + const QStringList actions = desktop.readListEntry( "Actions" ); + + if ( actions.size()!=1 ) + { + return false; + } + + const QStringList types = desktop.readListEntry( "ServiceTypes" ); + + if ( mimetype.isEmpty() ) + { + QStringList::ConstIterator type_it = types.begin(); + QStringList::ConstIterator type_end = types.end(); + for (; type_it != type_end; ++type_it) + { + if ( (*type_it).startsWith( "media/" ) ) + { + return true; + } + } + } + else if ( types.contains(mimetype) ) + { + return true; + } + } + + return false; +} + +QValueList<NotifierServiceAction*> NotifierSettings::listServices( const QString &mimetype ) const +{ + QValueList<NotifierServiceAction*> services; + QStringList dirs = KGlobal::dirs()->findDirs("data", "konqueror/servicemenus/"); + + QStringList::ConstIterator dir_it = dirs.begin(); + QStringList::ConstIterator dir_end = dirs.end(); + for (; dir_it != dir_end; ++dir_it) + { + QDir dir( *dir_it ); + + QStringList entries = dir.entryList( "*.desktop", QDir::Files ); + + QStringList::ConstIterator entry_it = entries.begin(); + QStringList::ConstIterator entry_end = entries.end(); + + for (; entry_it != entry_end; ++entry_it ) + { + QString filename = *dir_it + *entry_it; + + KDesktopFile desktop( filename, true ); + + if ( shouldLoadActions(desktop, mimetype) ) + { + services+=loadActions(desktop); + } + } + } + + return services; +} diff --git a/kioslave/media/libmediacommon/notifiersettings.h b/kioslave/media/libmediacommon/notifiersettings.h new file mode 100644 index 000000000..6ddee318b --- /dev/null +++ b/kioslave/media/libmediacommon/notifiersettings.h @@ -0,0 +1,63 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _NOTIFIERSETTINGS_H_ +#define _NOTIFIERSETTINGS_H_ + +#include <qvaluelist.h> +#include <qmap.h> + +#include "notifieraction.h" +#include "notifierserviceaction.h" + + +class NotifierSettings +{ +public: + NotifierSettings(); + ~NotifierSettings(); + + QValueList<NotifierAction*> actions(); + QValueList<NotifierAction*> actionsForMimetype( const QString &mimetype ); + + bool addAction( NotifierServiceAction *action ); + bool deleteAction( NotifierServiceAction *action ); + + void setAutoAction( const QString &mimetype, NotifierAction *action ); + void resetAutoAction( const QString &mimetype ); + void clearAutoActions(); + NotifierAction *autoActionForMimetype( const QString &mimetype ); + + const QStringList &supportedMimetypes(); + + void reload(); + void save(); + +private: + QValueList<NotifierServiceAction*> listServices( const QString &mimetype = QString() ) const; + bool shouldLoadActions( KDesktopFile &desktop, const QString &mimetype ) const; + QValueList<NotifierServiceAction*> loadActions( KDesktopFile &desktop ) const; + + QStringList m_supportedMimetypes; + QValueList<NotifierAction*> m_actions; + QValueList<NotifierServiceAction*> m_deletedActions; + QMap<QString,NotifierAction*> m_idMap; + QMap<QString,NotifierAction*> m_autoMimetypesMap; +}; +#endif diff --git a/kioslave/media/media.protocol b/kioslave/media/media.protocol new file mode 100644 index 000000000..053611c1f --- /dev/null +++ b/kioslave/media/media.protocol @@ -0,0 +1,18 @@ +[Protocol] +exec=kio_media +protocol=media +input=none +output=filesystem +listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +Icon=system +maxInstances=4 +#TODO DocPath=kioslave/file.html +Class=:local +deleteRecursive=true +fileNameUsedForCopying=Name diff --git a/kioslave/media/mediaimpl.cpp b/kioslave/media/mediaimpl.cpp new file mode 100644 index 000000000..741227cdb --- /dev/null +++ b/kioslave/media/mediaimpl.cpp @@ -0,0 +1,429 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "mediaimpl.h" + +#include <klocale.h> +#include <kdebug.h> +#include <dcopclient.h> +#include <dcopref.h> +#include <kio/netaccess.h> + +#include <kmimetype.h> + +#include <kapplication.h> +#include <qeventloop.h> + +#include <sys/stat.h> + +#include "medium.h" + +MediaImpl::MediaImpl() : QObject(), DCOPObject("mediaimpl"), mp_mounting(0L) +{ + +} + +bool MediaImpl::parseURL(const KURL &url, QString &name, QString &path) const +{ + QString url_path = url.path(); + + int i = url_path.find('/', 1); + if (i > 0) + { + name = url_path.mid(1, i-1); + path = url_path.mid(i+1); + } + else + { + name = url_path.mid(1); + path = QString::null; + } + + return name != QString::null; +} + +bool MediaImpl::realURL(const QString &name, const QString &path, KURL &url) +{ + bool ok; + Medium m = findMediumByName(name, ok); + if ( !ok ) return false; + + ok = ensureMediumMounted(m); + if ( !ok ) return false; + + url = m.prettyBaseURL(); + url.addPath(path); + return true; +} + + +bool MediaImpl::statMedium(const QString &name, KIO::UDSEntry &entry) +{ + kdDebug(1219) << "MediaImpl::statMedium: " << name << endl; + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "properties", name ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = KIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The KDE mediamanager is not running."); + return false; + } + + Medium m = Medium::create(reply); + + if (m.id().isEmpty()) + { + entry.clear(); + return false; + } + + createMediumEntry(entry, m); + + return true; +} + +bool MediaImpl::statMediumByLabel(const QString &label, KIO::UDSEntry &entry) +{ + kdDebug(1219) << "MediaImpl::statMediumByLabel: " << label << endl; + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "nameForLabel", label ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = KIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The KDE mediamanager is not running."); + return false; + } + + QString name = reply; + + if (name.isEmpty()) + { + entry.clear(); + return false; + } + + return statMedium(name, entry); +} + + +bool MediaImpl::listMedia(QValueList<KIO::UDSEntry> &list) +{ + kdDebug(1219) << "MediaImpl::listMedia" << endl; + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "fullList" ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = KIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The KDE mediamanager is not running."); + return false; + } + + Medium::List media = Medium::createList(reply); + + KIO::UDSEntry entry; + + Medium::List::iterator it = media.begin(); + Medium::List::iterator end = media.end(); + + for(; it!=end; ++it) + { + entry.clear(); + + createMediumEntry(entry, *it); + + list.append(entry); + } + + return true; +} + +bool MediaImpl::setUserLabel(const QString &name, const QString &label) +{ + kdDebug(1219) << "MediaImpl::setUserLabel: " << name << ", " << label << endl; + + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "nameForLabel", label ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = KIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The KDE mediamanager is not running."); + return false; + } + else + { + QString returned_name = reply; + if (!returned_name.isEmpty() + && returned_name!=name) + { + m_lastErrorCode = KIO::ERR_DIR_ALREADY_EXIST; + m_lastErrorMessage = i18n("This media name already exists."); + return false; + } + } + + reply = mediamanager.call( "setUserLabel", name, label ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = KIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The KDE mediamanager is not running."); + return false; + } + else + { + return true; + } +} + +const Medium MediaImpl::findMediumByName(const QString &name, bool &ok) +{ + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "properties", name ); + + if ( reply.isValid() ) + { + ok = true; + } + else + { + m_lastErrorCode = KIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The KDE mediamanager is not running."); + ok = false; + } + + return Medium::create(reply); +} + +bool MediaImpl::ensureMediumMounted(Medium &medium) +{ + if (medium.id().isEmpty()) + { + m_lastErrorCode = KIO::ERR_COULD_NOT_MOUNT; + m_lastErrorMessage = i18n("No such medium."); + return false; + } + + if ( medium.needMounting() ) + { + m_lastErrorCode = 0; + + mp_mounting = &medium; + + + /* + KIO::Job* job = KIO::mount(false, 0, + medium.deviceNode(), + medium.mountPoint()); + job->setAutoWarningHandlingEnabled(false); + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( slotMountResult( KIO::Job * ) ) ); + connect( job, SIGNAL( warning( KIO::Job *, const QString & ) ), + this, SLOT( slotWarning( KIO::Job *, const QString & ) ) ); + */ + kapp->dcopClient() + ->connectDCOPSignal("kded", "mediamanager", + "mediumChanged(QString, bool)", + "mediaimpl", + "slotMediumChanged(QString)", + false); + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "mount", medium.id()); + if (reply.isValid()) + reply.get(m_lastErrorMessage); + else + m_lastErrorMessage = i18n("Internal Error"); + if (!m_lastErrorMessage.isEmpty()) + m_lastErrorCode = KIO::ERR_SLAVE_DEFINED; + else { + qApp->eventLoop()->enterLoop(); + } + + mp_mounting = 0L; + + kapp->dcopClient() + ->disconnectDCOPSignal("kded", "mediamanager", + "mediumChanged(QString, bool)", + "mediaimpl", + "slotMediumChanged(QString)"); + + return m_lastErrorCode==0; + } + + return true; +} + +void MediaImpl::slotWarning( KIO::Job * /*job*/, const QString &msg ) +{ + emit warning( msg ); +} + +void MediaImpl::slotMountResult(KIO::Job *job) +{ + kdDebug(1219) << "MediaImpl::slotMountResult" << endl; + + if ( job->error() != 0) + { + m_lastErrorCode = job->error(); + m_lastErrorMessage = job->errorText(); + qApp->eventLoop()->exitLoop(); + } +} + +void MediaImpl::slotMediumChanged(const QString &name) +{ + kdDebug(1219) << "MediaImpl::slotMediumChanged:" << name << endl; + + if (mp_mounting->name()==name) + { + kdDebug(1219) << "MediaImpl::slotMediumChanged: updating mp_mounting" << endl; + bool ok; + *mp_mounting = findMediumByName(name, ok); + qApp->eventLoop()->exitLoop(); + } +} + +static void addAtom(KIO::UDSEntry &entry, unsigned int ID, long l, + const QString &s = QString::null) +{ + KIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void MediaImpl::createTopLevelEntry(KIO::UDSEntry& entry) const +{ + entry.clear(); + addAtom(entry, KIO::UDS_URL, 0, "media:/"); + addAtom(entry, KIO::UDS_NAME, 0, "."); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_ACCESS, 0555); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, KIO::UDS_ICON_NAME, 0, "blockdevice"); +} + +void MediaImpl::slotStatResult(KIO::Job *job) +{ + if ( job->error() == 0) + { + KIO::StatJob *stat_job = static_cast<KIO::StatJob *>(job); + m_entryBuffer = stat_job->statResult(); + } + + qApp->eventLoop()->exitLoop(); +} + +KIO::UDSEntry MediaImpl::extractUrlInfos(const KURL &url) +{ + m_entryBuffer.clear(); + + KIO::StatJob *job = KIO::stat(url, false); + job->setAutoWarningHandlingEnabled( false ); + connect( job, SIGNAL( result(KIO::Job *) ), + this, SLOT( slotStatResult(KIO::Job *) ) ); + connect( job, SIGNAL( warning( KIO::Job *, const QString & ) ), + this, SLOT( slotWarning( KIO::Job *, const QString & ) ) ); + qApp->eventLoop()->enterLoop(); + + KIO::UDSEntry::iterator it = m_entryBuffer.begin(); + KIO::UDSEntry::iterator end = m_entryBuffer.end(); + + KIO::UDSEntry infos; + + for(; it!=end; ++it) + { + switch( (*it).m_uds ) + { + case KIO::UDS_ACCESS: + case KIO::UDS_USER: + case KIO::UDS_GROUP: + case KIO::UDS_CREATION_TIME: + case KIO::UDS_MODIFICATION_TIME: + case KIO::UDS_ACCESS_TIME: + infos.append(*it); + break; + default: + break; + } + } + + if (url.isLocalFile()) + { + addAtom(infos, KIO::UDS_LOCAL_PATH, 0, url.path()); + } + + return infos; +} + + +void MediaImpl::createMediumEntry(KIO::UDSEntry& entry, + const Medium &medium) +{ + kdDebug(1219) << "MediaProtocol::createMedium" << endl; + + QString url = "media:/"+medium.name(); + + kdDebug(1219) << "url = " << url << ", mime = " << medium.mimeType() << endl; + + entry.clear(); + + addAtom(entry, KIO::UDS_URL, 0, url); + + QString label = KIO::encodeFileName( medium.prettyLabel() ); + addAtom(entry, KIO::UDS_NAME, 0, label); + + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + + addAtom(entry, KIO::UDS_MIME_TYPE, 0, medium.mimeType()); + addAtom(entry, KIO::UDS_GUESSED_MIME_TYPE, 0, "inode/directory"); + + if (!medium.iconName().isEmpty()) + { + addAtom(entry, KIO::UDS_ICON_NAME, 0, medium.iconName()); + } + else + { + QString mime = medium.mimeType(); + QString icon = KMimeType::mimeType(mime)->icon(mime, false); + addAtom(entry, KIO::UDS_ICON_NAME, 0, icon); + } + + if (medium.needMounting()) + { + addAtom(entry, KIO::UDS_ACCESS, 0400); + } + else + { + KURL url = medium.prettyBaseURL(); + entry+= extractUrlInfos(url); + } +} + +#include "mediaimpl.moc" diff --git a/kioslave/media/mediaimpl.h b/kioslave/media/mediaimpl.h new file mode 100644 index 000000000..2d82aefda --- /dev/null +++ b/kioslave/media/mediaimpl.h @@ -0,0 +1,81 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _MEDIAIMPL_H_ +#define _MEDIAIMPL_H_ + +#include <kio/global.h> +#include <kio/job.h> +#include <kurl.h> +#include <dcopobject.h> + +#include <qobject.h> +#include <qstring.h> + +#include "medium.h" + +class MediaImpl : public QObject, public DCOPObject +{ +Q_OBJECT +K_DCOP +public: + MediaImpl(); + bool parseURL(const KURL &url, QString &name, QString &path) const; + bool realURL(const QString &name, const QString &path, KURL &url); + + bool statMedium(const QString &name, KIO::UDSEntry &entry); + bool statMediumByLabel(const QString &label, KIO::UDSEntry &entry); + bool listMedia(QValueList<KIO::UDSEntry> &list); + bool setUserLabel(const QString &name, const QString &label); + + void createTopLevelEntry(KIO::UDSEntry& entry) const; + + int lastErrorCode() const { return m_lastErrorCode; } + QString lastErrorMessage() const { return m_lastErrorMessage; } + +k_dcop: + void slotMediumChanged(const QString &name); + +signals: + void warning(const QString &msg); + +private slots: + void slotWarning(KIO::Job *job, const QString &msg); + void slotMountResult(KIO::Job *job); + void slotStatResult(KIO::Job *job); + +private: + const Medium findMediumByName(const QString &name, bool &ok); + bool ensureMediumMounted(Medium &medium); + + KIO::UDSEntry extractUrlInfos(const KURL &url); + KIO::UDSEntry m_entryBuffer; + + void createMediumEntry(KIO::UDSEntry& entry, + const Medium &medium); + + Medium *mp_mounting; + + /// Last error code stored in class to simplify API. + /// Note that this means almost no method can be const. + int m_lastErrorCode; + QString m_lastErrorMessage; +}; + +#endif diff --git a/kioslave/media/mediamanager/Makefile.am b/kioslave/media/mediamanager/Makefile.am new file mode 100644 index 000000000..45289a9eb --- /dev/null +++ b/kioslave/media/mediamanager/Makefile.am @@ -0,0 +1,32 @@ +kde_module_LTLIBRARIES = kded_mediamanager.la + +if include_media_halbackend +HALBACKEND_INCS = $(HAL_INCS) $(DBUS_INCS) $(DBUSQT_INCS) +endif + +METASOURCES = AUTO +INCLUDES = -I$(srcdir)/../libmediacommon -I../libmediacommon $(HALBACKEND_INCS) $(all_includes) + +if include_media_halbackend +HALBACKEND_LIB = libhalbackend.la +libhalbackend_la_SOURCES = halbackend.cpp +libhalbackend_la_LDFLAGS = -avoid-version $(all_libraries) -no-undefined +libhalbackend_la_LIBADD = $(HAL_LIBS) $(DBUS_LIBS) $(DBUSQT_LIBS) +endif + +if include_media_linuxcdpolling +LINUXCDPOLLING_LIB = liblinuxcdpolling.la +liblinuxcdpolling_la_SOURCES = linuxcdpolling.cpp +liblinuxcdpolling_la_LDFLAGS = -avoid-version $(all_libraries) -no-undefined +endif + +noinst_LTLIBRARIES = $(LINUXCDPOLLING_LIB) $(HALBACKEND_LIB) + +kded_mediamanager_la_SOURCES = mediamanager.cpp mediamanager.skel medialist.cpp backendbase.cpp fstabbackend.cpp removablebackend.cpp mediadirnotify.cpp mediadirnotify.skel +kded_mediamanager_la_LDFLAGS = $(all_libraries) -module -avoid-version +kded_mediamanager_la_LIBADD = $(LIB_KSYCOCA) ../libmediacommon/libmediacommon.la $(HALBACKEND_LIB) $(LINUXCDPOLLING_LIB) + + +servicesdir = $(kde_servicesdir)/kded +services_DATA = mediamanager.desktop + diff --git a/kioslave/media/mediamanager/backendbase.cpp b/kioslave/media/mediamanager/backendbase.cpp new file mode 100644 index 000000000..157acfca5 --- /dev/null +++ b/kioslave/media/mediamanager/backendbase.cpp @@ -0,0 +1,26 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "backendbase.h" + +BackendBase::BackendBase(MediaList &list) + : m_mediaList(list) +{ + +} + diff --git a/kioslave/media/mediamanager/backendbase.h b/kioslave/media/mediamanager/backendbase.h new file mode 100644 index 000000000..689522d1f --- /dev/null +++ b/kioslave/media/mediamanager/backendbase.h @@ -0,0 +1,35 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _BACKENDBASE_H_ +#define _BACKENDBASE_H_ + +#include "medialist.h" + +class BackendBase +{ +protected: + BackendBase(MediaList &list); +public: + virtual ~BackendBase() { } + +protected: + MediaList &m_mediaList; +}; + +#endif diff --git a/kioslave/media/mediamanager/fstabbackend.cpp b/kioslave/media/mediamanager/fstabbackend.cpp new file mode 100644 index 000000000..4ffcf4b2d --- /dev/null +++ b/kioslave/media/mediamanager/fstabbackend.cpp @@ -0,0 +1,483 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + Linux CD/DVD detection + Copyright (c) 2005 Bernhard Rosenkraenzer <bero arklinux 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 "fstabbackend.h" + +#ifdef __linux__ +// For CD/DVD drive detection +#include <fcntl.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <stdint.h> +#define CDROM_GET_CAPABILITY 0x5331 +#define CDSL_CURRENT ((int) (~0U>>1)) +#define CDC_DVD_R 0x10000 /* drive can write DVD-R */ +#define CDC_DVD_RAM 0x20000 /* drive can write DVD-RAM */ +#define CDC_CD_R 0x2000 /* drive is a CD-R */ +#define CDC_CD_RW 0x4000 /* drive is a CD-RW */ +#define CDC_DVD 0x8000 /* drive is a DVD */ +#include <qfile.h> +#endif + +#include <klocale.h> +#include <kio/job.h> +#include <kio/netaccess.h> +#include <kdebug.h> +#include <kdirwatch.h> +#include <kurl.h> +#include <kmountpoint.h> +#include <kstandarddirs.h> + +#ifdef _OS_SOLARIS_ +#define FSTAB "/etc/vfstab" +#define MTAB "/etc/mnttab" +#else +#define FSTAB "/etc/fstab" +#define MTAB "/etc/mtab" +#endif + + + +FstabBackend::FstabBackend(MediaList &list, bool networkSharesOnly) + : QObject(), BackendBase(list), m_networkSharesOnly(networkSharesOnly) +{ + KDirWatch::self()->addFile(MTAB); + KDirWatch::self()->addFile(FSTAB); + + connect( KDirWatch::self(), SIGNAL( dirty(const QString&) ), + this, SLOT( slotDirty(const QString&) ) ); + + handleFstabChange(false); + handleMtabChange(false); + + KDirWatch::self()->startScan(); + +#ifdef Q_OS_FREEBSD + connect( &m_mtabTimer, SIGNAL( timeout() ), + this, SLOT( handleMtabChange() ) ); + m_mtabTimer.start(250); +#endif +} + +FstabBackend::~FstabBackend() +{ + QStringList::iterator it = m_mtabIds.begin(); + QStringList::iterator end = m_mtabIds.end(); + + for (; it!=end; ++it) + { + m_mediaList.removeMedium(*it, false); + } + + it = m_fstabIds.begin(); + end = m_fstabIds.end(); + + for (; it!=end; ++it) + { + m_mediaList.removeMedium(*it, false); + } + KDirWatch::self()->removeFile(FSTAB); + KDirWatch::self()->removeFile(MTAB); +} + +QString FstabBackend::mount( const QString &_udi ) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + KIO::Job* job = KIO::mount( false, 0, medium->deviceNode(), medium->mountPoint()); + KIO::NetAccess::synchronousRun( job, 0 ); + return QString::null; +} + +QString FstabBackend::unmount( const QString &_udi ) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + KIO::Job* job = KIO::unmount( medium->mountPoint(), false); + KIO::NetAccess::synchronousRun( job, 0 ); + return QString::null; +} + +void FstabBackend::slotDirty(const QString &path) +{ + if (path==MTAB) + { + handleMtabChange(); + } + else if (path==FSTAB) + { + handleFstabChange(); + } +} + +bool inExclusionPattern(KMountPoint *mount, bool networkSharesOnly) +{ + if ( mount->mountType() == "swap" + || mount->mountType() == "tmpfs" + || mount->mountType() == "sysfs" + || mount->mountType() == "fdescfs" + || mount->mountType() == "kernfs" + || mount->mountType() == "usbfs" + || mount->mountType().contains( "proc" ) + || mount->mountType() == "unknown" + || mount->mountType() == "none" + || mount->mountType() == "sunrpc" + || mount->mountedFrom() == "none" + || mount->mountedFrom() == "tmpfs" + || mount->mountedFrom().find("shm") != -1 + || mount->mountPoint() == "/dev/swap" + || mount->mountPoint() == "/dev/pts" + || mount->mountPoint().find("/proc") == 0 + || mount->mountPoint().find("/sys") == 0 + + // We might want to display only network shares + // since HAL doesn't handle them + || ( networkSharesOnly + && mount->mountType().find( "smb" ) == -1 + && mount->mountType().find( "cifs" ) == -1 + && mount->mountType().find( "nfs" ) == -1 + ) + ) + { + return true; + } + else + { + return false; + } +} + + +void FstabBackend::handleMtabChange(bool allowNotification) +{ + QStringList new_mtabIds; + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + + KMountPoint::List::iterator it = mtab.begin(); + KMountPoint::List::iterator end = mtab.end(); + + for (; it!=end; ++it) + { + QString dev = (*it)->mountedFrom(); + QString mp = (*it)->mountPoint(); + QString fs = (*it)->mountType(); + + if ( ::inExclusionPattern(*it, m_networkSharesOnly) ) continue; + + /* Did we know this already before ? If yes, then + nothing has changed, do not stat the mount point. Avoids + hang if network shares are stalling */ + QString mtabEntry = dev + "*" + mp + "*" + fs; + if(m_mtabEntries.contains(mtabEntry)) { + new_mtabIds += m_mtabEntries[mtabEntry]; + continue; + } + + QString id = generateId(dev, mp); + new_mtabIds+=id; + m_mtabEntries[mtabEntry] = id; + + if ( !m_mtabIds.contains(id) && m_fstabIds.contains(id) ) + { + QString mime, icon, label; + guess(dev, mp, fs, true, mime, icon, label); + m_mediaList.changeMediumState(id, true, false, + mime, icon, label); + } +#if 0 + else if ( !m_mtabIds.contains(id) ) + { + QString name = generateName(dev, fs); + + Medium *m = new Medium(id, name); + + m->mountableState(dev, mp, fs, true); + + QString mime, icon, label; + guess(dev, mp, fs, true, mime, icon, label); + + m->setMimeType(mime); + m->setIconName(icon); + m->setLabel(label); + + m_mediaList.addMedium(m, notificationAllowed); + } +#endif + } + + QStringList::iterator it2 = m_mtabIds.begin(); + QStringList::iterator end2 = m_mtabIds.end(); + + for (; it2!=end2; ++it2) + { + if ( !new_mtabIds.contains(*it2) && m_fstabIds.contains(*it2) ) + { + const Medium *medium = m_mediaList.findById(*it2); + + QString dev = medium->deviceNode(); + QString mp = medium->mountPoint(); + QString fs = medium->fsType(); + + + QString mtabEntry = dev + "*" + mp + "*" + fs; + m_mtabEntries.remove(mtabEntry); + + QString mime, icon, label; + guess(dev, mp, fs, false, mime, icon, label); + + m_mediaList.changeMediumState(*it2, false, false, + mime, icon, label); + } +#if 0 + else if ( !new_mtabIds.contains(*it2) ) + { + m_mediaList.removeMedium(*it2, allowNotification); + } +#endif + } + + m_mtabIds = new_mtabIds; +} + +void FstabBackend::handleFstabChange(bool allowNotification) +{ + QStringList new_fstabIds; + KMountPoint::List fstab = KMountPoint::possibleMountPoints(); + + KMountPoint::List::iterator it = fstab.begin(); + KMountPoint::List::iterator end = fstab.end(); + + for (; it!=end; ++it) + { + QString dev = (*it)->mountedFrom(); + QString mp = (*it)->mountPoint(); + QString fs = (*it)->mountType(); + + if ( ::inExclusionPattern(*it, m_networkSharesOnly) ) continue; + + QString id = generateId(dev, mp); + new_fstabIds+=id; + + if ( !m_fstabIds.contains(id) ) + { + QString name = generateName(dev, fs); + + Medium *m = new Medium(id, name); + + m->mountableState(dev, mp, fs, false); + + QString mime, icon, label; + guess(dev, mp, fs, false, mime, icon, label); + + m->setMimeType(mime); + m->setIconName(icon); + m->setLabel(label); + + m_mediaList.addMedium(m, allowNotification); + } + } + + QStringList::iterator it2 = m_fstabIds.begin(); + QStringList::iterator end2 = m_fstabIds.end(); + + for (; it2!=end2; ++it2) + { + if ( !new_fstabIds.contains(*it2) ) + { + m_mediaList.removeMedium(*it2, allowNotification); + } + } + + m_fstabIds = new_fstabIds; +} + +QString FstabBackend::generateId(const QString &devNode, + const QString &mountPoint) +{ + QString d = KStandardDirs::realFilePath(devNode); + QString m = KStandardDirs::realPath(mountPoint); + + return "/org/kde/mediamanager/fstab/" + +d.replace("/", "") + +m.replace("/", ""); +} + +QString FstabBackend::generateName(const QString &devNode, const QString &fsType) +{ + KURL url( devNode ); + + if ( url.isValid() ) + { + return url.fileName(); + } + else // surely something nfs or samba based + { + return fsType; + } +} + +void FstabBackend::guess(const QString &devNode, const QString &mountPoint, + const QString &fsType, bool mounted, + QString &mimeType, QString &iconName, QString &label) +{ + enum { UNKNOWN, CD, CDWRITER, DVD, DVDWRITER } devType = UNKNOWN; +#ifdef __linux__ + // Guessing device types by mount point is not exactly accurate... + // Do something accurate first, and fall back if necessary. + int device=open(QFile::encodeName(devNode), O_RDONLY|O_NONBLOCK); + if(device>=0) + { + bool isCd=false; + QString devname=devNode.section('/', -1); + if(devname.startsWith("scd") || devname.startsWith("sr")) + { + // SCSI CD/DVD drive + isCd=true; + } + else if(devname.startsWith("hd")) + { + // IDE device -- we can't tell if this is a + // CD/DVD drive or harddisk by just looking at the + // filename + QFile m(QString("/proc/ide/") + devname + "/media"); + if(m.open(IO_ReadOnly)) + { + QString buf; + m.readLine(buf, 1024); + if(buf.contains("cdrom")) + isCd=true; + m.close(); + } + } + if(isCd) + { + int drv=ioctl(device, CDROM_GET_CAPABILITY, CDSL_CURRENT); + if(drv>=0) + { + if((drv & CDC_DVD_R) || (drv & CDC_DVD_RAM)) + devType = DVDWRITER; + else if((drv & CDC_CD_R) || (drv & CDC_CD_RW)) + devType = CDWRITER; + else if(drv & CDC_DVD) + devType = DVD; + else + devType = CD; + } + } + close(device); + } +#endif + if ( devType == CDWRITER + || devNode.find("cdwriter")!=-1 || mountPoint.find("cdwriter")!=-1 + || devNode.find("cdrecorder")!=-1 || mountPoint.find("cdrecorder")!=-1 + || devNode.find("cdburner")!=-1 || mountPoint.find("cdburner")!=-1 + || devNode.find("cdrw")!=-1 || mountPoint.find("cdrw")!=-1 + || devNode.find("graveur")!=-1 + ) + { + mimeType = "media/cdwriter"; + label = i18n("CD Recorder"); + } + else if ( devType == DVD || devType == DVDWRITER + || devNode.find("dvd")!=-1 || mountPoint.find("dvd")!=-1 ) + { + mimeType = "media/dvd"; + label = i18n("DVD"); + } + else if ( devType == CD + || devNode.find("cdrom")!=-1 || mountPoint.find("cdrom")!=-1 + // LINUX SPECIFIC + || devNode.find("/dev/scd")!=-1 || devNode.find("/dev/sr")!=-1 + // FREEBSD SPECIFIC + || devNode.find("/acd")!=-1 || devNode.find("/scd")!=-1 + ) + { + mimeType = "media/cdrom"; + label = i18n("CD-ROM"); + } + else if ( devNode.find("fd")!=-1 || mountPoint.find("fd")!=-1 + || devNode.find("floppy")!=-1 || mountPoint.find("floppy")!=-1 ) + { + if ( devNode.find("360")!=-1 || devNode.find("1200")!=-1 ) + { + mimeType = "media/floppy5"; + } + else + { + mimeType = "media/floppy"; + } + label = i18n("Floppy"); + } + else if ( mountPoint.find("zip")!=-1 + // FREEBSD SPECIFIC + || devNode.find("/afd")!=-1 + ) + { + mimeType = "media/zip"; + label = i18n("Zip Disk"); + } + else if ( mountPoint.find("removable")!=-1 + || mountPoint.find("hotplug")!=-1 + || mountPoint.find("usb")!=-1 + || mountPoint.find("firewire")!=-1 + || mountPoint.find("ieee1394")!=-1 + || devNode.find("/usb/")!= -1 + ) + { + mimeType = "media/removable"; + label = i18n("Removable Device"); + } + else if ( fsType.find("nfs")!=-1 ) + { + mimeType = "media/nfs"; + label = i18n("Remote Share"); + } + else if ( fsType.find("smb")!=-1 || fsType.find("cifs")!=-1 + || devNode.find("//")!=-1 ) + { + mimeType = "media/smb"; + label = i18n("Remote Share"); + } + else + { + mimeType = "media/hdd"; + label = i18n("Hard Disk"); + } + + if ( mimeType=="media/nfs" || mimeType=="media/smb" ) + { + label+= " (" + devNode + ")"; + } + else + { + QString tmp = devNode; + if ( tmp.startsWith("/dev/") ) + { + tmp = tmp.mid(5); + } + label+= " (" + tmp + ")"; + } + mimeType+= (mounted ? "_mounted" : "_unmounted"); + iconName = QString::null; +} + +#include "fstabbackend.moc" diff --git a/kioslave/media/mediamanager/fstabbackend.h b/kioslave/media/mediamanager/fstabbackend.h new file mode 100644 index 000000000..ceb2f23ef --- /dev/null +++ b/kioslave/media/mediamanager/fstabbackend.h @@ -0,0 +1,68 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _FSTABBACKEND_H_ +#define _FSTABBACKEND_H_ + +#include "backendbase.h" + +#include <qobject.h> +#include <qstringlist.h> +#include <qmap.h> + +#ifdef Q_OS_FREEBSD +#include <qtimer.h> +#endif + +class FstabBackend : public QObject, public BackendBase +{ +Q_OBJECT + +public: + FstabBackend(MediaList &list, bool networkSharesOnly = false); + virtual ~FstabBackend(); + + static void guess(const QString &devNode, const QString &mountPoint, + const QString &fsType, bool mounted, + QString &mimeType, QString &iconName, + QString &label); + + QString mount(const QString &id); + QString unmount(const QString &id); + +private slots: + void slotDirty(const QString &path); + void handleFstabChange(bool allowNotification = true); + void handleMtabChange(bool allowNotification = true); + +private: + static QString generateId(const QString &devNode, + const QString &mountPoint); + static QString generateName(const QString &devNode, + const QString &fsType); + + bool m_networkSharesOnly; + QStringList m_mtabIds; + QMap<QString, QString> m_mtabEntries; + QStringList m_fstabIds; +#ifdef Q_OS_FREEBSD + QTimer m_mtabTimer; +#endif +}; + +#endif diff --git a/kioslave/media/mediamanager/halbackend.cpp b/kioslave/media/mediamanager/halbackend.cpp new file mode 100644 index 000000000..65c796605 --- /dev/null +++ b/kioslave/media/mediamanager/halbackend.cpp @@ -0,0 +1,1345 @@ +/* This file is part of the KDE Project + Copyright (c) 2004-2005 Jérôme Lodewyck <jerome dot lodewyck at normalesup dot 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 "halbackend.h" +#include "linuxcdpolling.h" + +#include <stdlib.h> + +#include <kapplication.h> +#include <qeventloop.h> +#include <qfile.h> +#include <klocale.h> +#include <kurl.h> +#include <kdebug.h> +#include <kprocess.h> +#include <kconfig.h> +#include <qstylesheet.h> +#include <kmountpoint.h> +#include <kmessagebox.h> +#include <kio/job.h> + +#define MOUNT_SUFFIX (libhal_volume_is_mounted(halVolume) ? QString("_mounted") : QString("_unmounted")) +#define MOUNT_ICON_SUFFIX (libhal_volume_is_mounted(halVolume) ? QString("_mount") : QString("_unmount")) + +/* Static instance of this class, for static HAL callbacks */ +static HALBackend* s_HALBackend; + +/* A macro function to convert HAL string properties to QString */ +QString libhal_device_get_property_QString(LibHalContext *ctx, const char* udi, const char *key) +{ + char* _ppt_string; + QString _ppt_QString; + DBusError error; + dbus_error_init(&error); + _ppt_string = libhal_device_get_property_string(ctx, udi, key, &error); + if ( _ppt_string ) + _ppt_QString = _ppt_string; + libhal_free_string(_ppt_string); + return _ppt_QString; +} + +/* Constructor */ +HALBackend::HALBackend(MediaList &list, QObject* parent) + : QObject() + , BackendBase(list) + , m_halContext(NULL) + , m_halStoragePolicy(NULL) + , m_parent(parent) +{ + s_HALBackend = this; +} + +/* Destructor */ +HALBackend::~HALBackend() +{ + /* Close HAL connection */ + if (m_halContext) + { + const QPtrList<Medium> medlist = m_mediaList.list(); + QPtrListIterator<Medium> it (medlist); + for ( const Medium *current_medium = it.current(); current_medium; current_medium = ++it) + { + if( !current_medium->id().startsWith( "/org/kde" )) + unmount(current_medium->id()); + } + + + /* Remove all the registered media first */ + int numDevices; + char** halDeviceList = libhal_get_all_devices( m_halContext, &numDevices, NULL ); + + if ( halDeviceList ) + { + for ( int i = 0; i < numDevices; i++ ) + { + m_mediaList.removeMedium( halDeviceList[i], false ); + } + } + + libhal_free_string_array( halDeviceList ); + + DBusError error; + dbus_error_init(&error); + libhal_ctx_shutdown(m_halContext, &error); + libhal_ctx_free(m_halContext); + } + + if (m_halStoragePolicy) + libhal_storage_policy_free(m_halStoragePolicy); +} + +/* Connect to the HAL */ +bool HALBackend::InitHal() +{ + kdDebug(1219) << "Context new" << endl; + m_halContext = libhal_ctx_new(); + if (!m_halContext) + { + kdDebug(1219) << "Failed to initialize HAL!" << endl; + return false; + } + + // Main loop integration + kdDebug(1219) << "Main loop integration" << endl; + DBusError error; + dbus_error_init(&error); + dbus_connection = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + + if (!dbus_connection || dbus_error_is_set(&error)) { + dbus_error_free(&error); + libhal_ctx_free(m_halContext); + m_halContext = NULL; + return false; + } + + dbus_connection_set_exit_on_disconnect (dbus_connection, FALSE); + + MainLoopIntegration(dbus_connection); + libhal_ctx_set_dbus_connection(m_halContext, dbus_connection); + + // HAL callback functions + kdDebug(1219) << "Callback functions" << endl; + libhal_ctx_set_device_added(m_halContext, HALBackend::hal_device_added); + libhal_ctx_set_device_removed(m_halContext, HALBackend::hal_device_removed); + libhal_ctx_set_device_new_capability (m_halContext, NULL); + libhal_ctx_set_device_lost_capability (m_halContext, NULL); + libhal_ctx_set_device_property_modified (m_halContext, HALBackend::hal_device_property_modified); + libhal_ctx_set_device_condition(m_halContext, HALBackend::hal_device_condition); + + kdDebug(1219) << "Context Init" << endl; + if (!libhal_ctx_init(m_halContext, &error)) + { + if (dbus_error_is_set(&error)) + dbus_error_free(&error); + libhal_ctx_free(m_halContext); + m_halContext = NULL; + kdDebug(1219) << "Failed to init HAL context!" << endl; + return false; + } + + /** @todo customize watch policy */ + kdDebug(1219) << "Watch properties" << endl; + if (!libhal_device_property_watch_all(m_halContext, &error)) + { + kdDebug(1219) << "Failed to watch HAL properties!" << endl; + return false; + } + + /* libhal-storage initialization */ + kdDebug(1219) << "Storage Policy" << endl; + m_halStoragePolicy = libhal_storage_policy_new(); + /** @todo define libhal-storage icon policy */ + + /* List devices at startup */ + return ListDevices(); +} + +/* List devices (at startup)*/ +bool HALBackend::ListDevices() +{ + kdDebug(1219) << "ListDevices" << endl; + + int numDevices; + char** halDeviceList = libhal_get_all_devices(m_halContext, &numDevices, NULL); + + if (!halDeviceList) + return false; + + kdDebug(1219) << "HALBackend::ListDevices : " << numDevices << " devices found" << endl; + for (int i = 0; i < numDevices; i++) + AddDevice(halDeviceList[i], false); + + libhal_free_string_array( halDeviceList ); + + return true; +} + +/* Create a media instance for the HAL device "udi". + This functions checks whether the device is worth listing */ +void HALBackend::AddDevice(const char *udi, bool allowNotification) +{ + /* We don't deal with devices that do not expose their capabilities. + If we don't check this, we will get a lot of warning messages from libhal */ + if (!libhal_device_property_exists(m_halContext, udi, "info.capabilities", NULL)) + return; + + /* If the device is already listed, do not process. + This should not happen, but who knows... */ + /** @todo : refresh properties instead ? */ + if (m_mediaList.findById(udi)) + return; + + if (libhal_device_get_property_bool(m_halContext, "/org/freedesktop/Hal/devices/computer", "storage.disable_volume_handling", NULL)) + allowNotification=false; + + /* Add volume block devices */ + if (libhal_device_query_capability(m_halContext, udi, "volume", NULL)) + { + /* We only list volume that have a filesystem or volume that have an audio track*/ + if ( libhal_device_get_property_QString(m_halContext, udi, "volume.fsusage") != "filesystem" && + !libhal_device_get_property_bool(m_halContext, udi, "volume.disc.has_audio", NULL) && + !libhal_device_get_property_bool(m_halContext, udi, "volume.disc.is_blank", NULL) ) + return; + + /* Query drive udi */ + QString driveUdi = libhal_device_get_property_QString(m_halContext, udi, "block.storage_device"); + if ( driveUdi.isNull() ) // no storage - no fun + return; + + // if the device is locked do not act upon it + if (libhal_device_get_property_bool(m_halContext, driveUdi.ascii(), "info.locked", NULL)) + allowNotification=false; + + // if the device is locked do not act upon it + if (libhal_device_get_property_bool(m_halContext, driveUdi.ascii(), "storage.partition_table_changed", NULL)) + allowNotification=false; + + /** @todo check exclusion list **/ + + /* Create medium */ + Medium* medium = new Medium(udi, ""); + setVolumeProperties(medium); + + if ( isInFstab( medium ).isNull() ) + { + // if it's not mountable by user and not by HAL, don't show it at all + if ( ( libhal_device_get_property_QString(m_halContext, udi, "volume.fsusage") == "filesystem" && + !libhal_device_get_property_bool(m_halContext, udi, "volume.is_mounted", NULL ) ) && + ( libhal_device_get_property_bool(m_halContext, udi, "volume.ignore", NULL ) ) ) + { + delete medium; + return; + } + } + QMap<QString,QString> options = MediaManagerUtils::splitOptions(mountoptions(udi)); + kdDebug() << "automount " << options["automount"] << endl; + if (options["automount"] == "true" && allowNotification ) { + QString error = mount(medium); + if (!error.isEmpty()) + kdDebug() << "error " << error << endl; + } + m_mediaList.addMedium(medium, allowNotification); + + return; + } + + /* Floppy & zip drives */ + if (libhal_device_query_capability(m_halContext, udi, "storage", NULL)) + if ((libhal_device_get_property_QString(m_halContext, udi, "storage.drive_type") == "floppy") || + (libhal_device_get_property_QString(m_halContext, udi, "storage.drive_type") == "zip") || + (libhal_device_get_property_QString(m_halContext, udi, "storage.drive_type") == "jaz")) + { + if (! libhal_device_get_property_bool(m_halContext, udi, "storage.removable.media_available", NULL) ) + allowNotification = false; + /* Create medium */ + Medium* medium = new Medium(udi, ""); + // if the storage has a volume, we ignore it + if ( setFloppyProperties(medium) ) + m_mediaList.addMedium(medium, allowNotification); + else + delete medium; + return; + } + + /* Camera handled by gphoto2*/ + if (libhal_device_query_capability(m_halContext, udi, "camera", NULL) && + ((libhal_device_get_property_QString(m_halContext, udi, "camera.access_method")=="ptp") || + + (libhal_device_property_exists(m_halContext, udi, "camera.libgphoto2.support", NULL) && + libhal_device_get_property_bool(m_halContext, udi, "camera.libgphoto2.support", NULL))) + ) + { + /* Create medium */ + Medium* medium = new Medium(udi, ""); + setCameraProperties(medium); + m_mediaList.addMedium(medium, allowNotification); + return; + } +} + +void HALBackend::RemoveDevice(const char *udi) +{ + m_mediaList.removeMedium(udi, true); +} + +void HALBackend::ModifyDevice(const char *udi, const char* key) +{ + const char* mediumUdi = findMediumUdiFromUdi(udi); + if (!mediumUdi) + return; + bool allowNotification = false; + if (strcmp(key, "storage.removable.media_available") == 0) + allowNotification = libhal_device_get_property_bool(m_halContext, udi, key, NULL); + ResetProperties(mediumUdi, allowNotification); +} + +void HALBackend::DeviceCondition(const char* udi, const char* condition) +{ + QString conditionName = QString(condition); + kdDebug(1219) << "Processing device condition " << conditionName << " for " << udi << endl; + + if (conditionName == "EjectPressed") { + const Medium* medium = m_mediaList.findById(udi); + if (!medium) { + /* the ejectpressed appears on the drive and we need to find the volume */ + const QPtrList<Medium> medlist = m_mediaList.list(); + QPtrListIterator<Medium> it (medlist); + for ( const Medium *current_medium = it.current(); current_medium; current_medium = ++it) + { + if( current_medium->id().startsWith( "/org/kde" )) + continue; + QString driveUdi = libhal_device_get_property_QString(m_halContext, current_medium->id().latin1(), "block.storage_device"); + if (driveUdi == udi) + { + medium = current_medium; + break; + } + } + } + if (medium) { + KProcess p; + p << "kio_media_mounthelper" << "-e" << medium->name(); + p.start(KProcess::DontCare); + } + } + + const char* mediumUdi = findMediumUdiFromUdi(udi); + kdDebug() << "findMedumUdiFromUdi " << udi << " returned " << mediumUdi << endl; + if (!mediumUdi) + return; + + /* TODO: Warn the user that (s)he should unmount devices before unplugging */ + if (conditionName == "VolumeUnmountForced") + ResetProperties(mediumUdi); + + /* Reset properties after mounting */ + if (conditionName == "VolumeMount") + ResetProperties(mediumUdi); + + /* Reset properties after unmounting */ + if (conditionName == "VolumeUnmount") + ResetProperties(mediumUdi); + +} + +void HALBackend::MainLoopIntegration(DBusConnection *dbusConnection) +{ + m_dBusQtConnection = new DBusQt::Connection(m_parent); + m_dBusQtConnection->dbus_connection_setup_with_qt_main(dbusConnection); +} + +/****************************************** + ** Properties attribution ** + ******************************************/ + +/* Return the medium udi that should be updated when recieving a call for + device udi */ +const char* HALBackend::findMediumUdiFromUdi(const char* udi) +{ + /* Easy part : this Udi is already registered as a device */ + const Medium* medium = m_mediaList.findById(udi); + if (medium) + return medium->id().ascii(); + + /* Hard part : this is a volume whose drive is registered */ + if (libhal_device_property_exists(m_halContext, udi, "info.capabilities", NULL)) + if (libhal_device_query_capability(m_halContext, udi, "volume", NULL)) + { + QString driveUdi = libhal_device_get_property_QString(m_halContext, udi, "block.storage_device"); + return findMediumUdiFromUdi(driveUdi.ascii()); + } + + return NULL; +} + +void HALBackend::ResetProperties(const char* mediumUdi, bool allowNotification) +{ + kdDebug(1219) << "HALBackend::setProperties" << endl; + if ( QString::fromLatin1( mediumUdi ).startsWith( "/org/kde/" ) ) + { + const Medium *cmedium = m_mediaList.findById(mediumUdi); + if ( cmedium ) + { + Medium m( *cmedium ); + if ( setFstabProperties( &m ) ) { + kdDebug() << "setFstabProperties worked" << endl; + m_mediaList.changeMediumState(m, allowNotification); + } + return; + } + } + + Medium* m = new Medium(mediumUdi, ""); + + if (libhal_device_query_capability(m_halContext, mediumUdi, "volume", NULL)) + setVolumeProperties(m); + if (libhal_device_query_capability(m_halContext, mediumUdi, "storage", NULL)) + setFloppyProperties(m); + if (libhal_device_query_capability(m_halContext, mediumUdi, "camera", NULL)) + setCameraProperties(m); + + m_mediaList.changeMediumState(*m, allowNotification); + + delete m; +} + +void HALBackend::setVolumeProperties(Medium* medium) +{ + kdDebug(1219) << "HALBackend::setVolumeProperties for " << medium->id() << endl; + + const char* udi = medium->id().ascii(); + /* Check if the device still exists */ + if (!libhal_device_exists(m_halContext, udi, NULL)) + return; + + /* Get device information from libhal-storage */ + LibHalVolume* halVolume = libhal_volume_from_udi(m_halContext, udi); + if (!halVolume) + return; + QString driveUdi = libhal_volume_get_storage_device_udi(halVolume); + LibHalDrive* halDrive = 0; + if ( !driveUdi.isNull() ) + halDrive = libhal_drive_from_udi(m_halContext, driveUdi.ascii()); + if (!halDrive) { + // at times HAL sends an UnmountForced event before the device is removed + libhal_volume_free(halVolume); + return; + } + + medium->setName( + generateName(libhal_volume_get_device_file(halVolume)) ); + + medium->mountableState( + libhal_volume_get_device_file(halVolume), /* Device node */ + libhal_volume_get_mount_point(halVolume), /* Mount point */ + libhal_volume_get_fstype(halVolume), /* Filesystem type */ + libhal_volume_is_mounted(halVolume) ); /* Mounted ? */ + + char* name = libhal_volume_policy_compute_display_name(halDrive, halVolume, m_halStoragePolicy); + QString volume_name = QString::fromUtf8(name); + QString media_name = volume_name; + medium->setLabel(media_name); + free(name); + + QString mimeType; + if (libhal_volume_is_disc(halVolume)) + { + mimeType = "media/cdrom" + MOUNT_SUFFIX; + + LibHalVolumeDiscType discType = libhal_volume_get_disc_type(halVolume); + if ((discType == LIBHAL_VOLUME_DISC_TYPE_CDROM) || + (discType == LIBHAL_VOLUME_DISC_TYPE_CDR) || + (discType == LIBHAL_VOLUME_DISC_TYPE_CDRW)) + if (libhal_volume_disc_is_blank(halVolume)) + { + mimeType = "media/blankcd"; + medium->unmountableState(""); + } + else + mimeType = "media/cdwriter" + MOUNT_SUFFIX; + + if ((discType == LIBHAL_VOLUME_DISC_TYPE_DVDROM) || (discType == LIBHAL_VOLUME_DISC_TYPE_DVDRAM) || + (discType == LIBHAL_VOLUME_DISC_TYPE_DVDR) || (discType == LIBHAL_VOLUME_DISC_TYPE_DVDRW) || + (discType == LIBHAL_VOLUME_DISC_TYPE_DVDPLUSR) || (discType == LIBHAL_VOLUME_DISC_TYPE_DVDPLUSRW) ) + if (libhal_volume_disc_is_blank(halVolume)) + { + mimeType = "media/blankdvd"; + medium->unmountableState(""); + } + else + mimeType = "media/dvd" + MOUNT_SUFFIX; + + if (libhal_volume_disc_has_audio(halVolume) && !libhal_volume_disc_has_data(halVolume)) + { + mimeType = "media/audiocd"; + medium->unmountableState( "audiocd:/?device=" + QString(libhal_volume_get_device_file(halVolume)) ); + } + + medium->setIconName(QString::null); + + /* check if the disc id a vcd or a video dvd */ + DiscType type = LinuxCDPolling::identifyDiscType(libhal_volume_get_device_file(halVolume)); + switch (type) + { + case DiscType::VCD: + mimeType = "media/vcd"; + break; + case DiscType::SVCD: + mimeType = "media/svcd"; + break; + case DiscType::DVD: + mimeType = "media/dvdvideo"; + break; + } + } + else + { + mimeType = "media/hdd" + MOUNT_SUFFIX; + medium->setIconName(QString::null); // reset icon + if (libhal_drive_is_hotpluggable(halDrive)) + { + mimeType = "media/removable" + MOUNT_SUFFIX; + medium->needMounting(); + switch (libhal_drive_get_type(halDrive)) { + case LIBHAL_DRIVE_TYPE_COMPACT_FLASH: + medium->setIconName("compact_flash" + MOUNT_ICON_SUFFIX); + break; + case LIBHAL_DRIVE_TYPE_MEMORY_STICK: + medium->setIconName("memory_stick" + MOUNT_ICON_SUFFIX); + break; + case LIBHAL_DRIVE_TYPE_SMART_MEDIA: + medium->setIconName("smart_media" + MOUNT_ICON_SUFFIX); + break; + case LIBHAL_DRIVE_TYPE_SD_MMC: + medium->setIconName("sd_mmc" + MOUNT_ICON_SUFFIX); + break; + case LIBHAL_DRIVE_TYPE_PORTABLE_AUDIO_PLAYER: + { + medium->setIconName("ipod" + MOUNT_ICON_SUFFIX); + break; + } + case LIBHAL_DRIVE_TYPE_CAMERA: + { + mimeType = "media/camera" + MOUNT_SUFFIX; + const char *physdev = libhal_drive_get_physical_device_udi(halDrive); + // get model from camera + if (physdev && libhal_device_query_capability(m_halContext, physdev, "camera", NULL)) + { + if (libhal_device_property_exists(m_halContext, physdev, "usb_device.product", NULL)) + medium->setLabel(libhal_device_get_property_QString(m_halContext, physdev, "usb_device.product")); + else if (libhal_device_property_exists(m_halContext, physdev, "usb.product", NULL)) + medium->setLabel(libhal_device_get_property_QString(m_halContext, physdev, "usb.product")); + } + break; + } + case LIBHAL_DRIVE_TYPE_TAPE: + medium->setIconName(QString::null); //FIXME need icon + break; + default: + medium->setIconName(QString::null); + } + + if (medium->isMounted() && QFile::exists(medium->mountPoint() + "/dcim")) + { + mimeType = "media/camera" + MOUNT_SUFFIX; + } + } + } + medium->setMimeType(mimeType); + + libhal_drive_free(halDrive); + libhal_volume_free(halVolume); +} + +bool HALBackend::setFstabProperties( Medium *medium ) +{ + QString mp = isInFstab(medium); + + if (!mp.isNull() && !medium->id().startsWith( "/org/kde" ) ) + { + // now that we know it's in fstab, we have to find out if it's mounted + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + + KMountPoint::List::iterator it = mtab.begin(); + KMountPoint::List::iterator end = mtab.end(); + + bool mounted = false; + + for (; it!=end; ++it) + { + if ((*it)->mountedFrom() == medium->deviceNode() && (*it)->mountPoint() == mp ) + { + mounted = true; + break; + } + } + + kdDebug() << mp << " " << mounted << " " << medium->deviceNode() << " " << endl; + QString fstype = medium->fsType(); + if ( fstype.isNull() ) + fstype = "auto"; + + medium->mountableState( + medium->deviceNode(), + mp, /* Mount point */ + fstype, /* Filesystem type */ + mounted ); /* Mounted ? */ + + return true; + } + + return false; + +} + +// Handle floppies and zip drives +bool HALBackend::setFloppyProperties(Medium* medium) +{ + kdDebug(1219) << "HALBackend::setFloppyProperties for " << medium->id() << endl; + + const char* udi = medium->id().ascii(); + /* Check if the device still exists */ + if (!libhal_device_exists(m_halContext, udi, NULL)) + return false; + + LibHalDrive* halDrive = libhal_drive_from_udi(m_halContext, udi); + if (!halDrive) + return false; + + QString drive_type = libhal_device_get_property_QString(m_halContext, udi, "storage.drive_type"); + + if (drive_type == "zip") { + int numVolumes; + char** volumes = libhal_drive_find_all_volumes(m_halContext, halDrive, &numVolumes); + libhal_free_string_array(volumes); + kdDebug(1219) << " found " << numVolumes << " volumes" << endl; + if (numVolumes) + { + libhal_drive_free(halDrive); + return false; + } + } + + medium->setName( generateName(libhal_drive_get_device_file(halDrive)) ); + medium->setLabel(i18n("Unknown Drive")); + + // HAL hates floppies - so we have to do it twice ;( + medium->mountableState(libhal_drive_get_device_file(halDrive), QString::null, QString::null, false); + setFloppyMountState(medium); + + if (drive_type == "floppy") + { + if (medium->isMounted()) // don't use _SUFFIX here as it accesses the volume + medium->setMimeType("media/floppy_mounted" ); + else + medium->setMimeType("media/floppy_unmounted"); + medium->setLabel(i18n("Floppy Drive")); + } + else if (drive_type == "zip") + { + if (medium->isMounted()) + medium->setMimeType("media/zip_mounted" ); + else + medium->setMimeType("media/zip_unmounted"); + medium->setLabel(i18n("Zip Drive")); + } + + /** @todo And mimtype for JAZ drives ? */ + + medium->setIconName(QString::null); + + libhal_drive_free(halDrive); + + return true; +} + +void HALBackend::setFloppyMountState( Medium *medium ) +{ + if ( !medium->id().startsWith( "/org/kde" ) ) + { + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + KMountPoint::List::iterator it = mtab.begin(); + KMountPoint::List::iterator end = mtab.end(); + + QString fstype; + QString mountpoint; + for (; it!=end; ++it) + { + if ((*it)->mountedFrom() == medium->deviceNode() ) + { + fstype = (*it)->mountType().isNull() ? (*it)->mountType() : "auto"; + mountpoint = (*it)->mountPoint(); + medium->mountableState( medium->deviceNode(), mountpoint, fstype, true ); + return; + } + } + } +} + +void HALBackend::setCameraProperties(Medium* medium) +{ + kdDebug(1219) << "HALBackend::setCameraProperties for " << medium->id() << endl; + + const char* udi = medium->id().ascii(); + /* Check if the device still exists */ + if (!libhal_device_exists(m_halContext, udi, NULL)) + return; + + /** @todo find name */ + medium->setName("camera"); + + QString device = "camera:/"; + + char *cam = libhal_device_get_property_string(m_halContext, udi, "camera.libgphoto2.name", NULL); + DBusError error; + dbus_error_init(&error); + if (cam && + libhal_device_property_exists(m_halContext, udi, "usb.linux.device_number", NULL) && + libhal_device_property_exists(m_halContext, udi, "usb.bus_number", NULL)) + device.sprintf("camera://%s@[usb:%03d,%03d]/", cam, + libhal_device_get_property_int(m_halContext, udi, "usb.bus_number", &error), + libhal_device_get_property_int(m_halContext, udi, "usb.linux.device_number", &error)); + + libhal_free_string(cam); + + /** @todo find the rest of this URL */ + medium->unmountableState(device); + medium->setMimeType("media/gphoto2camera"); + medium->setIconName(QString::null); + if (libhal_device_property_exists(m_halContext, udi, "usb_device.product", NULL)) + medium->setLabel(libhal_device_get_property_QString(m_halContext, udi, "usb_device.product")); + else if (libhal_device_property_exists(m_halContext, udi, "usb.product", NULL)) + medium->setLabel(libhal_device_get_property_QString(m_halContext, udi, "usb.product")); + else + medium->setLabel(i18n("Camera")); +} + +QString HALBackend::generateName(const QString &devNode) +{ + return KURL(devNode).fileName(); +} + +/****************************************** + ** HAL CALL-BACKS ** + ******************************************/ + +void HALBackend::hal_device_added(LibHalContext *ctx, const char *udi) +{ + kdDebug(1219) << "HALBackend::hal_device_added " << udi << endl; + Q_UNUSED(ctx); + s_HALBackend->AddDevice(udi); +} + +void HALBackend::hal_device_removed(LibHalContext *ctx, const char *udi) +{ + kdDebug(1219) << "HALBackend::hal_device_removed " << udi << endl; + Q_UNUSED(ctx); + s_HALBackend->RemoveDevice(udi); +} + +void HALBackend::hal_device_property_modified(LibHalContext *ctx, const char *udi, + const char *key, dbus_bool_t is_removed, dbus_bool_t is_added) +{ + kdDebug(1219) << "HALBackend::hal_property_modified " << udi << " -- " << key << endl; + Q_UNUSED(ctx); + Q_UNUSED(is_removed); + Q_UNUSED(is_added); + s_HALBackend->ModifyDevice(udi, key); +} + +void HALBackend::hal_device_condition(LibHalContext *ctx, const char *udi, + const char *condition_name, + const char* message + ) +{ + kdDebug(1219) << "HALBackend::hal_device_condition " << udi << " -- " << condition_name << endl; + Q_UNUSED(ctx); + Q_UNUSED(message); + s_HALBackend->DeviceCondition(udi, condition_name); +} + +QStringList HALBackend::mountoptions(const QString &name) +{ + const Medium* medium = m_mediaList.findById(name); + if (medium && !isInFstab(medium).isNull()) + return QStringList(); // not handled by HAL - fstab entry + + KConfig config("mediamanagerrc"); + config.setGroup(name); + + char ** array = libhal_device_get_property_strlist(m_halContext, name.latin1(), "volume.mount.valid_options", NULL); + QMap<QString,bool> valids; + + for (int index = 0; array && array[index]; ++index) { + QString t = array[index]; + if (t.endsWith("=")) + t = t.left(t.length() - 1); + valids[t] = true; + kdDebug() << "valid " << t << endl; + } + libhal_free_string_array(array); + QStringList result; + QString tmp; + + QString fstype = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.fstype"); + if (fstype.isNull()) + fstype = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.policy.mount_filesystem"); + + QString drive_udi = libhal_device_get_property_QString(m_halContext, name.latin1(), "block.storage_device"); + + bool removable = false; + if ( !drive_udi.isNull() ) + removable = libhal_device_get_property_bool(m_halContext, drive_udi.latin1(), "storage.removable", NULL) + || libhal_device_get_property_bool(m_halContext, drive_udi.latin1(), "storage.hotpluggable", NULL); + + config.setGroup(drive_udi); + bool value = config.readBoolEntry("automount", false); + config.setGroup(name); + + if (libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_blank", NULL) + || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_vcd", NULL) + || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_svcd", NULL) + || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.is_videodvd", NULL) + || libhal_device_get_property_bool(m_halContext, name.latin1(), "volume.disc.has_audio", NULL)) + value = false; + + result << QString("automount=%1").arg(value ? "true" : "false"); + + if (valids.contains("ro")) + { + value = config.readBoolEntry("ro", false); + tmp = QString("ro=%1").arg(value ? "true" : "false"); + if (fstype != "iso9660") // makes no sense + result << tmp; + } + + if (valids.contains("quiet")) + { + value = config.readBoolEntry("quiet", false); + tmp = QString("quiet=%1").arg(value ? "true" : "false"); + if (fstype != "iso9660") // makes no sense + result << tmp; + } + + if (valids.contains("flush")) + { + value = config.readBoolEntry("flush", fstype.endsWith("fat")); + tmp = QString("flush=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("uid")) + { + value = config.readBoolEntry("uid", true); + tmp = QString("uid=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("utf8")) + { + value = config.readBoolEntry("utf8", true); + tmp = QString("utf8=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("shortname")) + { + QString svalue = config.readEntry("shortname", "lower").lower(); + if (svalue == "winnt") + result << "shortname=winnt"; + else if (svalue == "win95") + result << "shortname=win95"; + else if (svalue == "mixed") + result << "shortname=mixed"; + else + result << "shortname=lower"; + } + + // pass our locale to the ntfs-3g driver so it can translate local characters + if (valids.contains("locale") && fstype == "ntfs-3g") + { + // have to obtain LC_CTYPE as returned by the `locale` command + // check in the same order as `locale` does + char *cType; + if ( (cType = getenv("LC_ALL")) || (cType = getenv("LC_CTYPE")) || (cType = getenv("LANG")) ) { + result << QString("locale=%1").arg(cType); + } + } + + if (valids.contains("sync")) + { + value = config.readBoolEntry("sync", ( valids.contains("flush") && !fstype.endsWith("fat") ) && removable); + tmp = QString("sync=%1").arg(value ? "true" : "false"); + if (fstype != "iso9660") // makes no sense + result << tmp; + } + + if (valids.contains("noatime")) + { + value = config.readBoolEntry("atime", !fstype.endsWith("fat")); + tmp = QString("atime=%1").arg(value ? "true" : "false"); + if (fstype != "iso9660") // makes no sense + result << tmp; + } + + QString mount_point = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.mount_point"); + if (mount_point.isEmpty()) + mount_point = libhal_device_get_property_QString(m_halContext, name.latin1(), "volume.policy.desired_mount_point"); + + mount_point = config.readEntry("mountpoint", mount_point); + + if (!mount_point.startsWith("/")) + mount_point = "/media/" + mount_point; + + result << QString("mountpoint=%1").arg(mount_point); + result << QString("filesystem=%1").arg(fstype); + + if (valids.contains("data")) + { + QString svalue = config.readEntry("journaling").lower(); + if (svalue == "ordered") + result << "journaling=ordered"; + else if (svalue == "writeback") + result << "journaling=writeback"; + else if (svalue == "data") + result << "journaling=data"; + else + result << "journaling=ordered"; + } + + return result; +} + +bool HALBackend::setMountoptions(const QString &name, const QStringList &options ) +{ + kdDebug() << "setMountoptions " << name << " " << options << endl; + + KConfig config("mediamanagerrc"); + config.setGroup(name); + + QMap<QString,QString> valids = MediaManagerUtils::splitOptions(options); + + const char *names[] = { "ro", "quiet", "atime", "uid", "utf8", "flush", "sync", 0 }; + for (int index = 0; names[index]; ++index) + if (valids.contains(names[index])) + config.writeEntry(names[index], valids[names[index]] == "true"); + + if (valids.contains("shortname")) + config.writeEntry("shortname", valids["shortname"]); + + if (valids.contains("journaling")) + config.writeEntry("journaling", valids["journaling"]); + + if (!mountoptions(name).contains(QString("mountpoint=%1").arg(valids["mountpoint"]))) + config.writeEntry("mountpoint", valids["mountpoint"]); + + if (valids.contains("automount")) { + QString drive_udi = libhal_device_get_property_QString(m_halContext, name.latin1(), "block.storage_device"); + config.setGroup(drive_udi); + config.writeEntry("automount", valids["automount"]); + } + + return true; +} + +static QString mount_priv(const char *udi, const char *mount_point, const char **poptions, int noptions, + DBusConnection *dbus_connection) +{ + DBusMessage *dmesg, *reply; + DBusError error; + + const char *fstype = ""; + if (!(dmesg = dbus_message_new_method_call ("org.freedesktop.Hal", udi, + "org.freedesktop.Hal.Device.Volume", + "Mount"))) { + kdDebug() << "mount failed for " << udi << ": could not create dbus message\n"; + return i18n("Internal Error"); + } + + if (!dbus_message_append_args (dmesg, DBUS_TYPE_STRING, &mount_point, DBUS_TYPE_STRING, &fstype, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &poptions, noptions, + DBUS_TYPE_INVALID)) + { + kdDebug() << "mount failed for " << udi << ": could not append args to dbus message\n"; + dbus_message_unref (dmesg); + return i18n("Internal Error"); + } + + QString qerror; + + dbus_error_init (&error); + if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, dmesg, -1, &error))) + { + QString qerror = error.message; + kdError() << "mount failed for " << udi << ": " << error.name << " - " << qerror << endl; + if ( !strcmp(error.name, "org.freedesktop.Hal.Device.Volume.UnknownFilesystemType")) + qerror = i18n("Invalid filesystem type"); + else if ( !strcmp(error.name, "org.freedesktop.Hal.Device.Volume.PermissionDenied")) + qerror = i18n("Permissions denied"); + else if ( !strcmp(error.name, "org.freedesktop.Hal.Device.Volume.AlreadyMounted")) + qerror = i18n("Device is already mounted."); + else if ( !strcmp(error.name, "org.freedesktop.Hal.Device.Volume.InvalidMountpoint") && strlen(mount_point)) { + dbus_message_unref (dmesg); + dbus_error_free (&error); + return mount_priv(udi, "", poptions, noptions, dbus_connection); + } + dbus_message_unref (dmesg); + dbus_error_free (&error); + return qerror; + } + + kdDebug() << "mount queued for " << udi << endl; + + dbus_message_unref (dmesg); + dbus_message_unref (reply); + + return qerror; + +} + +QString HALBackend::listUsingProcesses(const Medium* medium) +{ + QString proclist, fullmsg; + QString cmdline = QString("/usr/bin/env fuser -vm %1 2>&1").arg(KProcess::quote(medium->mountPoint())); + FILE *fuser = popen(cmdline.latin1(), "r"); + + uint counter = 0; + if (fuser) { + proclist += "<pre>"; + QTextIStream is(fuser); + QString tmp; + while (!is.atEnd()) { + tmp = is.readLine(); + tmp = QStyleSheet::escape(tmp) + "\n"; + + proclist += tmp; + if (counter++ > 10) + { + proclist += "..."; + break; + } + } + proclist += "</pre>"; + (void)pclose( fuser ); + } + if (counter) { + fullmsg = i18n("Moreover, programs still using the device " + "have been detected. They are listed below. You have to " + "close them or change their working directory before " + "attempting to unmount the device again."); + fullmsg += "<br>" + proclist; + return fullmsg; + } else { + return QString::null; + } +} + +void HALBackend::slotResult(KIO::Job *job) +{ + kdDebug() << "slotResult " << mount_jobs[job] << endl; + + struct mount_job_data *data = mount_jobs[job]; + QString& qerror = data->errorMessage; + const Medium* medium = data->medium; + + if (job->error() == KIO::ERR_COULD_NOT_UNMOUNT) { + QString proclist(listUsingProcesses(medium)); + + qerror = "<qt>"; + qerror += "<p>" + i18n("Unfortunately, the device <b>%1</b> (%2) named <b>'%3'</b> and " + "currently mounted at <b>%4</b> could not be unmounted. ").arg( + "system:/media/" + medium->name(), + medium->deviceNode(), + medium->prettyLabel(), + medium->prettyBaseURL().pathOrURL()) + "</p>"; + qerror += "<p>" + i18n("The following error was returned by umount command:"); + qerror += "</p><pre>" + job->errorText() + "</pre>"; + + if (!proclist.isEmpty()) { + qerror += proclist; + } + qerror += "</qt>"; + } else if (job->error()) { + qerror = job->errorText(); + } + + ResetProperties( medium->id().latin1() ); + mount_jobs.remove(job); + + /* Job completed. Notify the caller */ + data->error = job->error(); + data->completed = true; + kapp->eventLoop()->exitLoop(); +} + +QString HALBackend::isInFstab(const Medium *medium) +{ + KMountPoint::List fstab = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions|KMountPoint::NeedRealDeviceName); + + KMountPoint::List::iterator it = fstab.begin(); + KMountPoint::List::iterator end = fstab.end(); + + for (; it!=end; ++it) + { + QString reald = (*it)->realDeviceName(); + if ( reald.endsWith( "/" ) ) + reald = reald.left( reald.length() - 1 ); + kdDebug() << "isInFstab -" << medium->deviceNode() << "- -" << reald << "- -" << (*it)->mountedFrom() << "-" << endl; + if ((*it)->mountedFrom() == medium->deviceNode() || ( !medium->deviceNode().isEmpty() && reald == medium->deviceNode() ) ) + { + QStringList opts = (*it)->mountOptions(); + if (opts.contains("user") || opts.contains("users")) + return (*it)->mountPoint(); + } + } + + return QString::null; +} + +QString HALBackend::mount(const Medium *medium) +{ + if (medium->isMounted()) + return QString(); // that was easy + + QString mountPoint = isInFstab(medium); + if (!mountPoint.isNull()) + { + struct mount_job_data data; + data.completed = false; + data.medium = medium; + + kdDebug() << "triggering user mount " << medium->deviceNode() << " " << mountPoint << " " << medium->id() << endl; + KIO::Job *job = KIO::mount( false, 0, medium->deviceNode(), mountPoint ); + connect(job, SIGNAL( result (KIO::Job *)), + SLOT( slotResult( KIO::Job *))); + mount_jobs[job] = &data; + // The caller expects the device to be mounted when the function + // completes. Thus block until the job completes. + while (!data.completed) { + kapp->eventLoop()->enterLoop(); + } + // Return the error message (if any) to the caller + return (data.error) ? data.errorMessage : QString::null; + + } else if (medium->id().startsWith("/org/kde/") ) + return i18n("Permissions denied"); + + QStringList soptions; + + kdDebug() << "mounting " << medium->id() << "..." << endl; + + QMap<QString,QString> valids = MediaManagerUtils::splitOptions(mountoptions(medium->id())); + if (valids["flush"] == "true") + soptions << "flush"; + + if (valids["uid"] == "true") + { + soptions << QString("uid=%1").arg(getuid()); + } + + if (valids["ro"] == "true") + soptions << "ro"; + + if (valids["atime"] != "true") + soptions << "noatime"; + + if (valids["quiet"] == "true") + soptions << "quiet"; + + if (valids["utf8"] == "true") + soptions << "utf8"; + + if (valids["sync"] == "true") + soptions << "sync"; + + QString mount_point = valids["mountpoint"]; + if (mount_point.startsWith("/media/")) + mount_point = mount_point.mid(7); + + if (valids.contains("shortname")) + { + soptions << QString("shortname=%1").arg(valids["shortname"]); + } + + if (valids.contains("locale")) + { + soptions << QString("locale=%1").arg(valids["locale"]); + } + + if (valids.contains("journaling")) + { + QString option = valids["journaling"]; + if (option == "data") + soptions << QString("data=journal"); + else if (option == "writeback") + soptions << QString("data=writeback"); + else + soptions << QString("data=ordered"); + } + + const char **options = new const char*[soptions.size() + 1]; + uint noptions = 0; + for (QStringList::ConstIterator it = soptions.begin(); it != soptions.end(); ++it, ++noptions) + options[noptions] = (*it).latin1(); + options[noptions] = NULL; + + QString qerror = mount_priv(medium->id().latin1(), mount_point.utf8(), options, noptions, dbus_connection); + if (!qerror.isEmpty()) { + kdError() << "mounting " << medium->id() << " returned " << qerror << endl; + return qerror; + } + + medium->setHalMounted(true); + ResetProperties(medium->id().latin1()); + + return QString(); +} + +QString HALBackend::mount(const QString &_udi) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + + return mount(medium); +} + +QString HALBackend::unmount(const QString &_udi) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + { // now we get fancy: if the udi is no volume, it _might_ be a device with only one + // volume on it (think CDs) - so we're so nice to the caller to unmount that volume + LibHalDrive* halDrive = libhal_drive_from_udi(m_halContext, _udi.latin1()); + if (halDrive) + { + int numVolumes; + char** volumes = libhal_drive_find_all_volumes(m_halContext, halDrive, &numVolumes); + if (numVolumes == 1) + medium = m_mediaList.findById( volumes[0] ); + } + } + + if ( !medium ) + return i18n("No such medium: %1").arg(_udi); + + if (!medium->isMounted()) + return QString(); // that was easy + + QString mountPoint = isInFstab(medium); + if (!mountPoint.isNull()) + { + struct mount_job_data data; + data.completed = false; + data.medium = medium; + + kdDebug() << "triggering user unmount " << medium->deviceNode() << " " << mountPoint << endl; + KIO::Job *job = KIO::unmount( medium->mountPoint(), false ); + connect(job, SIGNAL( result (KIO::Job *)), + SLOT( slotResult( KIO::Job *))); + mount_jobs[job] = &data; + // The caller expects the device to be unmounted when the function + // completes. Thus block until the job completes. + while (!data.completed) { + kapp->eventLoop()->enterLoop(); + } + // Return the error message (if any) to the caller + return (data.error) ? data.errorMessage : QString::null; + } + + DBusMessage *dmesg, *reply; + DBusError error; + const char *options[2]; + + const char *udi = medium->id().latin1(); + kdDebug() << "unmounting " << udi << "..." << endl; + + dbus_error_init(&error); + DBusConnection *dbus_connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error); + if (dbus_error_is_set(&error)) + { + dbus_error_free(&error); + return false; + } + + if (!(dmesg = dbus_message_new_method_call ("org.freedesktop.Hal", udi, + "org.freedesktop.Hal.Device.Volume", + "Unmount"))) { + kdDebug() << "unmount failed for " << udi << ": could not create dbus message\n"; + return i18n("Internal Error"); + } + + options[0] = "force"; + options[1] = 0; + + if (!dbus_message_append_args (dmesg, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &options, 0, + DBUS_TYPE_INVALID)) + { + kdDebug() << "unmount failed for " << udi << ": could not append args to dbus message\n"; + dbus_message_unref (dmesg); + return i18n("Internal Error"); + } + + dbus_error_init (&error); + if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, dmesg, -1, &error))) + { + QString qerror, reason; + + kdDebug() << "unmount failed for " << udi << ": " << error.name << " " << error.message << endl; + qerror = "<qt>"; + qerror += "<p>" + i18n("Unfortunately, the device <b>%1</b> (%2) named <b>'%3'</b> and " + "currently mounted at <b>%4</b> could not be unmounted. ").arg( + "system:/media/" + medium->name(), + medium->deviceNode(), + medium->prettyLabel(), + medium->prettyBaseURL().pathOrURL()) + "</p>"; + qerror += "<p>" + i18n("Unmounting failed due to the following error:") + "</p>"; + if (!strcmp(error.name, "org.freedesktop.Hal.Device.Volume.Busy")) { + reason = i18n("Device is Busy:"); + } else if (!strcmp(error.name, "org.freedesktop.Hal.Device.Volume.NotMounted")) { + // this is faking. The error is that the device wasn't mounted by hal (but by the system) + reason = i18n("Permissions denied"); + } else { + reason = error.message; + } + qerror += "<p><b>" + reason + "</b></p>"; + + // Include list of processes (if any) using the device in the error message + reason = listUsingProcesses(medium); + if (!reason.isEmpty()) { + qerror += reason; + } + + dbus_message_unref (dmesg); + dbus_error_free (&error); + return qerror; + } + + kdDebug() << "unmount queued for " << udi << endl; + + dbus_message_unref (dmesg); + dbus_message_unref (reply); + + medium->setHalMounted(false); + ResetProperties(udi); + + return QString(); +} + +#include "halbackend.moc" diff --git a/kioslave/media/mediamanager/halbackend.h b/kioslave/media/mediamanager/halbackend.h new file mode 100644 index 000000000..31c682374 --- /dev/null +++ b/kioslave/media/mediamanager/halbackend.h @@ -0,0 +1,228 @@ +/* This file is part of the KDE Project + Copyright (c) 2004-2005 Jérôme Lodewyck <jerome dot lodewyck at normalesup dot 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. +*/ + +/** +* This is a media:/ backend for the freedesktop Hardware Abstraction Layer +* Usage : create an instance of HALBackend, then call InitHal(). A false +* result from the later function means that something went wrong and that +* the backend shall not be used. +* +* @author Jérôme Lodewyck <jerome dot lodewyck at normalesup dot org> +* @short media:/ backend for the HAL +*/ + +#ifndef _HALBACKEND_H_ +#define _HALBACKEND_H_ + +#include "backendbase.h" + +#include <qobject.h> +#include <qstringlist.h> +#include <qstring.h> + +#include <config.h> + +/* We acknowledge the the dbus API is unstable */ +#define DBUS_API_SUBJECT_TO_CHANGE +/* DBus-Qt bindings */ +#include <dbus/connection.h> +/* HAL libraries */ +#include <libhal.h> +#include <libhal-storage.h> + +namespace KIO { + class Job; +} + +class HALBackend : public QObject, public BackendBase +{ +Q_OBJECT + +public: + /** + * Constructor + */ + HALBackend(MediaList &list, QObject* parent); + + /** + * Destructor + */ + ~HALBackend(); + + /** + * Perform HAL initialization. + * + * @return true if succeded. If not, rely on some other backend + */ + bool InitHal(); + + /** + * List all devices and append them to the media device list (called only once, at startup). + * + * @return true if succeded, false otherwise + */ + bool ListDevices(); + + QStringList mountoptions(const QString &id); + + bool setMountoptions(const QString &id, const QStringList &options); + + QString mount(const QString &id); + QString mount(const Medium *medium); + QString unmount(const QString &id); + +private: + /** + * Append a device in the media list. This function will check if the device + * is worth listing. + * + * @param udi Universal Device Id + * @param allowNotification Indicates if this event will be notified to the user + */ + void AddDevice(const char* udi, bool allowNotification=true); + + /** + * Remove a device from the device list + * + * @param udi Universal Device Id + */ + void RemoveDevice(const char* udi); + + /** + * A device has changed, update it + * + * @param udi Universal Device Id + */ + void ModifyDevice(const char *udi, const char* key); + + /** + * HAL informed that a special action has occured + * (e.g. device unplugged without unmounting) + * + * @param udi Universal Device Id + */ + void DeviceCondition(const char *udi, const char *condition); + + /** + * Integrate the DBus connection within qt main loop + */ + void MainLoopIntegration(DBusConnection *dbusConnection); + +/* Set media properties */ +private: + /** + * Reset properties for the given medium + */ + void ResetProperties(const char* MediumUdi, bool allowNotification=false); + + /** + * Find the medium that is concerned with device udi + */ + const char* findMediumUdiFromUdi(const char* udi); + + void setVolumeProperties(Medium* medium); + bool setFloppyProperties(Medium* medium); + void setFloppyMountState( Medium* medium ); + bool setFstabProperties(Medium* medium); + void setCameraProperties(Medium* medium); + QString generateName(const QString &devNode); + static QString isInFstab(const Medium *medium); + static QString listUsingProcesses(const Medium *medium); + +private slots: + void slotResult(KIO::Job *job); + +/* Hal call-backs -- from gvm*/ +public: + /** Invoked when a device is added to the Global Device List. + * + * @param ctx LibHal context + * @param udi Universal Device Id + */ + static void hal_device_added(LibHalContext *ctx, const char *udi); + + /** Invoked when a device is removed from the Global Device List. + * + * @param ctx LibHal context + * @param udi Universal Device Id + */ + static void hal_device_removed(LibHalContext *ctx, const char *udi); + + /** Invoked when a property of a device in the Global Device List is + * changed, and we have we have subscribed to changes for that device. + * + * @param ctx LibHal context + * @param udi Univerisal Device Id + * @param key Key of property + */ + static void hal_device_property_modified(LibHalContext *ctx, const char *udi, const char *key, + dbus_bool_t is_removed, dbus_bool_t is_added); + + /** Type for callback when a non-continuos condition occurs on a device + * + * @param udi Univerisal Device Id + * @param condition_name Name of the condition + * @param message D-BUS message with variable parameters depending on condition + */ + static void hal_device_condition(LibHalContext *ctx, const char *udi, + const char *condition_name, + const char* message + ); + +/* HAL and DBus structures */ +private: + /** + * The HAL context connecting the whole application to the HAL + */ + LibHalContext* m_halContext; + + /** + * libhal-storage HAL policy, e.g. for icon names + */ + LibHalStoragePolicy* m_halStoragePolicy; + + /** + * The DBus-Qt bindings connection for mainloop integration + */ + DBusQt::Connection* m_dBusQtConnection; + + /** + * Object for the kded module + */ + QObject* m_parent; + + DBusConnection *dbus_connection; + + /** + * Data structure for fstab mount/unmount jobs + */ + struct mount_job_data { + // [in] Medium, which is being mounted/unmounted by the job + const Medium* medium; + // [in,out] Should be set to true when the job completes + bool completed; + // [out] KIO::Error if an error occured during operation. Otherwise, 0 + int error; + // [out] Error message to be displayed to the user + QString errorMessage; + }; + + QMap<KIO::Job *, struct mount_job_data*> mount_jobs; +}; + +#endif /* _HALBACKEND_H_ */ diff --git a/kioslave/media/mediamanager/linuxcdpolling.cpp b/kioslave/media/mediamanager/linuxcdpolling.cpp new file mode 100644 index 000000000..7519023fe --- /dev/null +++ b/kioslave/media/mediamanager/linuxcdpolling.cpp @@ -0,0 +1,585 @@ +/* This file is part of the KDE Project + Copyright (c) 2003 Gav Wood <gav kde org> + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +/* Some code of this file comes from kdeautorun */ + +#include "linuxcdpolling.h" + +#include <qthread.h> +#include <qmutex.h> +#include <qfile.h> + +#include <kdebug.h> + +#include "fstabbackend.h" + +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> + +// Never ever include directly a kernel header! +// #include <linux/cdrom.h> +// Instead we redefine the necessary (copied from the header) + +/* This struct is used by the CDROMREADTOCHDR ioctl */ +struct cdrom_tochdr +{ + unsigned char cdth_trk0; /* start track */ + unsigned char cdth_trk1; /* end track */ +}; + +#define CDROMREADTOCHDR 0x5305 /* Read TOC header + (struct cdrom_tochdr) */ +#define CDROM_DRIVE_STATUS 0x5326 /* Get tray position, etc. */ +#define CDROM_DISC_STATUS 0x5327 /* Get disc type, etc. */ + +/* drive status possibilities returned by CDROM_DRIVE_STATUS ioctl */ +#define CDS_NO_INFO 0 /* if not implemented */ +#define CDS_NO_DISC 1 +#define CDS_TRAY_OPEN 2 +#define CDS_DRIVE_NOT_READY 3 +#define CDS_DISC_OK 4 + +/* return values for the CDROM_DISC_STATUS ioctl */ +/* can also return CDS_NO_[INFO|DISC], from above */ +#define CDS_AUDIO 100 +#define CDS_DATA_1 101 +#define CDS_DATA_2 102 +#define CDS_XA_2_1 103 +#define CDS_XA_2_2 104 +#define CDS_MIXED 105 + +#define CDSL_CURRENT ((int) (~0U>>1)) + +// ------- + + + +DiscType::DiscType(Type type) + : m_type(type) +{ +} + +bool DiscType::isKnownDisc() const +{ + return m_type != None + && m_type != Unknown + && m_type != UnknownType + && m_type != Broken; +} + +bool DiscType::isDisc() const +{ + return m_type != None + && m_type != Unknown + && m_type != Broken; +} + +bool DiscType::isNotDisc() const +{ + return m_type == None; +} + +bool DiscType::isData() const +{ + return m_type == Data; +} + +DiscType::operator int() const +{ + return (int)m_type; +} + + +class PollingThread : public QThread +{ +public: + PollingThread(const QCString &devNode) : m_dev(devNode) + { + kdDebug(1219) << "PollingThread::PollingThread(" + << devNode << ")" << endl; + m_stop = false; + m_currentType = DiscType::None; + m_lastPollType = DiscType::None; + } + + + void stop() + { + QMutexLocker locker(&m_mutex); + m_stop = true; + } + + bool hasChanged() + { + QMutexLocker locker(&m_mutex); + + return m_currentType!=m_lastPollType; + } + + DiscType type() + { + QMutexLocker locker(&m_mutex); + m_currentType = m_lastPollType; + return m_currentType; + } + +protected: + virtual void run() + { + kdDebug(1219) << "PollingThread(" << m_dev << ") start" << endl; + while (!m_stop && m_lastPollType!=DiscType::Broken) + { + m_mutex.lock(); + DiscType type = m_lastPollType; + m_mutex.unlock(); + + type = LinuxCDPolling::identifyDiscType(m_dev, type); + + m_mutex.lock(); + m_lastPollType = type; + m_mutex.unlock(); + + msleep(500); + } + kdDebug(1219) << "PollingThread(" << m_dev << ") stop" << endl; + } + +private: + QMutex m_mutex; + bool m_stop; + const QCString m_dev; + DiscType m_currentType; + DiscType m_lastPollType; +}; + + +LinuxCDPolling::LinuxCDPolling(MediaList &list) + : QObject(), BackendBase(list) +{ + connect(&m_mediaList, SIGNAL(mediumAdded(const QString &, + const QString &, bool)), + this, SLOT(slotMediumAdded(const QString &)) ); + + connect(&m_mediaList, SIGNAL(mediumRemoved(const QString &, + const QString &, bool)), + this, SLOT(slotMediumRemoved(const QString &)) ); + + connect(&m_mediaList, SIGNAL(mediumStateChanged(const QString &, + const QString &, bool, bool)), + this, SLOT(slotMediumStateChanged(const QString &)) ); + + connect(&m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); +} + +LinuxCDPolling::~LinuxCDPolling() +{ + QMap<QString, PollingThread*>::iterator it = m_threads.begin(); + QMap<QString, PollingThread*>::iterator end = m_threads.end(); + + for(; it!=end; ++it) + { + PollingThread *thread = it.data(); + thread->stop(); + thread->wait(); + delete thread; + } +} + +void LinuxCDPolling::slotMediumAdded(const QString &id) +{ + kdDebug(1219) << "LinuxCDPolling::slotMediumAdded(" << id << ")" << endl; + + if (m_threads.contains(id)) return; + + const Medium *medium = m_mediaList.findById(id); + + QString mime = medium->mimeType(); + kdDebug(1219) << "mime == " << mime << endl; + + if (mime.find("dvd")==-1 && mime.find("cd")==-1) return; + + if (!medium->isMounted()) + { + m_excludeNotification.append( id ); + + QCString dev = QFile::encodeName( medium->deviceNode() ).data(); + PollingThread *thread = new PollingThread(dev); + m_threads[id] = thread; + thread->start(); + m_timer.start(500); + } +} + +void LinuxCDPolling::slotMediumRemoved(const QString &id) +{ + kdDebug(1219) << "LinuxCDPolling::slotMediumRemoved(" << id << ")" << endl; + + if (!m_threads.contains(id)) return; + + PollingThread *thread = m_threads[id]; + m_threads.remove(id); + thread->stop(); + thread->wait(); + delete thread; + + m_excludeNotification.remove(id); +} + +void LinuxCDPolling::slotMediumStateChanged(const QString &id) +{ + kdDebug(1219) << "LinuxCDPolling::slotMediumStateChanged(" + << id << ")" << endl; + + const Medium *medium = m_mediaList.findById(id); + + QString mime = medium->mimeType(); + kdDebug(1219) << "mime == " << mime << endl; + + if (mime.find("dvd")==-1 && mime.find("cd")==-1) return; + + if (!m_threads.contains(id) && !medium->isMounted()) + { + // It is just a mount state change, no need to notify + m_excludeNotification.append( id ); + + QCString dev = QFile::encodeName( medium->deviceNode() ).data(); + PollingThread *thread = new PollingThread(dev); + m_threads[id] = thread; + thread->start(); + m_timer.start(500); + } + else if (m_threads.contains(id) && medium->isMounted()) + { + PollingThread *thread = m_threads[id]; + m_threads.remove(id); + thread->stop(); + thread->wait(); + delete thread; + } +} + +void LinuxCDPolling::slotTimeout() +{ + //kdDebug(1219) << "LinuxCDPolling::slotTimeout()" << endl; + + if (m_threads.isEmpty()) + { + m_timer.stop(); + return; + } + + QMap<QString, PollingThread*>::iterator it = m_threads.begin(); + QMap<QString, PollingThread*>::iterator end = m_threads.end(); + + for(; it!=end; ++it) + { + QString id = it.key(); + PollingThread *thread = it.data(); + + if (thread->hasChanged()) + { + DiscType type = thread->type(); + const Medium *medium = m_mediaList.findById(id); + applyType(type, medium); + } + } +} + +static QString baseType(const Medium *medium) +{ + kdDebug(1219) << "baseType(" << medium->id() << ")" << endl; + + QString devNode = medium->deviceNode(); + QString mountPoint = medium->mountPoint(); + QString fsType = medium->fsType(); + bool mounted = medium->isMounted(); + + QString mimeType, iconName, label; + + FstabBackend::guess(devNode, mountPoint, fsType, mounted, + mimeType, iconName, label); + + if (devNode.find("dvd")!=-1) + { + kdDebug(1219) << "=> dvd" << endl; + return "dvd"; + } + else + { + kdDebug(1219) << "=> cd" << endl; + return "cd"; + } +} + +static void restoreEmptyState(MediaList &list, const Medium *medium, + bool allowNotification) +{ + kdDebug(1219) << "restoreEmptyState(" << medium->id() << ")" << endl; + + QString id = medium->id(); + QString devNode = medium->deviceNode(); + QString mountPoint = medium->mountPoint(); + QString fsType = medium->fsType(); + bool mounted = medium->isMounted(); + + QString mimeType, iconName, label; + + FstabBackend::guess(devNode, mountPoint, fsType, mounted, + mimeType, iconName, label); + + list.changeMediumState(id, devNode, mountPoint, fsType, mounted, + allowNotification, mimeType, iconName, label); +} + + +void LinuxCDPolling::applyType(DiscType type, const Medium *medium) +{ + kdDebug(1219) << "LinuxCDPolling::applyType(" << type << ", " + << medium->id() << ")" << endl; + + QString id = medium->id(); + QString dev = medium->deviceNode(); + + bool notify = !m_excludeNotification.contains(id); + m_excludeNotification.remove(id); + + switch (type) + { + case DiscType::Data: + restoreEmptyState(m_mediaList, medium, notify); + break; + case DiscType::Audio: + case DiscType::Mixed: + m_mediaList.changeMediumState(id, "audiocd:/?device="+dev, + notify, "media/audiocd"); + break; + case DiscType::VCD: + m_mediaList.changeMediumState(id, false, notify, "media/vcd"); + break; + case DiscType::SVCD: + m_mediaList.changeMediumState(id, false, notify, "media/svcd"); + break; + case DiscType::DVD: + m_mediaList.changeMediumState(id, false, notify, "media/dvdvideo"); + break; + case DiscType::Blank: + if (baseType(medium)=="dvd") + { + m_mediaList.changeMediumState(id, false, + notify, "media/blankdvd"); + } + else + { + m_mediaList.changeMediumState(id, false, + notify, "media/blankcd"); + } + break; + case DiscType::None: + case DiscType::Unknown: + case DiscType::UnknownType: + restoreEmptyState(m_mediaList, medium, false); + break; + } +} + +DiscType LinuxCDPolling::identifyDiscType(const QCString &devNode, + const DiscType ¤t) +{ + //kdDebug(1219) << "LinuxCDPolling::identifyDiscType(" + // << devNode << ")" << endl; + + int fd; + struct cdrom_tochdr th; + + // open the device + fd = open(devNode, O_RDONLY | O_NONBLOCK); + if (fd < 0) return DiscType::Broken; + + switch (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT)) + { + case CDS_DISC_OK: + { + if (current.isDisc()) + { + close(fd); + return current; + } + + // see if we can read the disc's table of contents (TOC). + if (ioctl(fd, CDROMREADTOCHDR, &th)) + { + close(fd); + return DiscType::Blank; + } + + // read disc status info + int status = ioctl(fd, CDROM_DISC_STATUS, CDSL_CURRENT); + + // release the device + close(fd); + + switch (status) + { + case CDS_AUDIO: + return DiscType::Audio; + case CDS_DATA_1: + case CDS_DATA_2: + if (hasDirectory(devNode, "video_ts")) + { + return DiscType::DVD; + } + else if (hasDirectory(devNode, "vcd")) + { + return DiscType::VCD; + } + else if (hasDirectory(devNode, "svcd")) + { + return DiscType::SVCD; + } + else + { + return DiscType::Data; + } + case CDS_MIXED: + return DiscType::Mixed; + default: + return DiscType::UnknownType; + } + } + case CDS_NO_INFO: + close(fd); + return DiscType::Unknown; + default: + close(fd); + return DiscType::None; + } +} + +bool LinuxCDPolling::hasDirectory(const QCString &devNode, const QCString &dir) +{ + bool ret = false; // return value + int fd = 0; // file descriptor for drive + unsigned short bs; // the discs block size + unsigned short ts; // the path table size + unsigned int tl; // the path table location (in blocks) + unsigned char len_di = 0; // length of the directory name in current path table entry + unsigned int parent = 0; // the number of the parent directory's path table entry + char dirname[256]; // filename for the current path table entry + int pos = 0; // our position into the path table + int curr_record = 1; // the path table record we're on + QCString fixed_directory = dir.upper(); // the uppercase version of the "directory" parameter + + // open the drive + fd = open(devNode, O_RDONLY | O_NONBLOCK); + if (fd == -1) return false; + + // read the block size + lseek(fd, 0x8080, SEEK_CUR); + if (read(fd, &bs, 2) != 2) + { + close(fd); + return false; + } + if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN) + bs = ((bs << 8) & 0xFF00) | ((bs >> 8) & 0xFF); + + // read in size of path table + lseek(fd, 2, SEEK_CUR); + if (read(fd, &ts, 2) != 2) + { + close(fd); + return false; + } + if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN) + ts = ((ts << 8) & 0xFF00) | ((ts >> 8) & 0xFF); + + // read in which block path table is in + lseek(fd, 6, SEEK_CUR); + if (read(fd, &tl, 4) != 4) + { + close(fd); + return false; + } + if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN) + tl = ((tl << 24) & 0xFF000000) | ((tl << 8) & 0xFF0000) | + ((tl >> 8) & 0xFF00) | ((tl >> 24) & 0xFF); + + // seek to the path table + lseek(fd, bs * tl, SEEK_SET); + + // loop through the path table entries + while (pos < ts) + { + // get the length of the filename of the current entry + if (read(fd, &len_di, 1) != 1) + { + ret = false; + break; + } + + // get the record number of this entry's parent + // i'm pretty sure that the 1st entry is always the top directory + lseek(fd, 5, SEEK_CUR); + if (read(fd, &parent, 2) != 2) + { + ret = false; + break; + } + if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN) + parent = ((parent << 8) & 0xFF00) | ((parent >> 8) & 0xFF); + + // read the name + if (read(fd, dirname, len_di) != len_di) + { + ret = false; + break; + } + dirname[len_di] = 0; + qstrcpy(dirname, QCString(dirname).upper()); + + // if we found a folder that has the root as a parent, and the directory name matches + // then return success + if ((parent == 1) && (dirname == fixed_directory)) + { + ret = true; + break; + } + + // all path table entries are padded to be even, so if this is an odd-length table, seek a byte to fix it + if (len_di%2 == 1) + { + lseek(fd, 1, SEEK_CUR); + pos++; + } + + // update our position + pos += 8 + len_di; + curr_record++; + } + + close(fd); + return ret; +} + + +#include "linuxcdpolling.moc" diff --git a/kioslave/media/mediamanager/linuxcdpolling.h b/kioslave/media/mediamanager/linuxcdpolling.h new file mode 100644 index 000000000..1df113962 --- /dev/null +++ b/kioslave/media/mediamanager/linuxcdpolling.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _LINUXCDPOLLING_H_ +#define _LINUXCDPOLLING_H_ + +#include "backendbase.h" + +#include <qobject.h> +#include <qcstring.h> +#include <qmap.h> +#include <qtimer.h> + +class DiscType +{ +public: + enum Type { None, Unknown, Audio, Data, DVD, Mixed, + Blank, VCD, SVCD, UnknownType, Broken }; + + DiscType(Type type = Unknown); + + bool isKnownDisc() const; + bool isDisc() const; + bool isNotDisc() const; + bool isData() const; + + operator int() const; + +private: + Type m_type; +}; + +class PollingThread; + +class LinuxCDPolling : public QObject, public BackendBase +{ +Q_OBJECT + +public: + + LinuxCDPolling(MediaList &list); + virtual ~LinuxCDPolling(); + + /** + * Find the disc type of the medium inserted in a drive + * (considered to be a cdrom or dvdrom) + * + * @param devNode the path to the device to test + * @param current the current known state of the drive + * @return the disc type + */ + static DiscType identifyDiscType(const QCString &devNode, + const DiscType ¤t = DiscType::Unknown); + +private slots: + void slotMediumAdded(const QString &id); + void slotMediumRemoved(const QString &id); + void slotMediumStateChanged(const QString &id); + void slotTimeout(); + +private: + void applyType(DiscType type, const Medium *medium); + + static bool hasDirectory(const QCString &devNode, const QCString &dir); + + QMap<QString, PollingThread*> m_threads; + QStringList m_excludeNotification; + QTimer m_timer; +}; + +#endif diff --git a/kioslave/media/mediamanager/mediadirnotify.cpp b/kioslave/media/mediamanager/mediadirnotify.cpp new file mode 100644 index 000000000..340414634 --- /dev/null +++ b/kioslave/media/mediamanager/mediadirnotify.cpp @@ -0,0 +1,124 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "mediadirnotify.h" + +#include <kdebug.h> + +#include <kdirnotify_stub.h> + +#include "medium.h" + +MediaDirNotify::MediaDirNotify(const MediaList &list) + : m_mediaList(list) +{ + +} + +KURL::List MediaDirNotify::toMediaURL(const KURL &url) +{ + kdDebug(1219) << "MediaDirNotify::toMediaURL(" << url << ")" << endl; + + KURL::List result; + + const QPtrList<Medium> list = m_mediaList.list(); + + QPtrList<Medium>::const_iterator it = list.begin(); + QPtrList<Medium>::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + const Medium *m = *it; + KURL base = m->prettyBaseURL(); + + if ( base.isParentOf(url) ) + { + QString path = KURL::relativePath(base.path(), + url.path()); + + KURL new_url("media:/"+m->name()+"/"+path ); + new_url.cleanPath(); + + result.append(new_url); + } + } + + kdDebug(1219) << result << endl; + return result; +} + +KURL::List MediaDirNotify::toMediaURLList(const KURL::List &list) +{ + KURL::List new_list; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL::List urls = toMediaURL(*it); + + if (!urls.isEmpty()) + { + new_list += urls; + } + } + + return new_list; +} + +ASYNC MediaDirNotify::FilesAdded(const KURL &directory) +{ + KURL::List new_urls = toMediaURL(directory); + + if (!new_urls.isEmpty()) + { + KDirNotify_stub notifier("*", "*"); + + KURL::List::const_iterator it = new_urls.begin(); + KURL::List::const_iterator end = new_urls.end(); + + for (; it!=end; ++it) + { + notifier.FilesAdded(*it); + } + } +} + +ASYNC MediaDirNotify::FilesRemoved(const KURL::List &fileList) +{ + KURL::List new_list = toMediaURLList(fileList); + + if (!new_list.isEmpty()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesRemoved( new_list ); + } +} + +ASYNC MediaDirNotify::FilesChanged(const KURL::List &fileList) +{ + KURL::List new_list = toMediaURLList(fileList); + + if (!new_list.isEmpty()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesChanged( new_list ); + } +} + diff --git a/kioslave/media/mediamanager/mediadirnotify.h b/kioslave/media/mediamanager/mediadirnotify.h new file mode 100644 index 000000000..beb1b8849 --- /dev/null +++ b/kioslave/media/mediamanager/mediadirnotify.h @@ -0,0 +1,47 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MEDIADIRNOTIFY_H_ +#define _MEDIADIRNOTIFY_H_ + +#include <kurl.h> +#include <kdirnotify.h> + +#include "medialist.h" + + +class MediaDirNotify : public KDirNotify +{ +K_DCOP + +public: + MediaDirNotify(const MediaList &list); + +k_dcop: + virtual ASYNC FilesAdded (const KURL &directory); + virtual ASYNC FilesRemoved (const KURL::List &fileList); + virtual ASYNC FilesChanged (const KURL::List &fileList); + +private: + KURL::List toMediaURL(const KURL &url); + KURL::List toMediaURLList(const KURL::List &list); + + const MediaList &m_mediaList; +}; + +#endif diff --git a/kioslave/media/mediamanager/medialist.cpp b/kioslave/media/mediamanager/medialist.cpp new file mode 100644 index 000000000..fed6091fb --- /dev/null +++ b/kioslave/media/mediamanager/medialist.cpp @@ -0,0 +1,288 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "medialist.h" + +#include <kdebug.h> + +MediaList::MediaList() +{ + kdDebug(1219) << "MediaList::MediaList()" << endl; + + m_media.setAutoDelete(true); +} + +const QPtrList<Medium> MediaList::list() const +{ + kdDebug(1219) << "MediaList::list()" << endl; + + return m_media; +} + +const Medium *MediaList::findById(const QString &id) const +{ + kdDebug(1219) << "MediaList::findById(" << id << ")" << endl; + + if ( !m_idMap.contains(id) ) return 0L; + + return m_idMap[id]; +} + +const Medium *MediaList::findByName(const QString &name) const +{ + kdDebug(1219) << "MediaList::findByName(" << name << ")" << endl; + + if ( !m_nameMap.contains(name) ) return 0L; + + return m_nameMap[name]; +} + + +QString MediaList::addMedium(Medium *medium, bool allowNotification) +{ + kdDebug(1219) << "MediaList::addMedium(@" << medium->id() << ")" << endl; + + QString id = medium->id(); + if ( m_idMap.contains(id) ) return QString::null; + + m_media.append( medium ); + m_idMap[id] = medium; + + QString name = medium->name(); + if ( !m_nameMap.contains(name) ) + { + m_nameMap[name] = medium; + + kdDebug(1219) << "MediaList emits mediumAdded(" << id << ", " + << name << ")" << endl; + emit mediumAdded(id, name, allowNotification); + + return name; + } + + QString base_name = name+"_"; + int i = 1; + + while ( m_nameMap.contains(base_name+QString::number(i)) ) + { + i++; + } + + name = base_name+QString::number(i); + medium->setName(name); + m_nameMap[name] = medium; + + kdDebug(1219) << "MediaList emits mediumAdded(" << id << ", " + << name << ")" << endl; + emit mediumAdded(id, name, allowNotification); + return name; +} + +bool MediaList::removeMedium(const QString &id, bool allowNotification) +{ + kdDebug(1219) << "MediaList::removeMedium(" << id << ")" << endl; + + if ( !m_idMap.contains(id) ) return false; + + Medium *medium = m_idMap[id]; + QString name = medium->name(); + + m_idMap.remove(id); + m_nameMap.remove( medium->name() ); + m_media.remove( medium ); + + emit mediumRemoved(id, name, allowNotification); + return true; +} + +bool MediaList::changeMediumState(const Medium &medium, bool allowNotification) +{ + kdDebug(1219) << "MediaList::changeMediumState(const Medium &)" << endl; + + if ( !m_idMap.contains(medium.id()) ) return false; + + Medium *m = m_idMap[medium.id()]; + + if ( medium.isMountable() ) + { + QString device_node = medium.deviceNode(); + QString mount_point = medium.mountPoint(); + QString fs_type = medium.fsType(); + bool mounted = medium.isMounted(); + + m->mountableState( device_node, mount_point, + fs_type, mounted ); + } + else + { + m->unmountableState( medium.baseURL() ); + } + + + if (!medium.mimeType().isEmpty()) + { + m->setMimeType( medium.mimeType() ); + } + + if (!medium.iconName().isEmpty()) + { + m->setIconName( medium.iconName() ); + } + + if (!medium.label().isEmpty()) + { + m->setLabel( medium.label() ); + } + + emit mediumStateChanged(m->id(), m->name(), !m->needMounting(), allowNotification); + return true; +} + +bool MediaList::changeMediumState(const QString &id, + const QString &baseURL, + bool allowNotification, + const QString &mimeType, + const QString &iconName, + const QString &label) +{ + kdDebug(1219) << "MediaList::changeMediumState(" << id << ", " + << baseURL << ", " << mimeType << ", " << iconName << ")" + << endl; + + if ( !m_idMap.contains(id) ) return false; + + Medium *medium = m_idMap[id]; + + medium->unmountableState( baseURL ); + + if (!mimeType.isEmpty()) + { + medium->setMimeType( mimeType ); + } + + if (!iconName.isEmpty()) + { + medium->setIconName( iconName ); + } + + if (!label.isEmpty()) + { + medium->setLabel( label ); + } + + emit mediumStateChanged(id, medium->name(), + !medium->needMounting(), + allowNotification); + return true; +} + +bool MediaList::changeMediumState(const QString &id, + const QString &deviceNode, + const QString &mountPoint, + const QString &fsType, bool mounted, + bool allowNotification, + const QString &mimeType, + const QString &iconName, + const QString &label) +{ + kdDebug(1219) << "MediaList::changeMediumState(" << id << ", " + << deviceNode << ", " << mountPoint << ", " << fsType << ", " + << mounted << ", " << mimeType << ", " << iconName << ")" + << endl; + + if ( !m_idMap.contains(id) ) return false; + + Medium *medium = m_idMap[id]; + + medium->mountableState( deviceNode, mountPoint, fsType, mounted ); + + if (!mimeType.isEmpty()) + { + medium->setMimeType( mimeType ); + } + + if (!iconName.isEmpty()) + { + medium->setIconName( iconName ); + } + + if (!label.isEmpty()) + { + medium->setLabel( label ); + } + + emit mediumStateChanged(id, medium->name(), + !medium->needMounting(), + allowNotification); + return true; +} + +bool MediaList::changeMediumState(const QString &id, bool mounted, + bool allowNotification, + const QString &mimeType, + const QString &iconName, + const QString &label) +{ + kdDebug(1219) << "MediaList::changeMediumState(" << id << ", " + << mounted << ", " << mimeType << ", " << iconName << ")" + << endl; + + if ( !m_idMap.contains(id) ) return false; + + Medium *medium = m_idMap[id]; + + if ( !medium->mountableState( mounted ) ) return false; + + if (!mimeType.isEmpty()) + { + medium->setMimeType( mimeType ); + } + + if (!iconName.isEmpty()) + { + medium->setIconName( iconName ); + } + + if (!label.isEmpty()) + { + medium->setLabel( label ); + } + + emit mediumStateChanged(id, medium->name(), + !medium->needMounting(), + allowNotification); + return true; +} + +bool MediaList::setUserLabel(const QString &name, const QString &label) +{ + kdDebug(1219) << "MediaList::setUserLabel(" << name << ", " + << label << ")" << endl; + + if ( !m_nameMap.contains(name) ) return false; + + Medium *medium = m_nameMap[name]; + medium->setUserLabel(label); + + emit mediumStateChanged(medium->id(), name, + !medium->needMounting(), + false); + return true; +} + +#include "medialist.moc" diff --git a/kioslave/media/mediamanager/medialist.h b/kioslave/media/mediamanager/medialist.h new file mode 100644 index 000000000..590491b0c --- /dev/null +++ b/kioslave/media/mediamanager/medialist.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MEDIALIST_H_ +#define _MEDIALIST_H_ + +#include <qobject.h> + +#include "medium.h" + +class MediaList : public QObject +{ +Q_OBJECT + +public: + MediaList(); + + // FIXME: should be <const Medium> or something similar... + const QPtrList<Medium> list() const; + const Medium *findById(const QString &id) const; + const Medium *findByName(const QString &name) const; + +public: + QString addMedium(Medium *medium, bool allowNotification = true); + bool removeMedium(const QString &id, bool allowNotification = true); + + bool changeMediumState(const Medium &medium, bool allowNotification); + bool changeMediumState(const QString &id, + const QString &baseURL, + bool allowNotification = true, + const QString &mimeType = QString::null, + const QString &iconName = QString::null, + const QString &label = QString::null); + bool changeMediumState(const QString &id, + const QString &deviceNode, + const QString &mountPoint, + const QString &fsType, bool mounted, + bool allowNotification = true, + const QString &mimeType = QString::null, + const QString &iconName = QString::null, + const QString &label = QString::null); + bool changeMediumState(const QString &id, bool mounted, + bool allowNotification = true, + const QString &mimeType = QString::null, + const QString &iconName = QString::null, + const QString &label = QString::null); + + bool setUserLabel(const QString &name, const QString &label); + +signals: + void mediumAdded(const QString &id, const QString &name, + bool allowNotification); + void mediumRemoved(const QString &id, const QString &name, + bool allowNotification); + void mediumStateChanged(const QString &id, const QString &name, + bool mounted, bool allowNotification); + +private: + QPtrList<Medium> m_media; + QMap<QString,Medium*> m_nameMap; + QMap<QString,Medium*> m_idMap; +}; + +#endif diff --git a/kioslave/media/mediamanager/mediamanager.cpp b/kioslave/media/mediamanager/mediamanager.cpp new file mode 100644 index 000000000..ad8f1b447 --- /dev/null +++ b/kioslave/media/mediamanager/mediamanager.cpp @@ -0,0 +1,342 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "mediamanager.h" + +#include <config.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <kglobal.h> +#include <klocale.h> + +#include <kdirnotify_stub.h> +#include <kstandarddirs.h> + +#include "mediamanagersettings.h" + +#include "fstabbackend.h" + +#ifdef COMPILE_HALBACKEND +#include "halbackend.h" +#endif //COMPILE_HALBACKEND + +#ifdef COMPILE_LINUXCDPOLLING +#include "linuxcdpolling.h" +#endif //COMPILE_LINUXCDPOLLING + + +MediaManager::MediaManager(const QCString &obj) + : KDEDModule(obj), m_dirNotify(m_mediaList) +{ + connect( &m_mediaList, SIGNAL(mediumAdded(const QString&, const QString&, bool)), + SLOT(slotMediumAdded(const QString&, const QString&, bool)) ); + connect( &m_mediaList, SIGNAL(mediumRemoved(const QString&, const QString&, bool)), + SLOT(slotMediumRemoved(const QString&, const QString&, bool)) ); + connect( &m_mediaList, + SIGNAL(mediumStateChanged(const QString&, const QString&, bool, bool)), + SLOT(slotMediumChanged(const QString&, const QString&, bool, bool)) ); + + QTimer::singleShot( 10, this, SLOT( loadBackends() ) ); +} + +MediaManager::~MediaManager() +{ + while ( !m_backends.isEmpty() ) + { + BackendBase *b = m_backends.first(); + m_backends.remove( b ); + delete b; + } +} + +void MediaManager::loadBackends() +{ + m_mediaList.blockSignals(true); + + while ( !m_backends.isEmpty() ) + { + BackendBase *b = m_backends.first(); + m_backends.remove( b ); + delete b; + } + + mp_removableBackend = 0L; + m_halbackend = 0L; + m_fstabbackend = 0L; + +#ifdef COMPILE_HALBACKEND + if ( MediaManagerSettings::self()->halBackendEnabled() ) + { + m_halbackend = new HALBackend(m_mediaList, this); + if (m_halbackend->InitHal()) + { + m_backends.append( m_halbackend ); + m_fstabbackend = new FstabBackend(m_mediaList, true); + m_backends.append( m_fstabbackend ); + // No need to load something else... + m_mediaList.blockSignals(false); + return; + } + else + { + delete m_halbackend; + m_halbackend = 0; + } + } +#endif // COMPILE_HALBACKEND + + mp_removableBackend = new RemovableBackend(m_mediaList); + m_backends.append( mp_removableBackend ); + +#ifdef COMPILE_LINUXCDPOLLING + if ( MediaManagerSettings::self()->cdPollingEnabled() ) + { + m_backends.append( new LinuxCDPolling(m_mediaList) ); + } +#endif //COMPILE_LINUXCDPOLLING + + m_fstabbackend = new FstabBackend(m_mediaList); + m_backends.append( m_fstabbackend ); + m_mediaList.blockSignals(false); +} + + +QStringList MediaManager::fullList() +{ + QPtrList<Medium> list = m_mediaList.list(); + + QStringList result; + + QPtrList<Medium>::const_iterator it = list.begin(); + QPtrList<Medium>::const_iterator end = list.end(); + for (; it!=end; ++it) + { + result+= (*it)->properties(); + result+= Medium::SEPARATOR; + } + + return result; +} + +QStringList MediaManager::properties(const QString &name) +{ + const Medium *m = m_mediaList.findByName(name); + + if (!m) + { + KURL u(name); + kdDebug() << "Media::prop " << name << " " << u.isValid() << endl; + if (u.isValid()) + { + if (u.protocol() == "system") + { + QString path = u.path(); + if (path.startsWith("/media/")) + path = path.mid(strlen("/media/")); + m = m_mediaList.findByName(path); + kdDebug() << "findByName " << path << m << endl; + } + else if (u.protocol() == "media") + { + m = m_mediaList.findByName(u.filename()); + kdDebug() << "findByName " << u.filename() << m << endl; + } + else if (u.protocol() == "file") + { + // look for the mount point + QPtrList<Medium> list = m_mediaList.list(); + QPtrList<Medium>::const_iterator it = list.begin(); + QPtrList<Medium>::const_iterator end = list.end(); + QString path; + + for (; it!=end; ++it) + { + path = KStandardDirs::realFilePath(u.path()); + kdDebug() << "comparing " << (*it)->mountPoint() << " " << path << " " << (*it)->deviceNode() << endl; + if ((*it)->mountPoint() == path || (*it)->deviceNode() == path) { + m = *it; + break; + } + } + } + } + } + + if (m) + return m->properties(); + else + return QStringList(); +} + +QStringList MediaManager::mountoptions(const QString &name) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return QStringList(); + return m_halbackend->mountoptions(name); +#else + return QStringList(); +#endif +} + +bool MediaManager::setMountoptions(const QString &name, const QStringList &options) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return false; + return m_halbackend->setMountoptions(name, options); +#else + return false; +#endif +} + +QString MediaManager::mount(const QString &name) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return i18n("Feature only available with HAL"); + return m_halbackend->mount(name); +#else + if ( !m_fstabbackend ) // lying :) + return i18n("Feature only available with HAL"); + return m_fstabbackend->mount( name ); +#endif +} + +QString MediaManager::unmount(const QString &name) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return i18n("Feature only available with HAL"); + return m_halbackend->unmount(name); +#else + if ( !m_fstabbackend ) // lying :) + return i18n("Feature only available with HAL"); + return m_fstabbackend->unmount( name ); +#endif +} + +QString MediaManager::nameForLabel(const QString &label) +{ + const QPtrList<Medium> media = m_mediaList.list(); + + QPtrList<Medium>::const_iterator it = media.begin(); + QPtrList<Medium>::const_iterator end = media.end(); + for (; it!=end; ++it) + { + const Medium *m = *it; + + if (m->prettyLabel()==label) + { + return m->name(); + } + } + + return QString::null; +} + +ASYNC MediaManager::setUserLabel(const QString &name, const QString &label) +{ + m_mediaList.setUserLabel(name, label); +} + +ASYNC MediaManager::reloadBackends() +{ + MediaManagerSettings::self()->readConfig(); + loadBackends(); +} + +bool MediaManager::removablePlug(const QString &devNode, const QString &label) +{ + if (mp_removableBackend) + { + return mp_removableBackend->plug(devNode, label); + } + return false; +} + +bool MediaManager::removableUnplug(const QString &devNode) +{ + if (mp_removableBackend) + { + return mp_removableBackend->unplug(devNode); + } + return false; +} + +bool MediaManager::removableCamera(const QString &devNode) +{ + if (mp_removableBackend) + { + return mp_removableBackend->camera(devNode); + } + return false; +} + + +void MediaManager::slotMediumAdded(const QString &/*id*/, const QString &name, + bool allowNotification) +{ + kdDebug(1219) << "MediaManager::slotMediumAdded: " << name << endl; + + KDirNotify_stub notifier("*", "*"); + notifier.FilesAdded( KURL("media:/") ); + + emit mediumAdded(name, allowNotification); + emit mediumAdded(name); +} + +void MediaManager::slotMediumRemoved(const QString &/*id*/, const QString &name, + bool allowNotification) +{ + kdDebug(1219) << "MediaManager::slotMediumRemoved: " << name << endl; + + KDirNotify_stub notifier("*", "*"); + notifier.FilesRemoved( KURL("media:/"+name) ); + + emit mediumRemoved(name, allowNotification); + emit mediumRemoved(name); +} + +void MediaManager::slotMediumChanged(const QString &/*id*/, const QString &name, + bool mounted, bool allowNotification) +{ + kdDebug(1219) << "MediaManager::slotMediumChanged: " << name << endl; + + KDirNotify_stub notifier("*", "*"); + if (!mounted) + { + notifier.FilesRemoved( KURL("media:/"+name) ); + } + notifier.FilesChanged( KURL("media:/"+name) ); + + emit mediumChanged(name, allowNotification); + emit mediumChanged(name); +} + + +extern "C" { + KDE_EXPORT KDEDModule *create_mediamanager(const QCString &obj) + { + KGlobal::locale()->insertCatalogue("kio_media"); + return new MediaManager(obj); + } +} + +#include "mediamanager.moc" diff --git a/kioslave/media/mediamanager/mediamanager.desktop b/kioslave/media/mediamanager/mediamanager.desktop new file mode 100644 index 000000000..88d8e507f --- /dev/null +++ b/kioslave/media/mediamanager/mediamanager.desktop @@ -0,0 +1,141 @@ +[Desktop Entry] +Type=Service +Name=KDED Media Manager +Name[af]=KDED Media Bestuurder +Name[ar]=مسيير الوسائط KDED +Name[az]=KDED Mediya Ä°darÉ™cisi +Name[be]=Кіраўнік ноÑьбітаў KDED +Name[bg]=Управление на уÑтройÑтвата KDED +Name[bn]=কে.ডি.ই.ডি. মিডিয়া মà§à¦¯à¦¾à¦¨à§‡à¦œà¦¾à¦° +Name[bs]=KDED upravitelj medijima +Name[ca]=Gestor de suports KDED +Name[cs]=KDED správce médià +Name[csb]=Menedżer zôpisownëch mediów dlô KDED +Name[da]=KDED MediehÃ¥ndtering +Name[de]=Medienverwaltung +Name[el]=ΔιαχειÏιστής μÎσων KDED +Name[eo]=KDatumportila administrilo +Name[es]=Gestor de dispositivos KDED +Name[et]=KDED andmekandjate haldur +Name[eu]=KDED media kudeatzailea +Name[fa]=مدیر رسانه KDED +Name[fi]=KDED-tallennusmedianhallinta +Name[fr]=Gestionnaire de média KDED +Name[fy]=KDEDED-mediabehearder +Name[ga]=Bainisteoir Meán KDED +Name[gl]=Xestor de Meios de KDED +Name[he]=×ž× ×”×œ המדיה של KDED +Name[hi]=केडीईडी मीडिया पà¥à¤°à¤¬à¤‚धक +Name[hr]=KDED upravitelj medija +Name[hu]=KDED médiakezelÅ‘ +Name[is]=KDED miðilstjóri +Name[it]=Gestore dei supporti KDED +Name[ja]=KDED メディアマãƒãƒ¼ã‚¸ãƒ£ +Name[ka]=მáƒáƒ¢áƒáƒ ებლების მმáƒáƒ თველი KDED +Name[kk]=KDED ауыÑтырмалы таÑушыларды баÑқару +Name[km]=កម្មវិធី​គ្រប់គ្រង​ពáŸážáŸŒáž˜áž¶áž“ KDED +Name[ko]=KDE ì°½ ê´€ë¦¬ìž +Name[lv]=KDED Datu nesÄ“ju menedžeris +Name[mk]=Менаџер на ноÑачи KDED +Name[ms]=Pengurus Media KDED +Name[nb]=KDED mediebehandler +Name[nds]=KDED-Medienpleeg +Name[ne]=KDED मिडिया पà¥à¤°à¤¬à¤¨à¥à¤§à¤• +Name[nl]=KDED-Mediabeheerder +Name[nn]=KDED Mediehandsamar +Name[pa]=KDED ਮਾਧਿਅਮ ਮੈਨੇਜਰ +Name[pl]=Menedżer noÅ›ników danych dla KDED +Name[pt]=Gestor de Dispositivos KDED +Name[pt_BR]=Gerenciador de MÃdia +Name[ro]=Manager multimedia KDED +Name[ru]=Управление подключаемыми уÑтройÑтвами +Name[rw]=Mugenga Igihuza KDED +Name[se]=KDED-mediagieÄ‘ahalli +Name[sk]=KDED správca médià +Name[sl]=Upravitelj medijev KDED +Name[sr]=Менаџер медијума, KDED +Name[sr@Latn]=Menadžer medijuma, KDED +Name[sv]=KDED-mediahanterare +Name[ta]=KDED மீடியா மேலாளர௠+Name[te]=కెడిఈడి మాధà±à°¯à°® à°…à°à°¿à°•à°°à±à°¤ +Name[th]=เครื่à¸à¸‡à¸¡à¸·à¸à¸ˆà¸±à¸”à¸à¸²à¸£à¸ªà¸·à¹ˆà¸à¸šà¸±à¸™à¸—ึภKDED +Name[tr]=KDED Ortam Yöneticisi +Name[tt]=KDED Media Ä°däräçe +Name[uk]=Менеджер ноÑіїв інформації Ð´Ð»Ñ KDED +Name[uz]=KDED saqlash uskunalarni boshqaruvchi +Name[uz@cyrillic]=KDED Ñақлаш уÑкуналарни бошқарувчи +Name[vi]=Trình quản là Ổ lÆ°u trữ KDED +Name[wa]=Manaedjeu di fitchîs KDED +Name[zh_CN]=KDED 介质管ç†å™¨ +Name[zh_TW]=KDED 媒體管ç†ç¨‹å¼ +Comment=Keep track of media activities and allow to (un)mount (media:/) +Comment[af]=Hou tred van media aktiwiteite en laat die (ont)koppel van 'media:/' toe +Comment[ar]=يتتبع نشاطات الوسائط Ùˆ ÙŠØ³Ù…Ø Ø¨ØªÙƒÙŠØ¨Ù‡Ø§/إزالة تركيبها (media:/) +Comment[be]=Ð’Ñдзе інфармацыю аб медыÑноÑьбітах Ñ– дазвалÑе прымацоўваць/адмацоўваць Ñ–Ñ… (media:/) +Comment[bg]=Ðаблюдение на уÑтройÑтвата и разрешаване на монтиране/демонтиране на (media:/) +Comment[bn]=কোথায় কী মিডিয়া বà§à¦¯à¦¬à¦¹à¦¾à¦° করা হচà§à¦›à§‡ খেয়াল রাখà§à¦¨ à¦à¦¬à¦‚ (আন)মাউনà§à¦Ÿ করà§à¦¨ (media:/) +Comment[bs]=Prati aktivnosti montiranja ureÄ‘aja za smjeÅ¡taj podataka (media:/) +Comment[ca]=Fa el seguiment de les activitats dels suports i permet muntar i desmuntar (media:/) +Comment[cs]=Udržuje pÅ™ehled o pÅ™ipojených zaÅ™ÃzenÃch +Comment[csb]=Dozérô zdarzeniów sparÅ‚Ä…czonëch z mediama pòdôwków ë zezwôlô je (òd)mòntowac (media:/) +Comment[da]=Hold styr pÃ¥ medieaktiviteter og tillad at (af)montere (media:/) +Comment[de]=Ãœberwacht Medien-Aktivität und ermöglicht das Einbinden/Lösen von Einbindungen (media:/) +Comment[el]=Έλεγχος ενεÏγειών των μÎσων και δυνατότητα (από)Ï€ÏοσάÏτησης (media:/) +Comment[eo]=Sekvu spurojn de datumportilaj aktivoj kaj permesu (de/sur)meti (media:/) +Comment[es]=Monitoriza las actividades de los recursos y permite (des)montarlos (media:/) +Comment[et]=Hoiab silma peal andmekandjate aktiivsusel ja võimaldab neid ühendada/lahutada (media:/) +Comment[eu]=Montatzeak begiztatzen ditu (media:/) eta desmontatzen uzten du +Comment[fa]=ØÙظ رد Ùعالیتهای رسانه Ùˆ اجازۀ سوار(پیاده) کردن (media:/) +Comment[fi]=Pidä kirjaa tallennustapahtumista ja salli tallennusvälineen liittäminen/irrotus (devices:/) +Comment[fr]=Gardez une trace des montages et permettre le (dé)montage (media:/) +Comment[fy]=Hâld de media-aktiviteiten by en stien ta om media oan- en ôf te keppelje (media:/) +Comment[gl]=Seguemento das actividades dos meios e permite-lle (des)montar (media:/) +Comment[he]=×ž× ×˜×¨ ×חר פעילויות מדיה, ומ×פשר לחבר ×ו ×œ× ×ª×§ ×”×ª×§× ×™× (media:/) +Comment[hi]=मीडिया कà¥à¤°à¤¿à¤¯à¤¾à¤“ं की जानकारी रखे तथा (अन)माउनà¥à¤Ÿ करने दे (मीडिया:/) +Comment[hr]=Praćenje aktivnosti medija i omogućavanje pristupanja i napuÅ¡tanja +Comment[hu]=Az adathordozók követése, csatlakoztatása és leválasztása (media:/) +Comment[is]=Fylgjast með breytingum á tækjum og bjóða upp á (af)tengingu (media:/) +Comment[it]=Tiene traccia delle attività dei supporti e permette di montarli o smontarli (device:/) +Comment[ja]=メディアã®æ´»å‹•ã‚’追跡ã—ã€(media:/) をマウントã—ãŸã‚Šã‚¢ãƒ³ãƒžã‚¦ãƒ³ãƒˆã—ã¾ã™ +Comment[ka]=თვáƒáƒšáƒ§áƒ£áƒ ს áƒáƒ“ევნებს მედიის áƒáƒ¥áƒ¢áƒ˜áƒ£áƒ áƒáƒ‘áƒáƒ¡ დრსáƒáƒ¨áƒ£áƒáƒšáƒ”ბáƒáƒ¡ áƒáƒ«áƒšáƒ”ვს (დე)მáƒáƒœáƒ¢áƒ˜áƒ ების გáƒáƒ™áƒ”თებáƒáƒ¡ (media:/) +Comment[kk]=ТаÑушылардың белÑендігін байқап тіркеуге не тіркеуден шығаруға мүмкіндік береді: (un)mount (media:/) +Comment[km]=ážáž¶áž˜ážŠáž¶áž“​សកម្មភាព​ពáŸážáŸŒáž˜áž¶áž“ និង​អនុញ្ញាážâ€‹áž²áŸ’យ​រៀបចំ (មិន​រៀបចំ) (media:/) +Comment[lt]=Stebi laikmenose vykdomus veiksmus ir leidžia (iÅ¡)montuoti (media:/) +Comment[lv]=Seko datu nesÄ“ju aktivitÄtÄ“m un ļauj montÄ“t/nomontÄ“t tos (media:/) +Comment[mk]=Води Ñметка за активноÑтите на ноÑачите и дозволува (од)монтирање (media:/) +Comment[ms]=Ikuti perkembangan aktiviti media dan benarkan untuk (nyah)lekap (media:/) +Comment[mt]=Å»omm kont ta' attivitajiet ta' mmuntar u ippermetti (un)mount (media:/) +Comment[nb]=Holder styr pÃ¥ monteringsaktiviteter og lar deg (av)montere (media:/) +Comment[nds]=Blifft bi all Medienaktiviteten op'n Stand un verlöövt dat In- un Afhangen (media:/) +Comment[ne]=मिडिया कà¥à¤°à¤¿à¤¯à¤¾à¤•à¤²à¤¾à¤ªà¤•à¥‹ मारà¥à¤— राखà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ र (अन)माउनà¥à¤Ÿ (media:/) गरà¥à¤¨ अनà¥à¤®à¤¤à¤¿ दिनà¥à¤¹à¥‹à¤¸à¥ +Comment[nl]=Houdt de media-activiteiten bij en staat u toe om media aan- en af te koppelen (media:/) +Comment[nn]=Held styr pÃ¥ medieaktivitetar og lèt deg montera og avmontera (media:/) +Comment[pa]=ਮਾਊਟ ਕਾਰਵਾਈਆਂ ਦੀ ਜਾਣਕਾਰੀ ਰੱਖੋ ਅਤੇ ਅਨ-ਮਾਊਟ ਕਰਨ ਦਿਓ (ਜੰਤਰ:/) +Comment[pl]=Åšledzi zdarzenia zwiÄ…zane z noÅ›nikami danych i pozwala je (od)montować (media:/) +Comment[pt]=Manter o registo das actividades de dispositivos e permitir a (des)montagem (media:/) +Comment[pt_BR]=Monitora as atividades de mÃdias e permite a (des)montagem (media:/) +Comment[ro]=UrmăreÈ™te activitățile multimedia È™i permite (de)montarea media:/ +Comment[ru]=ÐвтоматичеÑкое подключение уÑтройÑтв (media:/) +Comment[rw]=Kugumana inzira y'ibikorwa by'ibitangazamakuru no kwemerera gushyiramo(gukuramo) (ibitangazamakuru:/) +Comment[se]=Gozit mii dáhpáhuvvá median ja diktá du gálgat ja Äadnat daid (media:/) +Comment[sk]=Sledovanie pripojenia/odpojenia medià (media:/) +Comment[sl]=Nadzoruj dejanja medija in dovilo priklop/odklop (media:/) +Comment[sr]=Прати активноÑÑ‚ медијума и омогућава (де)монтирање (media:/) +Comment[sr@Latn]=Prati aktivnost medijuma i omogućava (de)montiranje (media:/) +Comment[sv]=HÃ¥ll reda pÃ¥ mediaaktiviteter och tillÃ¥t (av)montering (media:/) +Comment[ta]=இடைகà¯à®•à®¾à®²à®¤à¯à®¤à®¿à®±à¯à®•à¯à®°à®¿à®¯ நடவடிகà¯à®•à¯ˆà®•à®³à®¿à®©à¯ வைதà¯à®¤à®¿à®°à¯. (சாதனமà¯:/)தà¯à®¤à¯ˆ à®à®±à¯à®±(இறகà¯à®•)கவà¯à®®à¯ அனà¯à®®à®¤à®¿ +Comment[th]=จะคà¸à¸¢à¸•à¸´à¸”ตามà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚à¸à¸‡à¸ªà¸·à¹ˆà¸à¸šà¸±à¸™à¸—ึภà¹à¸¥à¸°à¸à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¹€à¸¡à¸²à¸™à¸—์หรืà¸à¸¢à¸à¹€à¸¥à¸´à¸à¸à¸²à¸£à¹€à¸¡à¸²à¸™à¸—์ (media:/) +Comment[tr]=Ortam iÅŸlemlerini takip et ve baÄŸlanma iÅŸlemlerine izin ver(media:/) +Comment[tt]=Cıhazlarnıñ totaÅŸuın/ayırıluın sizüçe närsä (media:/) +Comment[uk]=СпоÑтерігає за змінами Ñеред ноÑіїв інформації та дозволÑÑ” (роз)Ð¼Ð¾Ð½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ (media:/) +Comment[vi]=Theo dõi các hoạt Ä‘á»™ng của ổ lÆ°u trữ và cho phép lắp đặt hay gỡ bá» chúng ở thÆ° mục "media:/" +Comment[wa]=WÃ¥de li trace des activités media eyet permete di (dis)monter (media:/) +Comment[zh_CN]=跟踪介质活动并å…许挂载或å¸è½½(media:/) +Comment[zh_TW]=æŒçºŒè¿½è¹¤åª’體活動並å…許(解除)掛載 (media:/) +ServiceTypes=KDEDModule +X-KDE-ModuleType=Library +X-KDE-Library=mediamanager +X-KDE-FactoryName=mediamanager +X-KDE-Kded-autoload=true +X-KDE-Kded-load-on-demand=true +X-KDE-Kded-phase=1 diff --git a/kioslave/media/mediamanager/mediamanager.h b/kioslave/media/mediamanager/mediamanager.h new file mode 100644 index 000000000..1e5aa1d84 --- /dev/null +++ b/kioslave/media/mediamanager/mediamanager.h @@ -0,0 +1,90 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MEDIAMANAGER_H_ +#define _MEDIAMANAGER_H_ + +#include <kdedmodule.h> +#include <qstring.h> +#include <qstringlist.h> + +#include "medialist.h" +#include "backendbase.h" +#include "removablebackend.h" +#include "mediadirnotify.h" + +class HALBackend; +class FstabBackend; + +class MediaManager : public KDEDModule +{ +Q_OBJECT +K_DCOP +public: + MediaManager(const QCString &obj); + ~MediaManager(); + +k_dcop: + QStringList fullList(); + QStringList properties(const QString &name); + QStringList mountoptions(const QString &name); + bool setMountoptions(const QString &name, const QStringList &options); + + QString mount(const QString &uid); + QString unmount(const QString &uid); + + QString nameForLabel(const QString &label); + ASYNC setUserLabel(const QString &name, const QString &label); + + ASYNC reloadBackends(); + + // Removable media handling (for people not having HAL) + bool removablePlug(const QString &devNode, const QString &label); + bool removableUnplug(const QString &devNode); + bool removableCamera(const QString &devNode); + +k_dcop_signals: + void mediumAdded(const QString &name, bool allowNotification); + void mediumRemoved(const QString &name, bool allowNotification); + void mediumChanged(const QString &name, bool allowNotification); + + // For compatibility purpose, not needed for KDE4 + void mediumAdded(const QString &name); + void mediumRemoved(const QString &name); + void mediumChanged(const QString &name); + +private slots: + void loadBackends(); + + void slotMediumAdded(const QString &id, const QString &name, + bool allowNotification); + void slotMediumRemoved(const QString &id, const QString &name, + bool allowNotification); + void slotMediumChanged(const QString &id, const QString &name, + bool mounted, bool allowNotification); + +private: + MediaList m_mediaList; + QValueList<BackendBase*> m_backends; + RemovableBackend *mp_removableBackend; + HALBackend *m_halbackend; + MediaDirNotify m_dirNotify; + FstabBackend *m_fstabbackend; +}; + +#endif diff --git a/kioslave/media/mediamanager/removablebackend.cpp b/kioslave/media/mediamanager/removablebackend.cpp new file mode 100644 index 000000000..ea8318ecf --- /dev/null +++ b/kioslave/media/mediamanager/removablebackend.cpp @@ -0,0 +1,180 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "removablebackend.h" + +#include <klocale.h> +#include <kdirwatch.h> +#include <kurl.h> +#include <kmountpoint.h> +#include <kstandarddirs.h> + +#ifdef _OS_SOLARIS_ +#define MTAB "/etc/mnttab" +#else +#define MTAB "/etc/mtab" +#endif + + + +RemovableBackend::RemovableBackend(MediaList &list) + : QObject(), BackendBase(list) +{ + KDirWatch::self()->addFile(MTAB); + + connect( KDirWatch::self(), SIGNAL( dirty(const QString&) ), + this, SLOT( slotDirty(const QString&) ) ); + KDirWatch::self()->startScan(); +} + +RemovableBackend::~RemovableBackend() +{ + QStringList::iterator it = m_removableIds.begin(); + QStringList::iterator end = m_removableIds.end(); + + for (; it!=end; ++it) + { + m_mediaList.removeMedium(*it, false); + } + + KDirWatch::self()->removeFile(MTAB); +} + +bool RemovableBackend::plug(const QString &devNode, const QString &label) +{ + QString name = generateName(devNode); + QString id = generateId(devNode); + + if (!m_removableIds.contains(id)) + { + Medium *medium = new Medium(id, name); + medium->mountableState(devNode, QString::null, + QString::null, false); + + QStringList words = QStringList::split(" ", label); + + QStringList::iterator it = words.begin(); + QStringList::iterator end = words.end(); + + QString tmp = (*it).lower(); + tmp[0] = tmp[0].upper(); + QString new_label = tmp; + + ++it; + for (; it!=end; ++it) + { + tmp = (*it).lower(); + tmp[0] = tmp[0].upper(); + new_label+= " "+tmp; + } + + medium->setLabel(new_label); + medium->setMimeType("media/removable_unmounted"); + + m_removableIds.append(id); + return !m_mediaList.addMedium(medium).isNull(); + } + return false; +} + +bool RemovableBackend::unplug(const QString &devNode) +{ + QString id = generateId(devNode); + if (m_removableIds.contains(id)) + { + m_removableIds.remove(id); + return m_mediaList.removeMedium(id); + } + return false; +} + +bool RemovableBackend::camera(const QString &devNode) +{ + QString id = generateId(devNode); + if (m_removableIds.contains(id)) + { + return m_mediaList.changeMediumState(id, + QString("camera:/"), false, "media/gphoto2camera"); + } + return false; +} + +void RemovableBackend::slotDirty(const QString &path) +{ + if (path==MTAB) + { + handleMtabChange(); + } +} + + +void RemovableBackend::handleMtabChange() +{ + QStringList new_mtabIds; + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + + KMountPoint::List::iterator it = mtab.begin(); + KMountPoint::List::iterator end = mtab.end(); + + for (; it!=end; ++it) + { + QString dev = (*it)->mountedFrom(); + QString mp = (*it)->mountPoint(); + QString fs = (*it)->mountType(); + + QString id = generateId(dev); + new_mtabIds+=id; + + if ( !m_mtabIds.contains(id) + && m_removableIds.contains(id) ) + { + m_mediaList.changeMediumState(id, dev, mp, fs, true, + false, "media/removable_mounted"); + } + } + + QStringList::iterator it2 = m_mtabIds.begin(); + QStringList::iterator end2 = m_mtabIds.end(); + + for (; it2!=end2; ++it2) + { + if ( !new_mtabIds.contains(*it2) + && m_removableIds.contains(*it2) ) + { + m_mediaList.changeMediumState(*it2, false, + false, "media/removable_unmounted"); + } + } + + m_mtabIds = new_mtabIds; +} + +QString RemovableBackend::generateId(const QString &devNode) +{ + QString dev = KStandardDirs::realFilePath(devNode); + + return "/org/kde/mediamanager/removable/" + +dev.replace("/", ""); +} + +QString RemovableBackend::generateName(const QString &devNode) +{ + return KURL(devNode).fileName(); +} + +#include "removablebackend.moc" diff --git a/kioslave/media/mediamanager/removablebackend.h b/kioslave/media/mediamanager/removablebackend.h new file mode 100644 index 000000000..02fc0409e --- /dev/null +++ b/kioslave/media/mediamanager/removablebackend.h @@ -0,0 +1,52 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _REMOVABLEBACKEND_H_ +#define _REMOVABLEBACKEND_H_ + +#include "backendbase.h" + +#include <qobject.h> +#include <qstringlist.h> + +class RemovableBackend : public QObject, public BackendBase +{ +Q_OBJECT + +public: + RemovableBackend(MediaList &list); + virtual ~RemovableBackend(); + + bool plug(const QString &devNode, const QString &label); + bool unplug(const QString &devNode); + bool camera(const QString &devNode); + +private slots: + void slotDirty(const QString &path); + +private: + void handleMtabChange(); + + static QString generateId(const QString &devNode); + static QString generateName(const QString &devNode); + + QStringList m_removableIds; + QStringList m_mtabIds; +}; + +#endif diff --git a/kioslave/media/medianotifier/Makefile.am b/kioslave/media/medianotifier/Makefile.am new file mode 100644 index 000000000..a0bff573e --- /dev/null +++ b/kioslave/media/medianotifier/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES = -I$(srcdir)/../libmediacommon -I../libmediacommon $(all_includes) + +kde_module_LTLIBRARIES = kded_medianotifier.la +kded_medianotifier_la_LDFLAGS = -module -avoid-version $(all_libraries) +kded_medianotifier_la_LIBADD = ../libmediacommon/libmediacommon.la $(LIB_KDECORE) \ + $(LIB_KDEUI) $(LIB_KIO) +kded_medianotifier_la_SOURCES = medianotifier.cpp medianotifier.skel \ + notificationdialog.cpp notificationdialogview.ui + +noinst_HEADERS = medianotifier.h notificationdialog.h + +METASOURCES = AUTO + +services_DATA = medianotifier.desktop +servicesdir = $(kde_servicesdir)/kded + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kay.pot diff --git a/kioslave/media/medianotifier/medianotifier.cpp b/kioslave/media/medianotifier/medianotifier.cpp new file mode 100644 index 000000000..98a474ba7 --- /dev/null +++ b/kioslave/media/medianotifier/medianotifier.cpp @@ -0,0 +1,312 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "medianotifier.h" + +#include <qfile.h> +#include <qfileinfo.h> + +#include <kapplication.h> +#include <kglobal.h> +#include <kdebug.h> +#include <klocale.h> +#include <kprocess.h> +#include <krun.h> +#include <kmessagebox.h> +#include <kstdguiitem.h> +#include <kstandarddirs.h> + +#include "notificationdialog.h" +#include "notifiersettings.h" +#include "notifieraction.h" +#include "mediamanagersettings.h" + +MediaNotifier::MediaNotifier(const QCString &name) : KDEDModule(name) +{ + connectDCOPSignal( "kded", "mediamanager", "mediumAdded(QString, bool)", + "onMediumChange(QString, bool)", true ); + + connectDCOPSignal( "kded", "mediamanager", "mediumChanged(QString, bool)", + "onMediumChange(QString, bool)", true ); +} + +MediaNotifier::~MediaNotifier() +{ + disconnectDCOPSignal( "kded", "mediamanager", "mediumAdded(QString, bool)", + "onMediumChange(QString, bool)" ); + + disconnectDCOPSignal( "kded", "mediamanager", "mediumChanged(QString, bool)", + "onMediumChange(QString, bool)" ); +} + +void MediaNotifier::onMediumChange( const QString &name, bool allowNotification ) +{ + kdDebug() << "MediaNotifier::onMediumChange( " << name << ", " + << allowNotification << ")" << endl; + + if ( !allowNotification ) + return; + +// Update user activity timestamp, otherwise the notification dialog will be shown +// in the background due to focus stealing prevention. Entering a new media can +// be seen as a kind of user activity after all. It'd be better to update the timestamp +// as soon as the media is entered, but it apparently takes some time to get here. + kapp->updateUserTimestamp(); + + KURL url( "system:/media/"+name ); + + KIO::SimpleJob *job = KIO::stat( url, false ); + job->setInteractive( false ); + + m_allowNotificationMap[job] = allowNotification; + + connect( job, SIGNAL( result( KIO::Job * ) ), + this, SLOT( slotStatResult( KIO::Job * ) ) ); +} + +void MediaNotifier::slotStatResult( KIO::Job *job ) +{ + bool allowNotification = m_allowNotificationMap[job]; + m_allowNotificationMap.remove( job ); + + if ( job->error() != 0 ) return; + + KIO::StatJob *stat_job = static_cast<KIO::StatJob *>( job ); + + KIO::UDSEntry entry = stat_job->statResult(); + KURL url = stat_job->url(); + + KFileItem medium( entry, url ); + + if ( autostart( medium ) ) return; + + if ( allowNotification ) notify( medium ); +} + +bool MediaNotifier::autostart( const KFileItem &medium ) +{ + QString mimetype = medium.mimetype(); + + bool is_cdrom = mimetype.startsWith( "cd" ) || mimetype.startsWith( "dvd" ); + bool is_mounted = mimetype.endsWith( "_mounted" ); + + // We autorun only on CD/DVD or removable disks (USB, Firewire) + if ( !( is_cdrom || is_mounted ) + && mimetype!="media/removable_mounted" ) + { + return false; + } + + + // Here starts the 'Autostart Of Applications After Mount' implementation + + // The desktop environment MAY ignore Autostart files altogether + // based on policy set by the user, system administrator or vendor. + MediaManagerSettings::self()->readConfig(); + if ( !MediaManagerSettings::self()->autostartEnabled() ) + { + return false; + } + + // From now we're sure the medium is already mounted. + // We can use the local path for stating, no need to use KIO here. + bool local; + QString path = medium.mostLocalURL( local ).path(); // local is always true here... + + // When a new medium is mounted the root directory of the medium should + // be checked for the following Autostart files in order of precedence: + // .autorun, autorun, autorun.sh + QStringList autorun_list; + autorun_list << ".autorun" << "autorun" << "autorun.sh"; + + QStringList::iterator it = autorun_list.begin(); + QStringList::iterator end = autorun_list.end(); + + for ( ; it!=end; ++it ) + { + if ( QFile::exists( path + "/" + *it ) ) + { + return execAutorun( medium, path, *it ); + } + } + + // When a new medium is mounted the root directory of the medium should + // be checked for the following Autoopen files in order of precedence: + // .autoopen, autoopen + QStringList autoopen_list; + autoopen_list << ".autoopen" << "autoopen"; + + it = autoopen_list.begin(); + end = autoopen_list.end(); + + for ( ; it!=end; ++it ) + { + if ( QFile::exists( path + "/" + *it ) ) + { + return execAutoopen( medium, path, *it ); + } + } + + return false; +} + +bool MediaNotifier::execAutorun( const KFileItem &medium, const QString &path, + const QString &autorunFile ) +{ + // The desktop environment MUST prompt the user for confirmation + // before automatically starting an application. + QString mediumType = medium.mimeTypePtr()->name(); + QString text = i18n( "An autorun file has been found on your '%1'." + " Do you want to execute it?\n" + "Note that executing a file on a medium may compromise" + " your system's security").arg( mediumType ); + QString caption = i18n( "Autorun - %1" ).arg( medium.url().prettyURL() ); + KGuiItem yes = KStdGuiItem::yes(); + KGuiItem no = KStdGuiItem::no(); + int options = KMessageBox::Notify | KMessageBox::Dangerous; + + int answer = KMessageBox::warningYesNo( 0L, text, caption, yes, no, + QString::null, options ); + + if ( answer == KMessageBox::Yes ) + { + // When an Autostart file has been detected and the user has + // confirmed its execution the autostart file MUST be executed + // with the current working directory ( CWD ) set to the root + // directory of the medium. + KProcess proc; + proc << "sh" << autorunFile; + proc.setWorkingDirectory( path ); + proc.start(); + proc.detach(); + } + + return true; +} + +bool MediaNotifier::execAutoopen( const KFileItem &medium, const QString &path, + const QString &autoopenFile ) +{ + // An Autoopen file MUST contain a single relative path that points + // to a non-executable file contained on the medium. [...] + QFile file( path+"/"+autoopenFile ); + file.open( IO_ReadOnly ); + QTextStream stream( &file ); + + QString relative_path = stream.readLine().stripWhiteSpace(); + + // The relative path MUST NOT contain path components that + // refer to a parent directory ( ../ ) + if ( relative_path.startsWith( "/" ) || relative_path.contains( "../" ) ) + { + return false; + } + + // The desktop environment MUST verify that the relative path points + // to a file that is actually located on the medium [...] + QString resolved_path + = KStandardDirs::realFilePath( path+"/"+relative_path ); + + if ( !resolved_path.startsWith( path ) ) + { + return false; + } + + + QFile document( resolved_path ); + + // TODO: What about FAT all files are executable... + // If the relative path points to an executable file then the desktop + // environment MUST NOT execute the file. + if ( !document.exists() /*|| QFileInfo(document).isExecutable()*/ ) + { + return false; + } + + KURL url = medium.url(); + url.addPath( relative_path ); + + // The desktop environment MUST prompt the user for confirmation + // before opening the file. + QString mediumType = medium.mimeTypePtr()->name(); + QString filename = url.filename(); + QString text = i18n( "An autoopen file has been found on your '%1'." + " Do you want to open '%2'?\n" + "Note that opening a file on a medium may compromise" + " your system's security").arg( mediumType ).arg( filename ); + QString caption = i18n( "Autoopen - %1" ).arg( medium.url().prettyURL() ); + KGuiItem yes = KStdGuiItem::yes(); + KGuiItem no = KStdGuiItem::no(); + int options = KMessageBox::Notify | KMessageBox::Dangerous; + + int answer = KMessageBox::warningYesNo( 0L, text, caption, yes, no, + QString::null, options ); + + // TODO: Take case of the "UNLESS" part? + // When an Autoopen file has been detected and the user has confirmed + // that the file indicated in the Autoopen file should be opened then + // the file indicated in the Autoopen file MUST be opened in the + // application normally preferred by the user for files of its kind + // UNLESS the user instructed otherwise. + if ( answer == KMessageBox::Yes ) + { + ( void ) new KRun( url ); + } + + return true; +} + +void MediaNotifier::notify( KFileItem &medium ) +{ + kdDebug() << "Notification triggered." << endl; + + NotifierSettings *settings = new NotifierSettings(); + + if ( settings->autoActionForMimetype( medium.mimetype() )==0L ) + { + QValueList<NotifierAction*> actions + = settings->actionsForMimetype( medium.mimetype() ); + + // If only one action remains, it's the "do nothing" action + // no need to popup in this case. + if ( actions.size()>1 ) + { + NotificationDialog *dialog + = new NotificationDialog( medium, settings ); + dialog->show(); + } + } + else + { + NotifierAction *action = settings->autoActionForMimetype( medium.mimetype() ); + action->execute( medium ); + delete settings; + } +} + +extern "C" +{ + KDE_EXPORT KDEDModule *create_medianotifier(const QCString &name) + { + KGlobal::locale()->insertCatalogue("kay"); + return new MediaNotifier(name); + } +} + +#include "medianotifier.moc" diff --git a/kioslave/media/medianotifier/medianotifier.desktop b/kioslave/media/medianotifier/medianotifier.desktop new file mode 100644 index 000000000..1bd0a2be1 --- /dev/null +++ b/kioslave/media/medianotifier/medianotifier.desktop @@ -0,0 +1,122 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KDEDModule +X-KDE-ModuleType=Library +X-KDE-Library=medianotifier +X-KDE-FactoryName=medianotifier +X-KDE-Kded-autoload=true +X-KDE-Kded-load-on-demand=false +Name=Media Notifier Daemon +Name[af]=Media Inkennistelling Bediener +Name[ar]=مراقبالتبليغ عن وسائط +Name[be]=ДÑман нагадваннÑÑž аб ноÑьбітах +Name[bg]=Мултимедиен демон +Name[bn]=মিডিয়া বিজà§à¦žà¦ªà§à¦¤à¦¿ ডিমন +Name[bs]=Daemon za obavjeÅ¡tenja o novim medijima +Name[ca]=Dimoni notificador de suports +Name[cs]=Démon upozorňovánà na média +Name[csb]=Ùsłëżnota pòwiadomieniô ò zôpisownëch mediach +Name[da]=Mediebekendtgørelsesdæmon +Name[de]=Geräteüberwachung +Name[el]=Δαίμονας ειδοποίησης μÎσων +Name[eo]=Media-atentigilo +Name[es]=Demonio de notificaciones de medios +Name[et]=Andmekandjate märguannete deemon +Name[eu]=Euskarrien jakinarazpen daemon-a +Name[fa]=Ø´Ø¨Ø Ø§Ø®Ø·Ø§Ø±Ø¯Ù‡Ù†Ø¯Û€ رسانه +Name[fi]=Mediahuomautin +Name[fr]=Démon de notifications des média +Name[fy]=Medianotifikaasje-daemon +Name[gl]=Demo de Notificacións dos Médios +Name[he]=שירות הודעות מערכת +Name[hr]=Demon obavijesti o medijima +Name[hu]=LemezfigyelÅ‘ szolgáltatás +Name[is]=Miðils tilkynningarpúki +Name[it]=Demone notifiche dispositivi +Name[ja]=メディア通知デーモン +Name[ka]=მედიის შემტყáƒáƒ‘ინებელი +Name[kk]=Медиа туралы құлақтандыру қызметі +Name[km]=ដáŸáž˜áž·áž“​របស់​ឧបករណáŸâ€‹áž”្រាប់​ដំណឹង​មáŸážŒáŸ€ +Name[lt]=Media praneÅ¡imų tarnyba +Name[nb]=Medievarslingsnisse +Name[nds]=Medien-Narichtendämoon +Name[ne]=मिडिया सूचक डेइमन +Name[nl]=Medianotificatie-daemon +Name[nn]=MediepÃ¥minningsnisse +Name[pa]=ਮੀਡਿਆ ਸੂਚਨਾ ਡੈਮਨ +Name[pl]=UsÅ‚uga powiadamiania o noÅ›nikach +Name[pt]=Servidor de Notificação de Dispositivos +Name[pt_BR]=Daemon de Notificação de MÃdia +Name[ro]=Daemonul de notificare mediu +Name[ru]=Демон уведомлений от подключаемых уÑтройÑтв +Name[sk]=Notifikátor medià +Name[sl]=Demon za obveÅ¡Äanje o nosilcih +Name[sr]=Демон за обавештења о медијумима +Name[sr@Latn]=Demon za obaveÅ¡tenja o medijumima +Name[sv]=Demon för mediaunderrättelser +Name[th]=เดมà¸à¸™à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¸ªà¸·à¹ˆà¸à¸šà¸±à¸™à¸—ึภ+Name[tr]=CD/USB/Firewire Durum Ä°zleme Sistemi +Name[uk]=Демон ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ ноÑÑ–Ñ— інформації +Name[uz]=Saqlash uskunalar uchun demon +Name[uz@cyrillic]=Сақлаш уÑкуналар учун демон +Name[vi]=Trình ná»n Thông báo á»” lÆ°u trữ +Name[wa]=Demon di notifiaedje media +Name[zh_CN]=ä»‹è´¨é€šçŸ¥å®ˆæŠ¤ç¨‹åº +Name[zh_TW]=媒體通知伺æœç¨‹å¼ +Comment=A media plugged notifier +Comment[af]='n Inpropbare media inkennissteller +Comment[ar]=مبلÙغ عن إدخال وسائط +Comment[be]=ДÑман нагадваннÑÑž аб зменах Ñтану ноÑьбітаў +Comment[bg]=Мултимедиен демон за уведомÑване при поÑтавÑне/включване на ново мултимедийно уÑтройÑтво +Comment[bn]=নতà§à¦¨ মিডিয়া পà§à¦°à¦¬à§‡à¦¶ করলে তা জানায় +Comment[bs]=ObavjeÅ¡tenje da je novi ureÄ‘aj prikljuÄen +Comment[ca]=Un notificador de què s'ha introduït un suport +Comment[cs]=Upozorňovánà na pÅ™ipojená média +Comment[csb]=Wiédzô ò przëłączeniô zôpisownëch mediów +Comment[da]=En opdager af at medier sættes ind +Comment[de]=Benachrichtigt das System über angeschlossene Geräte +Comment[el]=Ένας ειδοποιητής εισαγωγής μÎσου +Comment[eo]=Media konekt-atentigilo +Comment[es]=Un notificador de medios empotrable +Comment[et]=Andmekandja ühendamisest märkuandev deemon +Comment[eu]=Euskarri konektatze jakinarazlea +Comment[fa]=یک اخطاردهندۀ وصل‌شدۀ رسانه +Comment[fi]=Liitetyn median huomautin +Comment[fr]=Notification de connexion de média +Comment[fy]=Meidieling fan oankeppele media +Comment[gl]=Un notificador de meios disponÃbeis +Comment[hr]=PrikljuÄeno obavjeÅ¡tavanje o medijima +Comment[hu]=LemezérzékelÅ‘ +Comment[is]=Tilkynnari um tengda miðla +Comment[it]=Notifiche dei dispositivi attaccati +Comment[ja]=メディアã®æŽ¥ç¶šã‚’通知ã—ã¾ã™ +Comment[ka]=მედიის ჩáƒáƒ“გმის შემტყáƒáƒ‘ინებელი +Comment[kk]=Медиа Ñалынған туралы құлақтандыру +Comment[lt]=PerspÄ—jimo apie media prijungimÄ… priedas +Comment[mk]=ИзвеÑтување за приклучен медиум +Comment[nb]=En medievarsler som programtillegg +Comment[nds]=En Deenst för Narichten över tokoppelt Reedschappen +Comment[ne]=मिडिया पà¥à¤²à¤— गरिà¤à¤•à¥‹ सूचक +Comment[nl]=Meldingen van aangesloten media +Comment[nn]=Ein medievarslar som programtillegg +Comment[pa]=ਇੱਕ ਮੀਡਿਆ ਪਲੱਗ ਸੂਚਕ +Comment[pl]=Powiadamianie o podÅ‚Ä…czeniu noÅ›ników danych +Comment[pt]=Um notificador ligado aos dispositivos +Comment[pt_BR]=um notificador para mÃdia que é espetada (plug) +Comment[ro]=Un notificator de adăugare mediu +Comment[ru]=СиÑтема обработки уведомлений от подключаемых уÑтройÑтв +Comment[sk]=Notifikátor pripojených médià +Comment[sl]=ObveÅ¡Äanje o vstavljenih/priklopljenih nosilcih +Comment[sr]=Обавештавач о укљученим медијима +Comment[sr@Latn]=ObaveÅ¡tavaÄ o ukljuÄenim medijima +Comment[sv]=Inbäddad mediaunderrättelse +Comment[th]=ตัวà¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™à¸à¸²à¸£à¹€à¸ªà¸µà¸¢à¸šà¸ªà¸·à¹ˆà¸à¸šà¸±à¸™à¸—ึภ+Comment[tr]=CD/USB/Firewire durum izleme sistemi +Comment[tt]=Cıhaz totaşılÄŸan buluı turında beldergeç +Comment[uk]=Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ð¾Ñіїв інформації +Comment[uz]=Saqlash uskunalar ulanganida xabar beruvchi +Comment[uz@cyrillic]=Сақлаш уÑкуналар уланганида хабар берувчи +Comment[vi]=Trình thông báo vá» các ổ lÆ°u trữ đã kết nối +Comment[wa]=Notifiaedje di medias tchôké dvins +Comment[zh_CN]=介质æ’入通知器 +Comment[zh_TW]=媒體æ’å…¥é€šçŸ¥ç¨‹å¼ diff --git a/kioslave/media/medianotifier/medianotifier.h b/kioslave/media/medianotifier/medianotifier.h new file mode 100644 index 000000000..c3e4b9bf9 --- /dev/null +++ b/kioslave/media/medianotifier/medianotifier.h @@ -0,0 +1,57 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _MEDIANOTIFIER_H_ +#define _MEDIANOTIFIER_H_ + +#include <kdedmodule.h> +#include <kfileitem.h> +#include <kio/job.h> + +#include <qstring.h> +#include <qmap.h> + +class MediaNotifier: public KDEDModule +{ + Q_OBJECT + K_DCOP + +public: + MediaNotifier( const QCString &name ); + virtual ~MediaNotifier(); + +k_dcop: + void onMediumChange( const QString &name, bool allowNotification ); + +private slots: + void slotStatResult( KIO::Job *job ); + +private: + bool autostart( const KFileItem &medium ); + void notify( KFileItem &medium ); + + bool execAutorun( const KFileItem &medium, const QString &path, + const QString &autorunFile ); + bool execAutoopen( const KFileItem &medium, const QString &path, + const QString &autoopenFile ); + + QMap<KIO::Job*,bool> m_allowNotificationMap; +}; +#endif + diff --git a/kioslave/media/medianotifier/notificationdialog.cpp b/kioslave/media/medianotifier/notificationdialog.cpp new file mode 100644 index 000000000..a4dab0245 --- /dev/null +++ b/kioslave/media/medianotifier/notificationdialog.cpp @@ -0,0 +1,147 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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 "notificationdialog.h" +#include <qlayout.h> + +#include <krun.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kio/global.h> +#include <klistbox.h> +#include <qlabel.h> +#include <qcheckbox.h> +#include <qpushbutton.h> + +#include "actionlistboxitem.h" +#include "notificationdialogview.h" + +NotificationDialog::NotificationDialog( KFileItem medium, NotifierSettings *settings, + QWidget* parent, const char* name ) + : KDialogBase( parent, name, false, i18n( "Medium Detected" ), Ok|Cancel|User1, Ok, true), + m_medium(medium), m_settings( settings ) +{ + setCaption( KIO::decodeFileName(m_medium.name()) ); + clearWState( WState_Polished ); + + QWidget *page = new QWidget( this ); + setMainWidget(page); + QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() ); + + m_view = new NotificationDialogView( page ); + + topLayout->addWidget(m_view); + m_view->iconLabel->setPixmap( m_medium.pixmap(64) ); + m_view->mimetypeLabel->setText( i18n( "<b>Medium type:</b>" ) + " " + + m_medium.mimeTypePtr()->comment() ); + + updateActionsListBox(); + + resize( QSize(400,400).expandedTo( minimumSizeHint() ) ); + + + m_actionWatcher = new KDirWatch(); + QString services_dir + = locateLocal( "data", "konqueror/servicemenus", true ); + m_actionWatcher->addDir( services_dir ); + + setButtonText( User1, i18n("Configure...") ); + + connect( m_actionWatcher, SIGNAL( dirty( const QString & ) ), + this, SLOT( slotActionsChanged( const QString & ) ) ); + connect( this , SIGNAL( okClicked() ), + this, SLOT( slotOk() ) ); + connect( this, SIGNAL( user1Clicked() ), + this, SLOT( slotConfigure() ) ); + connect( m_view->actionsList, SIGNAL( doubleClicked ( QListBoxItem*, const QPoint & ) ), + this, SLOT( slotOk() ) ); + + connect( this, SIGNAL( finished() ), + this, SLOT( delayedDestruct() ) ); + + m_actionWatcher->startScan(); + QPushButton * btn = actionButton( Ok ); + btn->setFocus(); +} + +NotificationDialog::~NotificationDialog() +{ + delete m_actionWatcher; + delete m_settings; +} + +void NotificationDialog::updateActionsListBox() +{ + m_view->actionsList->clear(); + + QValueList<NotifierAction*> actions + = m_settings->actionsForMimetype( m_medium.mimetype() ); + + QValueList<NotifierAction*>::iterator it = actions.begin(); + QValueList<NotifierAction*>::iterator end = actions.end(); + + for ( ; it!=end; ++it ) + { + new ActionListBoxItem( *it, m_medium.mimetype(), + m_view->actionsList ); + } + + m_view->actionsList->setSelected( 0, true ); +} + + +void NotificationDialog::slotActionsChanged(const QString &/*dir*/) +{ + m_settings->reload(); + updateActionsListBox(); +} + +void NotificationDialog::slotOk() +{ + QListBoxItem *item = m_view->actionsList->selectedItem(); + + if ( item != 0L ) + { + ActionListBoxItem *action_item + = static_cast<ActionListBoxItem*>( item ); + NotifierAction *action = action_item->action(); + + launchAction( action ); + } +} + +void NotificationDialog::launchAction( NotifierAction *action ) +{ + if ( m_view->autoActionCheck->isChecked() ) + { + m_settings->setAutoAction( m_medium.mimetype(), action ); + m_settings->save(); + } + + action->execute(m_medium); + + QDialog::accept(); +} + +void NotificationDialog::slotConfigure() +{ + KRun::runCommand("kcmshell media"); +} + +#include "notificationdialog.moc" diff --git a/kioslave/media/medianotifier/notificationdialog.h b/kioslave/media/medianotifier/notificationdialog.h new file mode 100644 index 000000000..94db1fee0 --- /dev/null +++ b/kioslave/media/medianotifier/notificationdialog.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <jr.falleri@laposte.net> + Copyright (c) 2005 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _NOTIFICATIONDIALOG_H_ +#define _NOTIFICATIONDIALOG_H_ + +#include <kdialogbase.h> +#include <klistbox.h> +#include <kdirwatch.h> +#include <kfileitem.h> + +#include "notifiersettings.h" +#include "notifieraction.h" +#include "notificationdialogview.h" + +class NotificationDialog : public KDialogBase +{ + Q_OBJECT + +public: + NotificationDialog( KFileItem medium, NotifierSettings *settings, + QWidget* parent = 0, const char* name = 0 ); + ~NotificationDialog(); + +private slots: + void slotOk(); + void slotConfigure(); + void slotActionsChanged(const QString& dir); + +private: + void launchAction( NotifierAction *action ); + void updateActionsListBox(); + + KFileItem m_medium; + NotifierSettings *m_settings; + KDirWatch * m_actionWatcher; + NotificationDialogView *m_view; +}; + +#endif diff --git a/kioslave/media/medianotifier/notificationdialogview.ui b/kioslave/media/medianotifier/notificationdialogview.ui new file mode 100644 index 000000000..f37a007b2 --- /dev/null +++ b/kioslave/media/medianotifier/notificationdialogview.ui @@ -0,0 +1,117 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>NotificationDialogView</class> +<widget class="QWidget"> + <property name="name"> + <cstring>NotificationDialogView</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>508</width> + <height>480</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="spacing"> + <number>15</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="spacing"> + <number>20</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>iconLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="pixmap"> + <pixmap>image0</pixmap> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>A new medium has been detected.<br><b>What do you want to do?</b></string> + </property> + <property name="textFormat"> + <enum>RichText</enum> + </property> + </widget> + </hbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>mimetypeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + <widget class="KListBox"> + <property name="name"> + <cstring>actionsList</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>autoActionCheck</cstring> + </property> + <property name="text"> + <string>&Always do this for this type of media</string> + </property> + </widget> + </vbox> +</widget> +<images> + <image name="image0"> + <data format="XPM.GZ" length="55092">789cccbd47b7eb38b2ef397f9fa2d6e5ecae5e7cf266d0035266cb6da7ed77af1ed07b2f1aa9bf7c83883f704c56d5cd7c75bb5f95d6c9ca5f8a22114044202200f0fccffffcdbe7d3fddffef37ffe8fea625c02eb6f966f947ffb4fbb4e92ebfff57fff9fffcffff88fe160f0b7fecf68389bfe6df41fffc7fff80febf96fd6df9401ff5fcff64232ffde36244f7a762ac953fefd56f29cf35cf2a267f3072ff9f733c906e7836493f35eb2c59fd74ab6797b1f243bfcfb46b2cb7fef0a1e72799c5af2907f7f94cce5732cc963fefd483297cf3a8387d43f992999ffde0c25f3dfb79a64de5f662099dfcfe924cf386f24f3fe4a44ff0fd17e5b327f9e772799dfcfb8078fa87d662499cbebbb9249de6fc9bcbd9d2f99dab795ccc7b31a48e6e3b71a4ae6e315ed25f3f1b04f82a93f0dc1636a9ff32999daf32599f757a54ae6fde17e48e6fae23892b97e78a2bd63ea1f438cef849e97e892f9f7652b99b76f3d91cce53763f0947e6f08fd9c1137b664de5e43c83fc7f53bf0022cf475499c8be719c4f113d824de5cc01671fd0eb6895dbaff50d8ab174ae6f2ad3ac9a47f63c924df4832d73f732a99f777f324998fb73993ccfb5f7f96ccc7df3a49e6e3514592b9bd3aa664ae1fd6bd646eaf9a2b98f4ddf52573fdb58e60d89f6d49e6df9b13c97c3c1d45326fbf634826ffb2934cfee40086fdf88164de7ff65232d9eb9b646e1fa607867e9bbe641a8f816092cf14f2423f4d070c7d8b9f25f3e7bbaf60e89fb791ccfb3b15df43ffe22f30f42f13f240ffea1b18fab716bf877ee967b043dc92bf1f0ea16fdd9af3a8f740bc3dbe64f2f70bc9bcbfcd2fc9bcbfbd4432f90b4730f5cff55e3297bfc9c0187f732899f4772099c6e35b321f7f6b0fc6f8daa6649aff7692f9f3724d32d7f7cb4932ef6ffb4e32e9b302c6f86723c9dcdefc50326f9ff32e99f7975382a10fd75c32d7e795906742fdd3bd49e6df67a2bdd09ff20886beb89164fefcd54932ef9ffa2c98fca72bda0b7d6a9f25f3feb0c4f842bf9a07b0d02fd13fd0afe2090cffd6c660e85b24ae87be05a40f63e1df8277c9bc7debab643e5e575732efef6e2799cb1bfcf89ecbdbed2573fd5cfd781e1fcf952d98fa3b91cf277f74cd24f3fe0f3ec08807ba8d64ae9fc54232e98f27993faff9f13df787ae680ff4d57995cce57103c95c7fac3bc97c7c7c5f308d67b8028bf9f72299cbd3dc49e6f2981f92797fd69664de5f7e2499dbb33b92ccdb6f2dc1d067772099fca72e99b7ff6a49a6f946f437f4b9bd48267934b0d0ef77c9bc7dad68df9cee9fee25f3fe69447fcc31df88f1843eaf6e92f9f3da89649a0fee25f3f1cb85be41ff0331bed07f5f8ca7f0a7d43f13a1df9d2a99f777f32899dfffea80111faeae92b93cee41f090f7effa0c463ce77f48a6f9ee51321fafeb4930f547bb01433fecb164fe7cff5b321f2f2f92ccfb3f93d793bd745b30f421ae2593bfd94be6fd1b2692b93cde4e32b79fda164ced8ddec1d017e74332f9dbb564de5e772899e299140c7dcaa692b93db8df92797fe61d18fe3117e303fd59b792e979b164de3f8918ff05f5cf46f40ff4c715e301fdb9bd80e13f57f792797fad4a30fce906bf17f3f51ae331c4fc19a13d43f89780e693a9f0a7ed028cebfd4e321fafe6198c7cc00b2473f9bc77c95cdf4a4ff0908fc75a5c8f78bfb52553fcf02999ebbfdf08a6f10e5ec1c2bfdc49a6f97c2b99e6ff27c9642f3fbee7fab416f2407fc299643efe912f99ec692099cbd3b492b9bc9ab8ff94c63711fd0bfdb2a792c93e2ac9140f689229df5125f3feed5660e85ffd2a99fce34432efdfee00867e5e14c9941f8ec0d0bfcc904cf9572a99b7a76e25f3fe73c760a1afaa646ebf99e85fe86f2ec61bfaea0b79e11f0ba12f2eda8bfb4b7d267b98897c64dd81a1cfeb503297afbd934cf1d45e30fc93f81ef363b896ccefbf3a4a26793dc1149fae7d30f247ff2a99ea1d3fbee7fed68b05a31eb1924cf9c2083ca5f6076bc9542fd948e6fa900e2473fdbbbc09463d630246be1a6692f9f84481643edef95532b73f5df01cfd7993ccdb7be92453beb5924cf3c14432efbf5cfc7e01fffa2999e6bfb1646e5f9b022ce2cba364de3e37114cfec132c0d047f74d32f9e37b30f26b2f05431f5763c9bc7f42c1d04797e6cf99d0c71be41fc21fa52a18fe24207d9f0bfd5a1d04d3f8570fe009d573dca3642e7fbb054f697ca237c1246f3a944cf53a5b32c5a72f60f81bd796ccaf4f2bc994af2d2593be6f2453bea682e7345eed4e32d5ff14c9bcbf2f57c95cff7dd15ef8a35097ccdb93cd25537e2bfa6b3124fffd055ec2bfcc24537c548045fe518331decda7647ebd2eda6ba37fc478c01f6d44ffb8d43e1ded17e3af3960cc97f5022cf481e257a6dd149fd96bf08cc6ffe64aa6f92604c35eec4032b7efab2799e2375b30e94b2aae5f50ff58aa642e6fe148a67ce1065e927fb80d2553bd2494cc9fdf3e49a6fc45c863c07e1dc97cfcd3a3606a5f7d05633e887682497faf4bb08df8e2030cfb2bc9de17a2ff4bf49f886762f4cf10f666bd80618f3e8dff92f5177f5e914be6ed2f36e005c91b5f24533cf22598ecafeec04be467ae64f28757b041bfaf5f040f29fe68c0268d9726da63917e14a2bdd0c7f424998f5f44fdb314fd91bf83a18f8105467fdc4660e86783e789fee94a30e215e3118c78a3a0f9dc60fd45fa34072f491fc329d8a0f685a964aae7dfc0265d5f2f05537f568f60d86bb091ccfbe36248e6fad988dfbba85f8e24533e48f66f88fe892b30fa67634ba6f97508467f6d1cb0a827435ed11f35fa6388f9c9a7f8c814f157fd065e92fe5f6e6083e42f5cb0897ac417d8227dca3fc036f55fb305431fba77c1747f3b02bba4df3ad9ab29e42f669229df46fb647fe460d8d3aa0243fecd2758d4bbefc0a21eb403a33f2a8a772c263f6fff35055ba827d1f85aa2bf2f1a58f4f70a8cfb252a18febdf1c0f02701f58f2dda7f5d48a6fe9b8071fff21d2cfc83603caf51c1b87fb106433f4b1a1f87fd17eebf1b17ec903efbd43e46b0f71a0c79cb2d58c4e72d18cf2f1bb078be0dc6f3339abf5cd11f8906c6f5de1a8cf846e3f6c1cbe724df068cf6548964ae3f1b130c7f9096608c7f7603a3bde90e8ce7bb2f60b4d77e00c39f075c9f98f639e47fa34832d7e7f84c2c9eb7ba80216ffb08c6f3cc042cd64f1a30e42f9760d88fe382d19e1ced19c11e42aebf0cf17cd306435ecb04a33d610e16f20b16f1470a16ed3981457cf9443c82bdae2660e19f79bc341c8be7d70e18cfd71ec022df8ac178feaa05e379d72bf108e3df70ff369cc8fe1d8345bc9c81d1fe60452cfaeb7a03c3be74ba7e2adbd382f1fbcb3b18e3910dc1689f4df24e657fd4603cafb2c168bffb0cc6f32b1ebf0c67e2fe01e9d34cdcef1283e12faa6f30ee67f860e1ffe660a1efdc7f0ee7e2fed58c58b6e70cc6ef8d002cd6173db0a83f2dc0d0b78edbf77021e5bd078bf6aec1f87d41fabd14f2155bb0f8fd108cdfeb2330da93de83f1fc8ac6d310f2ac1a309ed772ff3734c5fdb48d64ee0fb72e58d4e75ec0f87d7202a33f0d713df4a5237bb7e4782a60b11ee681d15eed112cee1782a1cf158f4f86b6b8bf6581c5fa2af5a733823f762d30eed7d2efdd11f2efcd1d18d76bdc5e468311f2a3c007e379f5082cea75f760e8fbfa8d782cf49dfba3d150fcbebc030b7bdc83a17f099faf472371ffe64c2ceed73460e883cefb6b3416f7ab4fc463e10f2c30e46b8927e279b94b2cee6f6cc1d087ec092cec4930c6f342f24f85fc46442caef75660e8cf85fb93d18cfd9ec783f1162ceaa37cfe19cdc5fd3ceabfb990d79d80d13eeb152cd64fe9fa85f8de7c05a33df9188cf66cbec0d0c7f50d0c7df3b8ff1f2d85bcce002ce227078ceb1d0f2cfca54e3c41fb6f74bd21ef5780f17c5d058bf53beeaf46e618ebd77a2299c77f5bba9f25c6b7f4c1c2bfbd80d19eeb07f104fda1933cb678de2603e37eba01c6ef6f57b0a8ff71ff3d72c6a8b7e8d47faee88fb507c6effd188cdf3bdc1ec67d38c739e2e3c5a63393e21723269e60fde4f22c99d677b93e307577285eb7b8fd8f27ecfe9cf50e0c7d8a79ff8ca7ec7bfefb6b412cc627db81c57e823730ead9eb1158d43fb8bce399b8fe7a0716eb5d7330c663c3fb733c67eda1fa484d2cc6c39a83a11fed193ca57a651282b13e16f3f96ebc9860bdc8d880c5fe810c2cf21bae8fe3e544d4cb23c93c5ed30a30ea071b170cff92d0f81813d443d71730eaed9a07c6f53eb7b7b1c9bee7fa117c49a6f5024b30c5b3760cb6b19ec9e3adb13331e8face93ccfbaf3883f13c23013ba8a72e05d3fd0d1b8c7c734df23b53d8df86facf9da03e7e1b80f1fc82f7e76430813e6d63e229fa3be5e333194e4cd40b52b04ded31b8fe4e4653110ff2fa0a73a736f28d9a788afd67fa180cfd6a1db0588fe3fe7f32610accfb77c3f59721f2c77c493cc5fe0aaf00e37eb731788afa4e059ed1f8361f60d48f6dde1f93d914ebc7460a46bd2fe4fd37994fc7940f6cbec1e89f98cf5f93a578def5013c433d86da6f4c51bf09b9bd4dcce982f2b39cebcbc49a22fff6b83e4dec29ea15d95932adb704c433acffebf43c776ad1fd039d7886f5d315bf3f5356f09acf77d3d1d4a5fa65734f3cc3fe035d134cf7372c30ec355b824dc413dc3fb0e9906574dcdf79c4e2f74501c6fa5b74104cdfb73c3e9d4ea6d6887f7fdb10b3e791bef1f9940ddf1ceb078fe005f5b7c5e7e3e96c86f58f8d0246fd3a76c1f311f9b377b043e31bf07c693a9fa1de19df816dec07f804bb584fa3fe5accc47a86069e517b82002cd6f775b043f39bc6e3dde9923d8eeb7332035bd47f3705ec60ff824d3cc7fecf84ebefd498c19e2faf609bfa2be6f9f0d49c2d51cff5c016e5fb2b176c8f78fb8b15d8a5fa99c9e7d7a93543fdab1b831dfafde6287844f5d10e8cf1af49bfac39c63bff024f499e98ee6fb3fb937f7d023bd4fed407bb435aafb124d37e2d5d30b53f03cf87d43eeb0d8cf5dd7803c67cb7e1f63d75587f707df1e9fece7c30a2fd5d7b30f6b3469f9269fdc792cce5594dc1587fba52ffb84c1edebecd5e326f7f41faebceb19f2cda8271bfa6133ca2e727e0118d4fb2164cfa9c2892f978af4c30eca1f1c19321d7bf2bff7e3660e34ff5e53bb043cfaf5dc174bf6c0076a97fdb929805acfcfb6d0446bdbee4ed9f0d857eddd660f89b6e2298da5f05c4ac3f687e6bc0c311ad87e07a263f9f0fda77f094da537f8067245fc5f569369a1963d29f2b31eb5fdabfe20ba6e7a777e0d188ff3eb9815906c29fa78227d04f053ca3f6b65c3f6663260fd553a93d2cfca4fec91bc95cfeea041e517f5fb8fece2662fcdd023c86ff33c1d31167cd06cf21cf5c308d679d8017d41f0df72fb3e9cc1ef3ebab14ec8c69bfc05432ed47d38885fe5d4cc9b4fe37028fc8fecb37c9649f25784cf6ebbf82e7f47dbc042fe87e05f5cf4ce8936b82e7f4fb36113ca6f17a002fe8f986b8de80fe8bef4dd28fc292cce52fa68269fc4d1effb0db2f49decd0c0c7fdc92fe2ce6131a3f6d2d98fcb7bf02cf49bf370330d68fea5a30fdde38831dfabea3fe59b2fea178b0009bd41e4f03db23aeff37713decb5f00593fc2df5a7c1ee47eb6f2330eadd17156c8ea87ff7609bfc45590b267f78d5c02e714bfa6ab2f1207ff80136c87ef22918f34dfe06c67cd186c48b018dafcee3cd99c5ee47faa48217647f99609bf4c7a0f1b2160392afa3f6da6cfc68bcd69279ff949f6093daef4ec1f067f5817831a4e76de8f70ebb9ece177c806d6abfb3235ec0dfba2bc9b4df9bc6d7ed1376ae5f5c3fe703263f6f4f520a1e53fc99102f66d4be8cf7cf7cc8e4e3d7673e784c7cdd82a7d4de5a03c3fe751e9f3265c2fca09be009d94fc2e397394b87c99f186bf08cfa4fe3fd3b9fb0e7717dacc792e97c0b8f5f58f8bb20d6793d613e5f2cc7b49fc5005bd49e35b56fb1c4fcecf1fe9e2fd9f7743dfdde582ca83f8267b049ed59f3f9686eb2eb69fd782699d62fa83de67244f67de1fa3eb79643b237ef2698f4ab9e8347d4feb0008fc99e1c6a8fbd70d05f4762261e6f9f46e3e730a6f3160618fdbbe6f9c6dc5d0ea8bd0df5bf6b88f89aebdfa25f00a5f8ff4e32edd749c0636aff66019ee17939780e7de2f92a73f643e8bb031e537b7c133c237bf5dec073ba7f44f71f1a225ea3e78f447f79dfe011ddcfe3f22dc68c29feff06e379ba0e9ed2ef0b1effb1747d44f3b1fd0d5ed2f78d0236c65cdf367c7e5fb0009858bf07cfd15f7bf082f4a3492473f96f19d8257b6df8f82e66cb05f55fbe223630ff6f797cbc98b3f67079527afe82319d17e1f6b7581a03fafdc693ccc7d7a0fb194b97da7bbb111b63f2af1bae9f0bd31810eb7cbd61612d1dd2bf94da671943c8cfe7af856d8c69be6e0563be594f2573fdd7b8bd2c1c763ded0f53c01392c7bb4ae6fdd3717d5db8c684fa4bbb134cdfa767f094e28180fba3e5c080fff15fc0b309f5ef9764d24ff13dfc43cae7a3e5d0807d6cf8782d47acbdfcfeed0b783ea1fad50ebca0ef6f3ed82079a32f30f2c5644f6c627f5acce7df655fd0e1e3930460a662dc3f1cc14beaaf40133ca178a8069bf47cdf90ccc74ff3c10e8de78dfa676c8afd09b564daff5982b1ff36a6f64c58ffd2fed40b7836a1fac504bc9c50fd6101c6fc97abc426e2b3700fc67911f3018cf33ac99d643e1edd198cf3176e07463da4fa0063bf8826be47bd53e7fe773965e345fded806db217eb1d8c78f29612b3f6923ea9603cbffb0663bf4949d7b38083c663b3022f68fceb7bb043f6118682a9ff8c02ec92bfcbe9fe7dba49f5c30d18fb636c9e2f2ce7ecf7144f2a6097e4bbe5c4ecf7140fcdc0b0b734018bfde03a784af3c53504cfb15f47dc0ffb0bbb8160ecbffc002fa95e58edc1d8bf1a4dc1163d2f36c1a8575495609c5f1a4aa6f3a134febd83e2ed2f376093faafa3e72fcc01f1f50b3c217ddd5660ac37ad63f00cfe5407cf69beef5230ea35c6026c60bfe55532e55b39d8a4eb6f73b0d8df20ee6f637fe04a32cdff03b043fd757b904cf9d9168c7ae7ed2299ce7b3692697fc898d8c2f99ddb128cf3b317f49f85fa6348f75f1a06d9afb604c37ff817b04dfa1c0ec10ee9bb4ef22ecd015d7fdd8047345eb5079e907e598e60d2d7b40363ffe3762598e2d5750e16e703d692297ffc128cf35768af897afb36108cfaee5c32d56b0f9269fe2e24f3f61731d880ff5125d37ed80558ac974c25d37ec09d64de7fe64132f73fea27d8a2f15732c9b4bea008a6f655a23fb1dfb28c24d3fe470d8c7ae1ed5132e95b2099cf8fa6e82fd4e3c32fc9b4bee3125b38ef7723ff641816d99b7d03dbe48fc22bd8a1f9f95a825d9adf4af28706eb1ede5eaf060fe97aaf133ca1fc5605237e6d8ee009e5bbd5198cf58b4efc1efb6fb71730f67bae66e025c5df8a09c6fecae020998fafd248a6f3ba909fd93b6f4f71025bb0af52329ddf9849e6cf533fc036e993924ba6f96726993f2fcec00ef57ff12e99e2c34a32ed7f0cc1f017d5bb649aef7cc9bc3f22f13c97fa4fc3f859388f5a6d25f3f69443c1d86f3b06e37c4be54ae6edd354c9bc7dd791643aefdc4aa67acf2b18e7f134f13dced325a4ef26d3379affc95f9826e2cb952a99c6f7153ca57835fd0463ff691108267fb579028b78c2001b34dee14532bf7f62814df88b4832d52f2ab085f59d0118eba3752899ecff2699eae307c9b43fb405431f9454329d2fbb97ccdb938cc02ee95b2adaebd2f8b590dfc279e448974ce3f12998e67b4d30ce074713c934ff403e0be72d2dba9fc5e607defeca014fc85f5413f094e2313b954cfea0114cfe467901233fd41792a99e6a49a6f527c198afd71bc9549fb9822dea8fba944cf9502799dad782b17e6d7592c9be6782495fea42321fdff8c7f77cbcb477b043fa1deb9229ff4904e37cc697641a7f0decd27828aa647abfc45032ade7b982495f13b25fcbc2fb0c944032d9cb93606abf5949a6f314cf60bc4fc00a24f3f6b79960d29f5a914cf11dfa53d87bf82999d6df303e16ceb755a4cf369b3fc8ff7660ec3f57de04933ff0f760ac5f665bc114df2b67c1a44f57156c507f5f1cc9b49ff3198cfdc69bab60aac7ac4f601be7fd22c1781fc95132ddaf95ccdbabd492b93e794f60e883229e87fd9ef959326fbf3a964cf9e00318fe5d9d4be6fda192fed916ce83a92f92a93dbe64ca8fd11f229e4c6f92f9f3938164ca1f4f82b1fe994ae6df871f60acf79a6bc9bc7f62f49785f3b1b75230d95775914cf30bfacfc279b22df90f47c413a621995f9f7be011f99b6d2b98e2136d021e4f683d63059e4cb83e7ae2fa19dd4f3b83e734df943118f581f22099cba33c8397549f592dc026d69b8f9279fb6f9f60e453d6156c537f5a63c9644f8160aa47695330fc8dee0aa6e7c5b964f28791648a6fe692297e580aa6fa6628da8bf8c3bc974cf9642118fa38954cf58f89649acf1692e93c27e9936361ffbdd149e6df2b9e609c1f7d96ccf549ad24d37974e887d0676721998f8fba924cf1fa9364b247713df687754f92e9fc672399ea45a23d581f35af92f9f7b9f81eef13885ac1181f4b32d5dfa10f16f6a3044bc974dee9158cfd3f892399fcbb2d993fff26fa6786fd3ba23f711ef146d7bbcc7e283f7b944ce7995ec0639aafab25784af671db0a9e52feb200cfc9fe320f2cec650f5e52beae7d49a6f319e2fe4bd457766083f453bf80b11fa878154cf6b2adc1585f37c4fde0af235530f251717fc45b9e90d7c17ca848a67add5530ad1f74a160aa8f76e279c8e7c34630f97f3b174cf2379a645abf247fe35a781f90ff2818f3c1443297571d49a67cfd4930cd87da1b58e8ff5630ece74b32cd1fa564aaef7982697dc87900633f73694ae6faa0be4aa6fd789a648aa724637fd25a32ed3f79924ceb3d3330f6f325ad64de1fd98364927f2598facbd42553fd3594ccafcf311e16cecbdf22c95c5e652a99de7f267e8ff3c9e1bb641a6f4732bf3efb008bfd812f92297eb32473794b31bec87fa30a2ccea3ee0493ff30f79269bfe23718f53e6b2099ea0377609c7f34be2453fc7b914cf91c1f2f63c0e6478a0fb6e019d58be207c1538aff8fe039e503712b99e6eb5c3297a750c04b1afff22618fb1922c1347fc73bc9b47f4630f2f9eda360b2efab2e98fcd1ea158cfd83452598f47f6b48e6cf8feec1a8b7a40bc1f05f3bc9bcffb61918f596f85d32d5e3447fbaa48ff94030f6ffdc24737bf267c416f6a7ac3f2453fe580bc67e99122cceeb9c24533d6b2299f4ad108cfce30773fd500cc9341f7792a9fe6b0ba6f5da6b2399ce372ec1d85f1d9d25f3efd59b60f89f3bc9bc3fd4a364f22f6f60ec97543792797fa8a960d8c74d32f9932918f61ed49269ff832d99e44d24f3f61a23c1d43f96fc1ef1fe1c8cf763448e645a3f16cfc3fe3d632899e2955832cdafd01f0be7d5ad4a32ad9f3792b9fcc64030ced3d792b93e9723c9b41feb02c6fe67732b99f68b7c4aa6faa3180f9c17f6499ffa032dc46bf098ec5d8f24d3fe08133ca1f530ed049e527eb7f50593ffb83c48a6fd570a7846f7d36b30e6fff25b32d5ab73c1145f28ef82299ed1e660d473b64f92a9def92698fcdd5a3cdf46fd24914cfbcd74c9b43e21e445fda611f7437cb1fe124cf65edcc02edd3f7225d3fb001e05d3fc5ebd108b7879bb93ccfbc72f05937e66a9605abf538e92493f56e021d99f6249a6fa5b2b98fc9f72914cedc3785a789f52f12698e42943c964ef17c9e40fe792697e1849e6f2648e60ac470660bc9f243b4aa6fd3fa1647ebda20826f9d24c32ef0ff55e32d5d7447f60fe2f9f25933f5a4aa6f8e62c18f9502299f40ffa61e1fd4b4a2899f6279492495ed19e29e297028cf795048d64dedef05530e29137c9e47fe4efb19f740cc6fed152134ced0f55c9fcfb488c17dedf74b9934cf64dfa3c326754ffab0cc1643f510ac67a78ba04633e77466083ecb309c026cdffa921998f476582b19fa7ba0aa6f93e5e0ba6fca6188051bf4b447bb1bf4aff00237ff60f92e9fd41f792e9bc7d2599e6f717c1d8cf247e8f7a5d160ba6fecf23c1b4bf21a0f86264e13c72f82499f2fd57c1f06727c954bfcec098df755b30d9ef7a2e98ecdf78974cf54cb447d47b3d5330e2cb0fc9b4df47dc0ffbd12ea9647a9fec1e2cde07f92e99f60b1c05239e3e4ba6f9df974cf9d74932ed9ffff17b2e5f70014f90ef288261df8664b25f5332bd6f0ae36be1fc4ef92499f22d4b3297ff593c0fefb7525dc9e4df3c30de5fa53a9229feb12553bc2ec66f46f2565f92c97f37609ca7b4df25d37e825a32adff89fba39eaafb92293fea24f3ebb7627cb13ebbfd944cfbadbf2453fe37154cf287a664d257d15e715e80fa6fcce673f2af117846f94076075e503d2188c1d88f9a9b82c91f2457c954cf38832df2279ba9647adfe70a8cfaecf62098e2ef6e2699ce27ed04533cb2d5c0d8efaa3c09a6fa46f12199ec5d3c0feba51dcd9f63517f5dbd0886bd3f4be6d76f7f30d59b62c164afde5432d5271f2453bdf0118c7abd77124ced4f1ac914afae2553bd5edc0ff3b9f32498ec6d1308a6f66c55c9341fdd83313f9ba23da8ef46a564dabf664aa6fd21f27b8a37e291643abfe249a6f9ce174cfb13a2028cf78b25b9647a1fd4a364f20fdf92c97f0c25d37a512599d607a03f22bf375ac994ffa49229ff78944cf94a2618fe48fe1ef59e4632bd3f270163becf2ac994bf8beb71fe4b0d2453bd27964cf552d13fa807da2f9269ff612499e627d13ed40f424330b55f13d763bfc6ed4332c50f37c1f0f78a64daff3d964cfb43c578637daf7a138cfd39429f719e6745fa30314df21f590ec6fedd32134cfbb10c5d30f98bcb8b64da2f3c01dbd87fbc174cf5c92001e37c4d25eee760fff1b7605a0fc8b792e9fc81b8bf43f149740263ff68fe2a989eb722fb9c8878201849a6f75f9e25d3f37cc974fee15532bdaf752898ecdd0904d37a475080512faf7e30e9432e18e7f56e82914f5482c91e0bf13ce4ff7e2698c627fb924cebcfe2f7d8ff1b3e80719eaa934cfaee3992491e717ff80f6f2699f22ff13dcecfa753c9e4bf0e92293f394aa6fd139660b2cfc8904cf9df4230e68b4a32add78bf1c0feda740ec6fbebcab3648a8f2792c95fd492299e1848a67ccd164cf25b2d58f887ab64f2379d64de3fea4132bd6f4efc7e8e7827944cfb7f1e2553bd4f134cfa6c799269ffb6781eea8df14432b57f2718ebe591645a2f8b05231f15bfc77e0f652699e229f13cbc7faa9a4ba6f361427fb03f5013fa8cf5e2c495ccbfd73dc924af180fec0fd33e24737922f17c9c6f6cc91ea626f69b5e13f082fccfe64d32d547bfc0385f565ec0886fba1cecd07a663394ccfb237b05633d627b144cf6779949a6f75fd682c93f06349f4dc5fa433d154cfaace982697d721b4aa6f3c11918f585742299fce5bd64aa67958229be5ac560ac27ac12c178bfd79b64da0ff1e37b7adf95fc3dcef70592a95ebb174cfe3a10cf47fda01c08c67e1b5f32bdcfb0134cfde12a92493ed17f585fc81f25d378dc49a6f8e253308d57249e3f417c7a904cfbcd7e30c5d382b11e982592797f24df82b1de954ba67aa6907f4af69baec138af6c8d24533d692f99ea733bc1e4ff3c0b2cde37be944cfe712499f2b1b964b2675732c52bd0670bfb45d54832e58fba64da0fb301e3fd8d4a2499d67f857ee0fc72f82d99be5f81713e3e194aa6fd49e27e588fb89ec1884fae13c9d4ffa27fb0bff0229e67537fae481f66e612fb2d776083ec3debc026eaa53618f18a3b0323fe580f04d3f5c98b64da7f63825df217852e99ce77907c33113f6c4dc134df6f1f24d37c9982b17ee04f05537b9a77c9148f88dfa3deb07a92ccfb7f73168cf365778229be48bf25537d19f28bf501e55e309d4fc94f92c97fcec158cf2b12c1e41fbb4fc9b43e3d104cf3f7f6158cf87ebb170cffe448267942c1e47fba05187f1f455049a6f5614330f4ff0d8cf3b88ab83fde3770d94ba6fafc12bca0f6a557c1c80f6dc9644fb964aa7f1492a99eb1934cf3f718bc44fd632199f2ab67c914bfd782518f16e389f78346a23dd8bfa58c25d379ba4030c95b5f2453fc5c824deacfec2299f6eb6c25d37ae74630e2b94632ad170bf9b13fb87b964cf514713df61bd4aa643a9f0f7bb31cecbf10fa28f6e3f89269bd83e2adb9399f52bd33032fa6fcfef9086cd07e8d95f81eeb1b97a964b2bf0dd8a2f958ff02dbb41fbbbb825d9a3f2eb5608a3fe28560caafb6141fcd45fdb11a83b11e180f05d37e5c5f114cfe669d8051ef6f3dc1a40fab108cf3d9e55a30ce3b1a60d4d78bab649a5fe5f7645f978364dededbb360f247452899ea616730d6e7d6779269fdfb4330f9bbd4148cf8e7058cf5b8e44932bdffe25e30f6835c24d3f9d1b964b27f713decdd16ed9b937fd2f792a99ed2082679a247b0380f33964cf3ab18af05c52f57d1df381f5baf2453bcff0046fcacfe60daaf6a4aa6f9ed4b30ce7fce25d3fedd1918f65f8af1c37e6d4f954cf9cf5a32c927fa17fbede254326faf22aec77c1bd582a9fd971d58ecef3524d3fe5ea1bfd84f97d1f82ec47a83be124cf5c8ca07dba4efbab8de9df2dffba44f0b51afeb5cc1747d32154cf61eecc043ccf74f9269ffd3098cf7495c1e25d3f9c14a30ad9f281f92693de2001ee3bcf34430e94f71114cfe259883f17ef6b52599de2f36144cfaa23c4aa6fafd188cfa79d60a267de832c1345f1786649a3f7e30cd97a2bdc86ff55232c54fb9648a6fc4f367e44fbaa1649a5f669269ffb7f87e4ef6915f2453beaf4ba6f9443c1feb655a2c99e47b944cfb532493ff6bef25d3f86ec1e2bc532318ef1712fa82f3cdc55e32b5ff1b0c7b5acd25d3df9fa04ba67ac25e32bd0f44b4cfc0f95ad13f787f759c08c6fb8f34c9141f6e2453fee80b46fe22fac7c2fbafef24933f17fa8efdf9590dc6fb8d4a8a079626ceb3d513b04bfa5a527cb664f645eb65067848f359fd8369bfec063cc27ae45532d5e31f25f3f15c3d83315f5d12c9544f740553fe1e6592a93f3dc158bfb88127145ffb9560b2873a96cce5d75f25537ce90ba6fa62f283a95e2ae49bd2f82b0f82c93ef385645a6f17ed453d39984aa6fac04c32fdfd5607c1a42ff14a30cdb7b74232e5a76f60e483fe5132cdafb960c4fb5f60c4aba52218ebaf7b30d69fb6e27b9c1f0ceec1d82f962b92c95f1d24537da7148cf93d05239f534f92e9bc8bb81fcefb1547c9544fabc1383fe68af1c67e92f807d3fe43213ff6af7662fcb19fdaa37cc410f5ebfc5e30cd1f6b07ecd2f85a347e06332faa8f1cc0d0efdb17784cf6e91a82499ff44ff004eb318f92693fdebd649adf5f05937efa67c1347fe91618e7bb6e7782112faa60ac8736a1601a8fac00a31ee98c05e37d155bc1347ff8aa647a1fc009bca0f1f21682c99f9ae2fe0b6acf360763fd62bb168cfcb0148cf7790879f1be9ad4954cf53cd17fd8bfe49f24533db1164cfdbd7d07e33cd346fc1efb17b789643a4f300423bf303760bcdfec42f669b27885ee47f38169e1bc977e124cf18d3706e3fd11fa163c22fdda9482a7b43e7805c3ff058660ba9f2b9e87f364d14632d5fb4792e9ef37fe02e33cd926028bf364efe039c9b712f747fdd9ad24d3f58231fee58b601affdb9b648a6f7230dedfe12d25d3fa760a467d3817f218145fd99f9269bda1924cefdf93df23fe7e009b349f98623c2c1a5ffb2298ee97d8609c3f32e6609c3f8b2cc9646f0bc914cf3f4aa6786607c6fe958ee20f8bcdafb43f81e20b165e93fe5c3e25d37e30f13dea3ddd093cc27ede2d18f5983804e3fd1d9a25989ed7168269ff61fa089e223f9e8167d47f862699d6f39e25d3fe68152ce2b70318e7c9ec67c9f4fe12138cf70f252918f1932b9e67507caf08f9f1be9938069b648fc15e308dbf32108cf7df88e7637f627c150cff7a0163ff42f000c6f99c88ec9b4d37a867d1fc62b3f1a3fd3c743f5b8c5ff102463cb2da82117fb4df9269bde12498ec510f25d3fef01c8cfcb8b425d37eaa5232bd5f2506235ed85a9269bda4154ce31d89f6cef17ea8037841f76b447b17d84fb6012fa9fd6b211fea49978b608a4f0c156c423f3bc9b49f3602c31e6f7bc1d0bf4a32c50fe44f6c1bfb3be22918e7e93a6287d907adb70ec063eacfd51b784afdb3d1c0c85f8a5232cd97afe0398ddf652d99fcf75130e5879d09c67c961482c97eae7bb049f74b3ac9349e86649a4f2692b93cb978be8df749b88229be509660eca7f39f24d3f922713dd6afaedf8269bcd690c7c6f9d755001e52bc5b533ceb5a63d29fab2199ce63edc1139a9f82083c25f9f5068cf745dd2cf082c6238d25d3fb1d378229bf503ec178dfce6a0946fce9bd0aa6fe5ccf04e37d0af27aca77ac1bd8a2e747df9229fe2dc136f98ffc4530e9f34a934cf9f10fa6e78be739387f48fde9daa8f7273c3e310716f67b260f9269ffde1318efefb91e24537dcd06e3fd79f6156ca0deb1904cf6a080d1fe6a2199e67b5f303d4fbb801dd8fb0a8cf8f9a611db78ff5dac8211df745f60d413133e7e2c389be17ccc183c27ff97459279ffac4b30de27947e0826f94c4532bd7f5d032fa9fd6d2a784af58e21d8a0f9e1aa48a6f53f5b30d58ffd6730de5f94ad89ed01d9ef86eb2b0b2eb0feac6fc04b92af7525d3fbcd8f6093fccde64532d9cb4d30c57fe1128cfd33eb77c1a41feb028cf7355a1f82e9f9ee1cec62ffea9b60f227892918e785f0bd8dfdafe9b3601a4f270463fda81980f17ea59adbbf3966fd41ef472dc14bf2075b1b6c60fdec4132d50f0bc9944f1cc126c693dad7bf5e9ff2075c6f63bd6b45fd33b1e6549fb8adc0580fdc7682c97fdd5ab035a5f74309c67961e50bec523c1de2f736eaf7f11c3ca4feaba8bfa6d664467fffdc080c7f53b492a97e6f08a6f686776013fab4964cf69982717ed224ff30b5b15fe81608c6fbd3ce60c48b2d8dd78cdd8ff2eb5430c5839b5730f2dff60086be74a4af33d6ff148fdd8391ff6a74bf390b30693de808b6917f64c436ce8fdf1230f6bfb7345e0b6b3a27fbe9c0f319bdaf8bfcc782a5f7e43f0f60cc4705f99f256b3fedf71c826d92ef3625b627349f68dcdf9b2c9f9b927f237d3458ffd1fc5b8327f007241f530f1aaf6b00467cad51ffb2f8674efd41fed5b6711ed0e4fac192cf39b57733022fe97ecf977feda32abf7d8c9f3e26ffa7827fa74fcf0afb9da5daaac3fe58ff6a0be8f3df21cb2f72b9ecf3bb5c0afe9b90afff77f7df5116d62247f5d81ffad87ddba87590cbfd87bfb3f1f9779605f20899fa6be8cfffb79fff167b71e9f393743ffadc1663f4e33aa96fbdedf8eccf3f18b9ffff65e16dfbc90e6009240d1fa9bf2b3bc9439ee0df4d1661eb3f64f9313a3f24f3e4e717cbfa7791e5379fecfefa91d758f05a8e94457a8a7f2b590235542335561335553335570b364ea55aa917b5669f86e951ab76ec7365b2de544dd5d99f15ff9dc67eb1fe379365a3aed827652391a85bf6b963d2ecd43dfbec982c07464726dd91fdfb4e3da9f74cce7bf5817d0c76e53ff4daff5b6479e43dfdc446e699fddb597d515fb94ebda9efea87faa906ec9a9449f8c524f85607ea501da96375a24ed5993a67d2fc1bc9c25a7a5637ec9f91ba607f02d8fd92c9f2cafe6929aafaa4284cc7be15431d2ba662b18fad380af31a8aa7f8ff3d92fcf7cc954aa0844aa4c44aa2a44aa6e44ccf5ca63d2dfbf4b6622a059b45064aa954ca45a9954669954eb92a3745537465a5acffe8b5ff37cab251b6ca9db253cf8aa2ec95036bf1513929f7ca83f2a83c29cfca5979515e9537e55df9503e952ff6dfbf9581325446ca589928d37f23596c65a6cc9505d3b393b26413cd83f261288661b044c0b00dc7700dcff08dc0088dc8888dc4488dccc88dc2288dcab8a87746fddf23c99f9345b58ca6ff23a390df6677a3353ae36adc0ccdd08d95b13636c6d6b83376c69efd391847e364dcb3cf897d8e8c1fd8e7d178329e8db3f162bc1a6fc6bbf1c1b4b0cf02685e757f9b797f9aadfe999ff8af6561527c1a5feccfb7313086c6c818315fd4186363624c8d194b9916c6b2370ad3304dd3326de3683aa68b8fc73ebe19b04f684666c465eaa53a9a31936a6726666a66666e16666956e6c5accdc66ccdcebc9a375333757365aecdcd2f33b1f2eb1cfcd764311a736bde3169becd9db9e7b21ccc8379344fe6bdf9603e9a4fe6b379365fcc57f3cd7c373fcc4ff38b7dbecd8139c467648ed967c23e63736aceccb9b960992a3334e3de322cd3b22cdb722cd7f22cdf0aacd08aacd84aacd4caacdc2aac12b9dc2f91c5ffaa2c56655dac4b3f2e566d0cad5a3d5b8dd55a9d75b56e9666e9d6ca5a5b1bd6d3f7ccd96ead3bd6522689b5b3f61677bfd6019f2393666a9dac7beb817d1ead27ebd93ab351da592fd6abf566bd5b1fd6274b98bf5884b0601e3d61b3e9c0faeea3cf1fd1d1bfa2634cb31a8bfe82f001fff74a89aca135b2c6d6c49a5a336b6e2d7acdb196cc74fa4a9ed18f85b5b34d48f2439643bf4c614e6ddb766cd77ab03ddbb7033bb42326cfc178b4633bb1533bb37336abda766197ea935dd997de92fecce74fc932307756cdc6646055aa67d77663b776675fed9badd9ba71303fec95bdb6376c249ef9676fed9924625414662bcc5e602bf766681bf6d6beb377f6de3eb0cfde3eda27fb9e8debde7eb01fed27fbd93eb319d7b35fec57656fbfb1d1b07efffcafc9f27cb16a66274cb78cc6f8b4df992c1ff6a7fd657fdb037bc8faf3c47a7864bbe6dc1edb137b6a2db924bb7e64fab161ff9cb1f6ce85fd9b91bdb0972c485698ddcc1dc3311d662c8eed384cd2bde33a9ee33b8113b218f5dd89545df17fcdecfe155954d7183ab113333bb1edda499cd4c9ec2f27778a5e0ea7640ff0ed3d6b55653d3b17361acf4cc706cc6216dc6ed8ff3b356b6be3b44ca6b9d3998173756e8e66f7938feeac9cb5b3b1b7ecf78eb365d21c8d9d73e7ec9cbd73708ecec9b9771e7ec9107ecb7a7eb6a03f234b2f09f3c683fe7ecea3f3e43c3b67e7c58cadb33db1ced6b33d6656cc3ece2b6b5dc5dab5c0a7f765ec63cfe9e374f8bc39efbd24f6d20e9d0f66fd47e7d3f9ea7fe954ce379366dffff5f6cec8e9ff5acca933b3eb9fe4f8576599ab9db3b05f9c25fba1e21a6ce2b08c23f34accd3bab6ebb8aebd702a73e17ab66b1f5d9ff9a8931b981fccdacfaca5f76ec86c2ae673e6d18ddcd84d98bde0e3746e6a7acc73b02b594fbcd93336be67377373b7704bb7722f6e6d176e63356e6b358ae67656f3afe8987a70afaaeddeecd6d55cdd5db96b3626cfeec6dd9a43f7ceddb97b3622dbde86dd837bb4576e6031ed331df7e4debb0feea3fbe43e1b817b765fdc472195fbeabeb9efec5707f7c3f9703fdd2ff7db1db84377c474f1c0fe7dec4edca93b73e7eec25daabd2f6091b8a7aa81f70fbdda9f91c533d8f46d79b6e778ae937b9e71b2179eef055ec8e4601fdbf0222ff612e6030eb6d1eb8c977a99977b85577a9577f16aaff15aaff3aedecdd33cdd5b796b36e7c7c6c9db785befce7a70471ebb0b1b5d9dfd3ff36fde8e59cec1db7b07efe89dbc7bb5f01e58467af31ebcc71f5af5bb1ff853b23c79cfded97bf15ebd37a730637362dbde3bd32f2e099368cbe6f229b39ae7de76bd0fefd3fbf2bebd8137f446ded89b78536fe6cdbd85b76441d7a3aff8866ffa966ffb8e71f45ddfb31e7c9ff9e8ad6df8811ff663e547ee8069e4831ffb899ffa198bd6be51bffd523c9189fe75599e2f7eee177ee957fec5af0d6603e6d46f7a49fc967d3aff6acead27ffd6cf77bee6ebfeca5ffb1b7febdff93b7fef1ffca37f62f966a2bef8f78aea3ff88ffe93ffec9ffd17ffd57ff3df8da3bdf03ffc4fff8bf9f485bd30c74ee57ffb037fe88f8cbd3ff627fed49ff9737fa1382c27fd6679f70f597ea97dfc99d8d25f066aa004060b112da65f6aff97c0f7fae5b7811d38fe3570cd0fe31478811f04411844411c24411a64411e14411954c14509839a7d1afb3d68838ee59af3e06a3f07b7400bf460c5fc48ef0d9560ddeb99ad30d906f6c27a0e36ecaedbe02ed8057ba50e0e4a191c8353708f0af54f35ab3f2d8b1d3c048fc153f01c9ccdd829ad73f012bcf6da15bc05ef81137c049fccf38c83afe03b1804c360148c8309fb330d66c19c49b47016c1325443253442d3188416fbc39c71e8865ee88741188651c86cc73a8709f3cb4698f29881c50f61d6fbfc300f8bb00cabf012d661e32fd421d3b41fb2387f94e5e77af6cf63d25f1db661175ec39b19875aa8b3bbafac636f27e13a5c33795e8c7b2f0d37e136bc0b77e13e3c84c7f014de870fe163f8143e8767f52d7c095fc337ab08dfc38ff033fc62d2982c22f6d97ff90e07e1301c8563e3184ed82cb40dd6e1943d63e6ee99fd7c9b53f6dfe7e1225c466aa428456444e68fcacdaf5a46b288ff862c28f1d4c8525fd5d7c88e1c7f19b9e673e41987c8376751c02375a61351c8e2f7cf88e524511c25511a65c62dca8347ab65f369cafe3c4645f01895aa1255d125aaa3266a5910d345d7e8166966678ccc75d4467ab48ad6d126da4677c629da452c82f0039ef778d68ecdb40bf6bc7d74888ed129ba77d2e8217aecefcdabed866af4d1eeaff6f2932c068fb043b77516ca2e7a8a9ea373f4629ea357f6a437e6750d16a79ca37773c2e6e8aad7eae823fa8cbea2ef68a0ce153f1a2aea8f2a2c7f9e1b8da27134096d2b8ca6d1cc8aa379b48896b11a2b56111b319bac629b85166eccbc3d9f73962c7f93b29843e33ef6e3200ea3fb388ae3f081f554cadbcafc5a1f25fe2c0b7ff68f7131d4f8f9e22cd4f73889d3388bf3b888cbb86277bcb0f87111d7d6dedd5b4f7c2639b959dcc46ddcc557ff515dfdb4f662cb3bbaca5875e25bacc5ba3f8b57f13adec4dbf82edec57ba5890fea323ec6a7f83e7e881fe327e3143fb3d866c9bc7380b86e689de373fc120ce2d76011bfc5effd88736b3055a38fdeff992c0ad34726cb6b9cb8adba8a3f4c23fe649db7641ec6b09ee3da665ed3fc0c33f6dcaff83b1ec4c378a456eaca7e51ee94713c862c3c1eecff9dfdb7493c55f57816cfe345bc4c54e3c17e4894c448ccc44aecc449dcc44bfc24484263efde9b3e8bd8d887e7a42c3b65f3d6d17c4da2244e92244db2a84872b1baf07765b1c886f8155b36bbb2fe65914fa2b8499194e69b71f26fd69e8dc9d9b998dff6c13ad953e39054c925a99326d6d5f7beea9db4ccca52f40d55c1d97d95bb385332bb4abae49adc12cd38252ceb4956c93ad924dbe42eba26bb641f1c9243724cd82c657a8ec272014364d94c9b4b2f4dee9387e4d158264f09b34ab146d2e722ff4c96e499f5e198e50e2bf6efe7e4257935dfad3e3f3f276f56ff8afa7df26ece8dfb709c7c249fc957f29d0c143d192a8b64c4abe3bdc7b4fbac8dc9c264b2df93713231de9269324be6c63159d8c76469c6a99a2aa9919aa995daa9e3dfa76eeaa5bef1603a2cbfe1b2f45910cb54b769601cd3308dd2384dd294cbc2d76dfa7cc4180aaff6932c96b0fe3453466e6b97cc8e9d344f8bb4343fd22a4c98f75a5afdfe8687f4621becee75daa46ddaa5d7d451f4a04b6fa996ea91c9fd3f978549c3644957e93adda4dbf42eddb15fedd3837d4c8fe9c9d8a7f7e943fa18fae953642b7a344c9fd373fa92bed2c8902c2ca7db4681719fbea5efe947fa997ef5feb197a56f7f9f5bfd2acb8f91e17a78607dbb5412ff2139a7dfce9069eb673ae86da5f7c54ee5fa696f8f553a4ac7e9249da6b3a4b50b65c3f42c5177fe23ef31361a7d0edad724d379ba4897999a29c63163f971b4efb3e3ccecb3e2649559999d39ea57ea0475e6665ee66781e9fa81a3a06ab0eb675096e1c5599845599c25ce23b3172e8b6af5b9d52fb2889a9add3f99d14efd4a064ccf952c553eb2cc74cc2fe61b077dcecbf477613d982316e37efaeb2ccf8aaccc2a61e7e45b98b34db24b56678d5dc763f53b6bb32ebb662c62f343d6df6ff6c2fd66de9c652c99661ce3a74ccf56d93adb306bd3b36d76e74efa5999c53363a603cf76bfcd8445369691edb23d93e5901db313d31d85db7491ddff268bf28b2ca67a973d3032b24743c99eb82ccc37f67d642ebcc4661146f69c9db397ec357bcbde852c1855577d4bdaec23fb8c9e93419a655fd97736c8d8e866a36cec8eb24936355da7cc66d9bcf7ebacbf17d93257734509552b377233b7723b77d8377d3d6dc29edad74398b7cedddccbfdec900779a828ff4496be024a7dabf8bd36f63edacc23c330ee982cbd97efed70c024619219f7799c27fe364ff32c3c30597eac4172bf95e779919779a556f925aff3266f8d63dee5d75eb7dc613f1ef9cdfdeeb3e25cb316f153aee7ab7cad9eed32dfe4dbfcced67396c1b09879c8729a4bafd9feb7719f29f93e3fe4c73cc88e4c9b1392c52afe812cbd0f559209d332db2ed5263f1926f32baed05ca6678eed5877ecbfbce6f7fe5dfe104cf347a3fd690d92d97afe943f671725cecff94bfe9abfe5ef7dd4937ff49909af719cddaf6cca73628dcd8871fe997fe5dff9404d9cc833f3613ecac7f1579ff1997dbc4c51b4cfee30c9a7f92cc9988e1d59a699f47e377c0bdffe200bb75792259fab7b854d65ea77be302c63cfd2fb1dd5bc481673943bf9b2500b8545f5b3e8814547b6dcabc0575793b1fa919f0ba33059b7d9ac557d2cbfe8f312ebaeb7e4c2e96b1785cb62ad53e1157e111461112923d52ee22229d22233f67d5ec365611ac6aecdd98cb92f8aa22caa3c2459faf6b398f5f597f9e5375958c6b34f6fcc62be8b0b7be4ce74a97227646177ae8ba6688b2e288a6b714bf25f7684303d53fc645268855eac8ab5710835e7b597a5d8b06cabaf6a7ef4d5be60cd243916dbe2aed815fbe2a0d8c5317b284ec57df1503c16cc3efa1a279764614e0b268f7128cec54bf15abcfd248b1aaa7f57164fcab22bde59fe742a3ed894b5efa33c5420875c9645f1597c15dfc5a01806398ba2c90bffd8555114a3625c4c8a692f4931ebd72c989ea8c5bc8f467b5b6677e03163b12cd5ecbb544287c540d7d250c2d22cadd22e9dd2354ebc22c2ae36c7f6a1f498cdc6a55f06e1ae64290dd731f7efc822d654c40cf3adb665c472b053191b2ecbef852cfb5e16f3cb3a9749999659996749745f163f564a7824669465599597b22e9b5e92b2cd34661b565f3b6371dc985bb4e10ffaaca7ecca6b79b3be4acd0a4a9d8d67c4a45995eb72536e992cbabdeda326368e5bebbecf64cabb7257ee7b591425cd7a6d28b552fb45961fd13ef773ea8dcd103b75c56439183e1bebc81c5b7d3d2cb0674e63cfcb63792aefcb87f2b17c6271ce86e527a398c594ca9d5d97cfe5b97c295fcbb7f29d79d12eef32adafee9bb3bebec13f337b5f7eb079bc2c3fcbaff2bb1c985d392c47e5b89c94d3c42d67e5bc5c944be3c45c7d156a7c4c0f665f1939564a655466d06746a7e0d4ef2ab2aadf62fedefb3059d8b7663f36c8d834bbaa2c26cbbd19b2deec97483a264b6b06c6a1b22bc79bc5e3a00996e5c4dc562e538ba86236cfa29545e5574115329fca66bb3ea736471513d73a313946fd9a4515dbdb2aa9d22aabf2aaa84a2bacaaea52d55553b5556745c1b1ba062b362e5ba7a355b57ebda3aff254b74aabf42467b21c83a36ad9f51f641151a5cf6571d488e9e246bd315559556b264bc4662c268b193269f64c96a37bf65a6fae2e2387e9eb5bb5a9b6d55db5abf6cab53a54c7ea54f575e17be6c717d681c5d5233e26a3fe2ebd3d570f4c96c7eaa97af646d5b97ab1aaead5dc556fd57bf5517d565ff128afabef5e966ac07ac2e5f5cf678bddb11a56a36a9ce4ce23c6c5339adf627e4bca6270efba60d1ee539ac5a36a524db98e912c51af654c965335f33a8f656bca3c34ccae9a87af9eaac6ac3f17d5f2a26637f7f5a25cb81fb615f38349d17f26b406c3ee7570377d7e75312fd6c5be38e1cbc5bd78179f8d897f092e6134b8449798c9d2b91fccbadefb159cde673059946a7449823e47f6d9c7fea32ca859283c96225912f549bdbfa4ec9e593fc6fdfa9c6df523d4af3d18a774e75d2fb9925e8ad0c8eefbbcfd525eaacbe5525f9a4b6b9c2edde56a6fcdd1e5165f98bd93244764f10773e445d68e5da55df4cbeab2b6c2cbe6b2bddc5d7655a7b697fde57039ba2fbd2c173633b1d8cde27fd5d684fde2fef27079e4b2a0adfd5add3f95256071f24d29c2faf27479eeb585d61bfb95a07ecd81e9d8a377f35545b3df7975c88ca6feec72bebc5c5ecdc4383afae58daf136d8235f75dbd7e8db92c7c3d96f5f196c5dd5333bebc5f3e2e9f97afcb777cbcf06a13d388e16578195dc66c869ddb6c66cd466c967db5ee7adbbf4c2ed3cb8ccba2f0bcc2fe5d16392f4096545373b5cdcf97f9657159f6eb90bdcdb2d8b85fd50accc08d8c83a7298f8a1fb4c620fca8d55a49b6b5519bc98ac9f9565bb56def1cc38bfaf99ecd8d336ef74cc7b8341e9b3146e6dcdef82cdeac9ddaadbddaaf83484b46e9ad0eeba88eeb84e5ca9fcce3796c6e9db008f4c9dd32590e755a67756e325990875bfd0aeaeff9cbcfb2287eaa2b6e5dd4a5f25557c68149c0bc0fb3dfa8978569716cc69eee2b6aa9cc422bfc8caa9a4590ec7f0d6b5beb7eb84347cdaf2c63af9cd73063f67faa22f6ebdeaff7d27875c746f9cbfc626d5cd6d7fa566bb55eafea35d36ca77c8af27a536fcdd8b9b23cdceb6318d6239bdec2eabb7a57efcb3d932515759e3fe695d8112074cc55efd44370523ae5b13e184cb3597e7f6fdbbc12cfda581f599b4ff57dfde0b6a1556d13b77eac9fea67e3549feb17a65b8bfab59f159cd728b02d1691ceeb37bfb1b7fe376fd994b220ca89acbdfb5abfd71ff56738a8bfeaef7a500fcba01e992ed3c27db17077aec3f29881cb324b63578feb493de5b2507ef20f7264f6a797a59ffb13f6ff0d9bfda7f5ac9ed70b9657b2fc8bc52e5b7b592f9d779bcdc98dda288de13f5a35f3ca1f8dd958c64363db47f6d9374e3f438693be6ed3fbb2c665ff34a2d0def09a9af1cb1ae0d074eb5be3357e1334a1f3d4444ddc244dcafae291e54977eeaec9ac2dfb158b429bbc299ab2a9b8bdd09ca8f42b763fcb42732397c5e4b364aa8cd2cc99c77a7369eaa6316377c3eefb7039352d0b8359d6e186c6aee99a6b738badd0b48a466b74fbc18c9b557aecc7c57c6fd64cdfc76c2c3efa5cbdd9345bebdcdcb9dbbe8283d53fac9c9b5ebfb3a4d935fbe6604d9a63736aee9b0726c363f3d43c3799bbeb6be64c2fe3e6dcbc34afcd9b948559c51f65a1d95ecaa2f1959bb5faa138cd7bf3c1a22ac7ff609e89d973bfa6686f9b4fe3e0dbcd57f31dbae167ad3483669828d60b7bde283359ae356bc66c961b46611f41369366dacc9ab971b296eeb6e1e3815566167d9b9ed3354b63efad5bb5555aa3355bcbe8ebd581396efb15c45d9f2b87ba71689dd66dbdd6ef63181e7719aadfaf09ff365752a5cfa4bdc2fdfa937a8bc76de03dc65d1b1afd4afdb658703fbbed6dba8d8c531bb7499b865ef5de666d9e9fdaa22ddbcad8370fed85595585fce4beaddba66ddbaebd5e5876dfded8bcf3ddd70ee4eabfefed5aaddf1de36baddeaeda755bb35f6dda6df0dede056f5ec89eb7734a63d7eedb437b6c4f52163f38fd41161bd97abff39945bc2c951e313ddbe5b9a2b7f7ed43fbc8b4f7686ffa9d05b5d53ef55adf326fdf9edb97f6d518861f4e5c44ed5bb269df59ccb3cf3b7bc9645ef5f949fbd17eb65fed773b6887eda865b3068b697aed123b19f67e906bf6b69db0acf8d44edb593b6751cb397875ed76d12e3bb59fdbeace38754a67746667c571922779af472c8e39fe4116e75759d84c99a94967770e93e6dab99d67192cd770d89cb063be56ef7c7b5b9f7d3753baa00bbba8da767197746997b1b0b8e8ca7e6dc8db75157bfea5abbbc6df756dd775d7eed6699ddeadfaac8e723b5ec95b326b687b8fe2fb4e65dce75db7ee36eeaedb76776c02d875fbe6397aef0eddb13b75f7dd43f7f8ab2cbfc563b40fdae2fbb87b59fabdb76ef7d43de7cfddb97be95e6bc738b9ae7de8ff1a4166c38eb3eddeba77964f9cba8feeb3fbeabe9d85fad80dba6137eac6d19d195b0b33f6b56ed24dbb5937ef16ddf2aa5e95ab71358d9dfbca7763f9bd1ce6d00ffa99fd6ad97bc7bada4cbe73b7bb322bb18ee1cadd5d5deffdea3109dfaffe35b886d7280fa302b66ffe1d1d13eb0b72bf196a7fdcef5de36b52b2d65d53ebe19ab119ef6c9dddcf7eb5f79ab3ff5a5ccb6bc5e29897ebe55a5bfeb5b9b6d7ee7acd27d7db55bb321d5533b30bdf8da69c5c57d7f57573dde6937efe655aa8f4750ce623a2be06c6f7638dd87c74ecfd38d515ade7de5aaa8171bcde5d77d7bdda3847b5edab2c5993353c07367f9c07f8afd7f89ca7ebe17a8cd953afa7eb3d3dddbf998b6641d1eef5e1fa787dba3e5fcfd797eb6bad3069deaeefd78febe7f5ebfa5d75d74139bc0eafa3eb987d26d7e975769d7b6bd60b8bcccccceb92dba2f5f327f2dd7d73c7fc1c27ebcc52c3a79b7263f955348c4c36f3b9aa6dbf33591cbebef31764b99937eb66df1cd6379ecb66877edf8bad140b2fb9b139d0b8f7b51b0b346f8152ddc25b748bd5db2db9a5b7ec96df8a68d025e15755df4a7377ab6e97f0e3c626c65b5bac6e9db1ef772df0fd16d6af1f5eafb0d86cc4246aee7236b3dcaeb7db4d4bc6d133cb2d44fefb87ddf3ffb52ce6e34dbfad6eeb86c5860d1b0d663515f3f8ecff6d8fc5eecc9fe593dbe6b6bdddd5a5fa62d7cae2b65386b7fdeda0d4ea58dd869f55773bde4eb7fbcbddede1f6787bba3ddfce51623f180fb71733643e7af24759983454afa4fbbfdede54f7f67efb48068a226ac97f7d7d3f2a6e9fb7afdbf76dc02b565f2cb35c58776c86d89a73162f6d6e43e3741bddc6b7c96d9abab759eadce6f1241ef77bf56f8bc80a5fa2eab6d4d4a8d394a85202f5ac199aa9599aadb1b16e5acdedade4e78fbbef3396c82759cc58f3345f0bb4508bf242d1d20ce3f287d3597fc25e1eb5584bb454cbfad981dd7b617e7a119385f624cdedad96f733a75668a5566917ad564325615ed0b65fe289b25322add15aadd3aedacdf84c1dd573979aa6e9da4a5b6b1b7ba16da99af0ebc8b03f2a8bb1ad7667dada9db653bba0b9bddbef2c7f37642dcfa1f8f22fc8926a7beda01db59376af3db008efce9cfa1f2c3bda71eb6451633568f92e43ed517bd29eb5b3f6a2bd46b935d2de8c85f61e4fb40f36269f2c216e94246ccb2767e999da97f6ad0d3436a6edeeefd98bf5dcee2c35f2b59136d6260a8bd955d72eed525db37111f562ef2fcbf2a84db59936d716da52578d7da8318f79673d9993b86e6fecb363518056b6bac26c79a71bbad92a9dab5bc64db77527327457c97596ee844e7fe622ce597e32d4033dd4233dd6133dedc7e0d74f98f5b2447e2f95bbd7333dcfebb2d20bf6fbfe0c84266b797c86ff2b3a16157aa957fa45aff5466ff5aedf11c3f2de39af8f322fa45ffb798faffbfaa6ebbe66817ed3355dd757fa5adfe85bfdcef850bff59dbe77c6d5413fe847fda4dfeb0ffaa3fe14bd07aece722db28dde4ef86e0bf3e78fe9e8e764adbfe8af4aa36ead86cdca34abf4f6eff075c43fbd8724c9f537fdbdb8ea1ffaa7fea57febcc07588affed6ed3ca56a3f7beea637afd3a561f4733895c33d687fa888dd0589fe8537da6cff585be5ca92b6565accc95e5eddd3c7ded778d38d5ca5e39b6e1eec90f0b0ff68b2cae7170dc95bbf2567e3c535f56415f310a4e2cdeea57df6a5a7bfb73b2048fab701505b355bc4a56e92a5be5ed78555807166b6efa5ccb3afe248bc9b28265dee5ceed8565b4b755b9aa5697555dcd568d3e5ab5b1c7f7c23129dcddaaeb76abab395e752c5bbbe3f66e626ef955166f7533e3b261f9d270a5b1f178e335567efeafaf2afd9571311f57fa6ab55a6bb362b8daacb6abbbcbc76ad75767d2a05f79b0b67d0ecf64796372ec983c0adfc570cff7819ef85efea3d8cf6bdcbbdf76e87ff8cd6abf3aac8ead1d7cb84edffa7e9e679aa6b63b6627bfcae2af4eab7b9643ad570fcdfbea919fce727f3a052c4f69fca971795a3dafceab176db67ad5ebd5dbea7df5b1fa8cbff26ef595f615a31fbbddf98749f7c177bc0f9d57f6795b7daf06d17e35147b2cfbbdbccd9339ee7739b58bd5a8d72e173b9358e3acdf65b1ceab717d67ec5693d574355bcd51c3b7708ec1fdf3314c92af16abe55a5d2b6b439badcdb5b5b6d7ceda5d7bc69e45cffb1f7bdefb489eaffb32097a7b62b3105fc7e3fba9f9beea7e36627e6318266b7f1dacc3c0091cbecb91452c6431bd17f85d967e7473dbd8d7efeb681dab77e20c2d9745f9b16be9cff8b1f03198af933c5d5bc1789daeb375be2ed6e5ba5a5fd6b5b5b437741601fbf7fbd5b30fbe57a25f6bb15a16fc34ceba757416dfb3e83e9ce9d77ecd807df6ebceefd6cc62bc50c8d2eeb411f366bfd90bd7d26374b7beadb53573e4b2fee5fed5793f628aba5eaf37ebedfa6ebd5befd787f5717d5adfaf1fd68feba790c5fdd69eef5ac1b8b04fbf9ef76a7e704b3e584bbedb9f5950bffba8f7e2eb7e97fcb1df83b63eaf5f02d736cc28effa73246c8e1feb193f73d2e7373edfafe4b23c69bb7e5dbf35cafa7dfdc1e617c8f2f7f78ffdf38ff3283f69566595d196e7f5e7fa6bfdbd1eac87eb11d335bea38c568b980e4db0afbf8fa6c6acc7cffc04c6b2dfadd7cbe90f7a7bea77569a43967505d5e0a69a713e6171d79d36d1f3f5b8f776eb49bfa3b61f93cb389f38de7aba9eade7eabdd8d32b674be7afc8f2d3d9621ecf798f6a11dcaf179774bdcc5f36ea46d9181bb37faead164ebf368979c230a77db5439cb4602336661ab91436d3dc39174ba1fd0a2caa7f6511e4ae0f0b36569fdfacc6cce59f8ce3c6c9271b77e36dfc4d607da94f56f35b9bfed2b8fc7eb6381a2a9ee2da55972acd26dc449b78936cd24db6c9f9aa0bcb6eac27ffdbf68bbc782effdf76ae6549552488eee733a6f615fdba7dbb6356a899203e5a6db51f3b4444f0c14b1489987f9faa8202a4bd37b457331113b9d010943a661645669d93b6d9e37b5a6207f5ce54d90a7b630ee6244ed8f3c2d4532060d9afca32ad349d280e4d481246103617866ae8b10a11c3b2b36c86248e1fe983506f36cb7bd7f59cde1344592e9dd23decc91c0e90c0115250a0014d680104cf80a691efb672cf7c667bc1191af13a6a6c8d2ed758790ea8a041fba8a546ca6681e23a2c67047dffd9b4a1130fb9e20cbad0833e2ce34747a706bc2c0e85fef98baee722bfd4f4404187dc51957668dcd0c2d0b660004318c12b8c61025378f3347fb13bb0acbe5df0f8ab3a31cdd0e01d3ee0136ee036e9c2ddb1ed7437f7e95459c13d7169c0bcfd003fe0117eb2bb700a4ff08c94def1b9eef5e9337dca1557dfc052a8ea8aefae6ed98c216b9d45ef4ffa485524aebeeaa181333471eeaa5edbb7f71bff47100423b4421517519a70a1686ff7106f632dbe471b97ebe0a027b3a4874ec8a276354cdffc603e255ca59dee228325a4b8c2354b719e8d29bbca113774bcb961e319c8f15c1f6327bd3884873ca043ee1bdcda7dce35430f7d0c30740718e10e636fed2f7ddd7f0cc2861b3aa1161991827b3cecfa8d66ec6112bce2115354f693204dfadb96d559cd6ccab25f967cd21655d61b3627a2d61336b0892d72a03dc20e53978ef868d8fa229f5faee75a7fe935c07eb745b764495b08ec7d0311d5f514356ca3bffd70224ff774bfb3f1822818bb5ad8461d3bd8c5de6e837df6fe0507fb088787258ef015c7d68bd0fa1a7cc79ef3f0a8623d21573237714a0f6bb636061dba66de58d0211dac1b76bfe293ef6039b59ad281736c393795d713791dceebf80ee712f96e3009ddd07544a5d179e13613e6e4967d435ca3ec245389a0cad56bfd58aad7bf7aee9fc122ff178e852391e3f3ba9c7d9399eff23a234758aeb7d96b869def6fe7fb0f468e60216ac18678a2378b9890bd31a42ee18c42f1d27b720dcf09969c1fb0909d7904e76ec356026184f0baafc89d66956e3da5fecbf6f6f369d67545d6b5c5d924df135a7ec542cb5e26e49a7cbfc201afa191da8093df3604f79408ae9ab0ad576031ca9ca3caca1635c83935653d22cfb448ce3bcf6adc752c32e62a9eb9443352aa19aaaae7e288acea1ab4729f2cb50e052bf5d4871cf952b02732ae47d9db635ea9689b453c57bc2062244752ad5f5e84c592fcc3e1ee4c949995795bc152fdbea830d827ffc84c20e9088f91d2fb395e4b665b520152c3422492ebf27dce3a1163b10a2cc60916c11710bad85b6ed69a7320cdc09db863ceee0a1533e20c1ccef6c814cd995eb334f9fc9ecf4b7ebda988b5c23b67b1108e04dff03d63da7fc32f8b9a5f4caeec2fc7c9c7e6f5bc5e154b795ce2e596e3679fca7dd222b624e3bc58134fe3a1e0bbcd199677fcb81c4b3d6a7ea7d6aeb0fb7e71ceb9a3f5738b7cbeda2ba7de3ba79caf57e52fff15fb1fcbbfd3fefcfbaf3ffe0193cc32fd</data> + </image> +</images> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klistbox.h</includehint> +</includehints> +</UI> diff --git a/kioslave/media/mimetypes/Makefile.am b/kioslave/media/mimetypes/Makefile.am new file mode 100644 index 000000000..25c718035 --- /dev/null +++ b/kioslave/media/mimetypes/Makefile.am @@ -0,0 +1,22 @@ +mimetypedir = $(kde_mimedir)/media + +mimetype_DATA = floppy_mounted.desktop floppy_unmounted.desktop \ + floppy5_unmounted.desktop floppy5_mounted.desktop \ + zip_mounted.desktop zip_unmounted.desktop \ + hdd_mounted.desktop hdd_unmounted.desktop \ + removable_mounted.desktop removable_unmounted.desktop \ + cdrom_mounted.desktop cdrom_unmounted.desktop \ + dvd_mounted.desktop dvd_unmounted.desktop \ + cdwriter_mounted.desktop cdwriter_unmounted.desktop \ + smb_mounted.desktop smb_unmounted.desktop \ + nfs_mounted.desktop nfs_unmounted.desktop \ + audiocd.desktop \ + dvdvideo.desktop \ + blankcd.desktop \ + blankdvd.desktop \ + svcd.desktop \ + vcd.desktop\ + gphoto2camera.desktop camera_mounted.desktop camera_unmounted.desktop + + +EXTRA_DIST = $(mimetype_DATA) diff --git a/kioslave/media/mimetypes/audiocd.desktop b/kioslave/media/mimetypes/audiocd.desktop new file mode 100644 index 000000000..8b0e47e42 --- /dev/null +++ b/kioslave/media/mimetypes/audiocd.desktop @@ -0,0 +1,66 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/audiocd +Comment=Audio CD +Comment[af]=Musiek CD +Comment[ar]=قرص مدمج صوتي +Comment[be]=Гукавы CD +Comment[bg]=Ðудио диÑк +Comment[bn]=অডিও সিডি +Comment[br]=CD klevet +Comment[ca]=CD d'à udio +Comment[cs]=Zvukové CD +Comment[csb]=Aùdio CD +Comment[da]=Lyd-cd +Comment[el]=CD ήχου +Comment[eo]=Muzikaj lumdiskoj +Comment[fa]=دیسک Ùشردۀ صوتی +Comment[fi]=CD-äänilevy +Comment[fr]=CD audio +Comment[fy]=Audio-Kompaktskiif +Comment[ga]=Dlúthdhiosca Fuaime +Comment[gl]=CD de audio +Comment[he]=תקליטורי שמע +Comment[hi]=ऑडियो सीडी +Comment[hr]=Glazbeni CD +Comment[hu]=Hang-CD +Comment[is]=Hljóðdiskur +Comment[it]=CD audio +Comment[ja]=オーディオ CD +Comment[kk]=Ðудио CD +Comment[km]=ស៊ីឌី​អូឌីយ៉ូ +Comment[mk]=Ðудио ЦД +Comment[ms]=CD Audio +Comment[nb]=Lyd-CD +Comment[nds]=Audio-CD +Comment[ne]=अडियो सीडी +Comment[nl]=Audio-cd +Comment[nn]=Lyd-CD +Comment[pa]=ਆਡੀਓ CD +Comment[pl]=PÅ‚yta CD Audio +Comment[pt]=CD de áudio +Comment[pt_BR]=CD de Ãudio +Comment[ro]=CD Audio +Comment[ru]=Ðудио CD +Comment[rw]=CD y'Inyumvo +Comment[se]=Jietna-CD +Comment[sl]=Glasbeni CD +Comment[sr]=Ðудио CD +Comment[sv]=Ljud-cd +Comment[te]=ఆడియో సిడి +Comment[tg]=Ðудио CD +Comment[th]=ซีดีเพลง +Comment[tr]=Müzik CD'si +Comment[tt]=Tawış CD +Comment[uk]=Ðудіо КД +Comment[uz]=Audio kompakt-disk +Comment[uz@cyrillic]=Ðудио компакт-диÑк +Comment[vi]=CD Nhạc +Comment[wa]=Plake lazer CD odio +Comment[zh_CN]=音频 CD +Comment[zh_TW]=音樂 CD +Icon=cdaudio_unmount + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory + diff --git a/kioslave/media/mimetypes/blankcd.desktop b/kioslave/media/mimetypes/blankcd.desktop new file mode 100644 index 000000000..723ade236 --- /dev/null +++ b/kioslave/media/mimetypes/blankcd.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/blankcd +Comment=Blank CD +Comment[af]=Leë CD +Comment[ar]=قرص مدمج Ùارغ +Comment[be]=ПуÑÑ‚Ñ‹ CD +Comment[bg]=Празен диÑк +Comment[bn]=ফাà¦à¦•à¦¾ সিডি +Comment[br]=Goullonderiñ ur CD +Comment[bs]=Prazan CD +Comment[ca]=CD en blanc +Comment[cs]=Prázdné CD +Comment[csb]=Czëstô CD +Comment[da]=Blank cd +Comment[de]=Leere CD +Comment[el]=Κενό CD +Comment[eo]=Malplena lumdisko +Comment[es]=CD vacÃo +Comment[et]=Tühi CD +Comment[eu]=CD hutsik +Comment[fa]=دیسک Ùشردۀ خام +Comment[fi]=Tyhjä CD +Comment[fr]=CD vierge +Comment[fy]=Blanke Kompaktskiif +Comment[ga]=Dlúthdhiosca Folamh +Comment[gl]=CD valeiro +Comment[he]=תקליטור ריק +Comment[hi]=खाली सीडी +Comment[hr]=Prazan CD +Comment[hu]=Ãœres CD +Comment[is]=Tómur CD +Comment[it]=CD vergine +Comment[ja]=空㮠CD +Comment[ka]=სუფთრCD +Comment[kk]=Таза CD +Comment[km]=ស៊ីឌី​ទទ០+Comment[lt]=TuÅ¡Äias CD +Comment[lv]=TukÅ¡s CD +Comment[mk]=Празно ЦД +Comment[ms]=CD Kosong +Comment[nb]=Tom CD +Comment[nds]=Leddige CD +Comment[ne]=खाली सीडी +Comment[nl]=Lege cd +Comment[nn]=Tom CD +Comment[pa]=ਖਾਲੀ CD +Comment[pl]=Pusta pÅ‚yta CD +Comment[pt]=CD vazio +Comment[pt_BR]=CD virgem +Comment[ro]=CD gol +Comment[ru]=ЧиÑтый CD +Comment[rw]=CD Itanditseho +Comment[se]=Guorus CD +Comment[sk]=ÄŒisté CD +Comment[sl]=Prazen CD +Comment[sr]=Празан CD +Comment[sr@Latn]=Prazan CD +Comment[sv]=Tom cd +Comment[ta]=காலி கà¯à®±à¯à®¨à¯à®¤à®•à®Ÿà¯ +Comment[te]=ఖాళి సిడి +Comment[th]=ซีดีเปล่า +Comment[tr]=BoÅŸ CD +Comment[tt]=BuÅŸ CD +Comment[uk]=ЧиÑтий КД +Comment[uz]=BoÊ»sh kompakt-disk +Comment[uz@cyrillic]=Бўш компакт-диÑк +Comment[vi]=CD trống +Comment[wa]=Plake lazer CD sins rén dzo +Comment[zh_CN]=空 CD +Comment[zh_TW]=空白 CD +Icon=cdwriter_unmount + diff --git a/kioslave/media/mimetypes/blankdvd.desktop b/kioslave/media/mimetypes/blankdvd.desktop new file mode 100644 index 000000000..f5f06c9bc --- /dev/null +++ b/kioslave/media/mimetypes/blankdvd.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/blankdvd +Comment=Blank DVD +Comment[af]=Leë DVD +Comment[ar]=قرص رقمي مرئي Ùارغ +Comment[be]=ПуÑÑ‚Ñ‹ DVD +Comment[bg]=Празен DVD диÑк +Comment[bn]=ফাà¦à¦•à¦¾ ডিà¦à¦¿à¦¡à¦¿ +Comment[br]=Goullonderiñ un DVD +Comment[bs]=Prazan DVD +Comment[ca]=DVD en blanc +Comment[cs]=Prázdné DVD +Comment[csb]=Czëstô DVD +Comment[da]=Blank dvd +Comment[de]=Leere DVD +Comment[el]=Κενό DVD +Comment[eo]=Malplena DVD +Comment[es]=DVD vacÃo +Comment[et]=Tühi DVD +Comment[eu]=DVD hutsik +Comment[fa]=DVD خام +Comment[fi]=Tyhjä DVD-levy +Comment[fr]=DVD vierge +Comment[fy]=Blanke Dûbelskiif +Comment[ga]=DVD Folamh +Comment[gl]=DVD valeiro +Comment[he]=תקליטור DVD ריק +Comment[hi]=खाली डीवीडी +Comment[hr]=Prazan DVD +Comment[hu]=Ãœres DVD +Comment[is]=Tómur DVD +Comment[it]=DVD vergine +Comment[ja]=空㮠DVD +Comment[ka]=სუფთრDVD +Comment[kk]=Таза DVD +Comment[km]=ឌីវីឌី​ទទ០+Comment[lt]=TuÅ¡Äias DVD +Comment[lv]=TukÅ¡s DVD +Comment[mk]=Празно DVD +Comment[ms]=DVD Kosong +Comment[nb]=Tom DVD +Comment[nds]=Leddige DVD +Comment[ne]=खाली डी à¤à¥€ डी +Comment[nl]=Lege dvd +Comment[nn]=Tom DVD +Comment[pa]=ਖਾਲੀ DVD +Comment[pl]=Pusta pÅ‚yta DVD +Comment[pt]=DVD vazio +Comment[pt_BR]=DVD virgem +Comment[ro]=DVD gol +Comment[ru]=ЧиÑтый DVD +Comment[rw]=DVD Itanditseho +Comment[se]=Guorus DVD +Comment[sk]=ÄŒisté DVD +Comment[sl]=Prazen DVD +Comment[sr]=Празан DVD +Comment[sr@Latn]=Prazan DVD +Comment[sv]=Tom dvd +Comment[ta]=காலி டிவிடி +Comment[te]=ఖాళి డివిడి +Comment[th]=ดีวีดีเปล่า +Comment[tr]=BoÅŸ DVD +Comment[tt]=BuÅŸ DVD +Comment[uk]=ЧиÑтий DVD +Comment[uz]=BoÊ»sh DVD +Comment[uz@cyrillic]=Бўш DVD +Comment[vi]=DVD trống +Comment[wa]=Plake lazer DVD sins rén dzo +Comment[zh_CN]=空 DVD +Comment[zh_TW]=空白 DVD +Icon=cdwriter_unmount + diff --git a/kioslave/media/mimetypes/camera_mounted.desktop b/kioslave/media/mimetypes/camera_mounted.desktop new file mode 100644 index 000000000..e08488829 --- /dev/null +++ b/kioslave/media/mimetypes/camera_mounted.desktop @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/camera_mounted +Comment=Mounted Camera +Comment[af]=Gekoppelde Kamera +Comment[ar]=كاميرا مركبة +Comment[be]=ÐŸÑ€Ñ‹Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ ÐºÐ°Ð¼ÐµÑ€Ð° +Comment[bg]=Монтиран фотоапарат +Comment[bn]=মাউনà§à¦Ÿ করা কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾ +Comment[br]=Kamera marc'het +Comment[bs]=PrikljuÄena kamera +Comment[ca]=Cà mera muntada +Comment[cs]=PÅ™ipojená kamera +Comment[csb]=Zamòntowónô òdjimkòwô kamera +Comment[da]=Monteret kamera +Comment[de]=Eingebundene Kamera +Comment[el]=Î ÏοσαÏτημÎνη κάμεÏα +Comment[eo]=Surmetita Kamero +Comment[es]=Cámara montada +Comment[et]=Ãœhendatud kaamera +Comment[eu]=Kamara muntatuta +Comment[fa]=دوربین سوارشده +Comment[fi]=Liitetty kamera +Comment[fr]=Appareil photo monté +Comment[fy]=Oankeppele kamera +Comment[ga]=Ceamara Feistithe +Comment[gl]=Cámara Montada +Comment[he]=מצלמה מחוברת +Comment[hr]=Pristupljena kamera +Comment[hu]=Csatlakoztatott fényképezÅ‘gép +Comment[is]=Tengd myndavél +Comment[it]=Macchina fotografica montata +Comment[ja]=マウントã•ã‚ŒãŸãƒ¡ãƒ‡ã‚£ã‚¢ +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული კáƒáƒ›áƒ”რრ+Comment[kk]=Тіркеген фотокамера +Comment[km]=ម៉ាស៊ីនážážâ€‹ážŠáŸ‚ល​បានម៉ោន +Comment[lt]=Sumontuotas fotoaparatas +Comment[mk]=Монтирана камера +Comment[nb]=Montert kamera +Comment[nds]=Inhangt Kamera +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ कà¥à¤¯à¤¾à¤®à¥‡à¤°à¤¾ +Comment[nl]=Aangekoppelde camera +Comment[nn]=Montert kamera +Comment[pa]=ਮਾਊਂਟ ਹੋਇਆ ਕੈਮਰਾ +Comment[pl]=Zamontowany aparat fotograficzny +Comment[pt]=Câmara Montada +Comment[pt_BR]=Câmera montada +Comment[ro]=Aparat foto montat +Comment[ru]=ÐŸÐ¾Ð´ÐºÐ»ÑŽÑ‡Ñ‘Ð½Ð½Ð°Ñ ÐºÐ°Ð¼ÐµÑ€Ð° +Comment[se]=ÄŒatnon govvenapperáhtta +Comment[sk]=Pripojený digitálny fotoaparát +Comment[sl]=Priklopljen fotoaparat +Comment[sr]=Монтирана камера +Comment[sr@Latn]=Montirana kamera +Comment[sv]=Monterad kamera +Comment[th]=à¸à¸¥à¹‰à¸à¸‡à¸”ิจิตà¸à¸¥à¸—ี่เมานท์à¹à¸¥à¹‰à¸§ +Comment[tr]=BaÄŸlı Kamera +Comment[uk]=Приєднана камера +Comment[uz]=Ulangan fotoaparat +Comment[uz@cyrillic]=Уланган фотоапарат +Comment[vi]=Máy chụp ảnh số đã kết nối +Comment[wa]=Montêye camera +Comment[zh_CN]=挂载的相机 +Comment[zh_TW]=掛載的照相機 +Icon=camera_mount + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory + diff --git a/kioslave/media/mimetypes/camera_unmounted.desktop b/kioslave/media/mimetypes/camera_unmounted.desktop new file mode 100644 index 000000000..4a4d2ee6f --- /dev/null +++ b/kioslave/media/mimetypes/camera_unmounted.desktop @@ -0,0 +1,69 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/camera_unmounted +Comment=Unmounted Camera +Comment[af]=Ontkoppelde Kamera +Comment[ar]=كاميرا غير مركبة +Comment[be]=ÐÐ´Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ ÐºÐ°Ð¼ÐµÑ€Ð° +Comment[bg]=Демонтиран фотоапарат +Comment[bn]=আনমাউনà§à¦Ÿ করা কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾ +Comment[br]=DVD-ROM divarc'het +Comment[bs]=OtkopÄana kamera +Comment[ca]=Cà mera desmuntada +Comment[cs]=Odpojená kamera +Comment[csb]=Ã’dmòntowónô òdjimkòwô kamera +Comment[da]=Afmonteret kamera +Comment[de]=Nicht eingebundene Kamera +Comment[el]=Μη Ï€ÏοσαÏτημÎνη κάμεÏα +Comment[eo]=Demetita Kamero +Comment[es]=Cámara desmontada +Comment[et]=Lahutatud kaamera +Comment[eu]=Kamara desmuntatua +Comment[fa]=دوربین پیاده‌شده +Comment[fi]=Irrotettu kamera +Comment[fr]=Appareil photo non monté +Comment[fy]=Ofkeppele kamera +Comment[ga]=Ceamara Neamhfheistithe +Comment[gl]=Cámara non Montada +Comment[he]=מצלמה ×ž× ×•×ª×§×ª +Comment[hr]=Nepristupljena kamera +Comment[hu]=Leválasztott fényképezÅ‘gép +Comment[is]=Aftengd myndavél +Comment[it]=Macchina fotografica non montata +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„メディア +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული კáƒáƒ›áƒ”რრ+Comment[kk]=Тіркеуден шығарылған фотокамера +Comment[km]=ម៉ាស៊ីន​ážážâ€‹ážŠáŸ‚លបាន​អាន់​ម៉ោន +Comment[lt]=IÅ¡montuotas fotoaparatas +Comment[mk]=Одмонтирана камера +Comment[nb]=Avmontert kamera +Comment[nds]=Afhangt Kamera +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ कà¥à¤¯à¤¾à¤®à¥‡à¤°à¤¾ +Comment[nl]=Afgekoppelde camera +Comment[nn]=Avmontert kamera +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤਾ ਕੈਮਰਾ +Comment[pl]=Odmontowany aparat fotograficzny +Comment[pt]=Câmara Desmontada +Comment[pt_BR]=Câmera desmontada +Comment[ro]=Aparat foto nemontat +Comment[ru]=ÐÐµÐ¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ñ‘Ð½Ð½Ð°Ñ ÐºÐ°Ð¼ÐµÑ€Ð° +Comment[se]=Gálgajuvvon govvenapperáhtta +Comment[sk]=Odpojený digitálny fotoaparát +Comment[sl]=Odklopljen fotoaparat +Comment[sr]=Демонтирана камера +Comment[sr@Latn]=Demontirana kamera +Comment[sv]=Avmonterad kamera +Comment[th]=à¸à¸¥à¹‰à¸à¸‡à¸”ิจิตà¸à¸¥à¸—ี่ไม่ได้เมานท์ +Comment[tr]=Ayrılmış Kamera +Comment[uk]=Роз'єднана камера +Comment[uz]=Ulanmagan fotoaparat +Comment[uz@cyrillic]=Уланмаган фотоапарат +Comment[vi]=Máy chụp ảnh số đã gỡ ra +Comment[wa]=Dismontêye camera +Comment[zh_CN]=未挂载的相机 +Comment[zh_TW]=未掛載的照相機 +Icon=camera_unmount + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory + diff --git a/kioslave/media/mimetypes/cdrom_mounted.desktop b/kioslave/media/mimetypes/cdrom_mounted.desktop new file mode 100644 index 000000000..18119ee32 --- /dev/null +++ b/kioslave/media/mimetypes/cdrom_mounted.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Icon=cdrom_mount +Type=MimeType +MimeType=media/cdrom_mounted +Comment=Mounted CD-ROM +Comment[af]=Gekoppelde CD-ROM +Comment[ar]=قرص مدمج مركب +Comment[az]=BaÄŸlanmış CD-ROM +Comment[be]=Прымацаваны CD-ROM +Comment[bg]=Монтиран CD-ROM +Comment[bn]=মাউনà§à¦Ÿ করা সিডি-রম +Comment[br]=CD-ROM marc'het +Comment[bs]=Montiran CD-ROM +Comment[ca]=CD-ROM muntat +Comment[cs]=PÅ™ipojená CDROM +Comment[csb]=Zamòntowóny nëk CD +Comment[cy]=CD-ROM wedi ei osod +Comment[da]=Monteret cd-rom +Comment[de]=Eingebundene CD-ROM +Comment[el]=Î ÏοσαÏτημÎνο CD-ROM +Comment[eo]=Surmetita nurlegebla LD +Comment[es]=CD-ROM montado +Comment[et]=Ãœhendatud CD-ROM +Comment[eu]=CD-ROM muntatua +Comment[fa]=CD-ROM سوارشده +Comment[fi]=Liitetty CD-ROM +Comment[fr]=CD-ROM monté +Comment[fy]=Oankeppele Kompaktskiif +Comment[ga]=CD-ROM feistithe +Comment[gl]=CD-ROM Montado +Comment[he]=תקליטור מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ सीडी-रोम +Comment[hr]=Pristupljeni CD-ROM +Comment[hu]=Csatlakoztatott CD-ROM +Comment[is]=Tengt geisladrif +Comment[it]=CD-ROM montato +Comment[ja]=マウントã•ã‚ŒãŸ CD-ROM +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული CD-ROM +Comment[kk]=Тіркеген CD-ROM +Comment[km]=CD-ROM បាន​រៀបចំ +Comment[lo]=ຈà»àºžàº²àºš +Comment[lt]=Sumontuotas CD-ROM +Comment[lv]=PiemontÄ“ts CD-ROM +Comment[mk]=Монтиран ЦДРОМ +Comment[mn]=ЗалгагдÑан КД-ROM +Comment[ms]=CD-ROM Terlekap +Comment[mt]=CDROM immontat +Comment[nb]=Montert CD-plate +Comment[nds]=Inhangt CD-ROM +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ सीडी-रोम +Comment[nl]=Aangekoppelde cd-romschijf +Comment[nn]=Montert CD-ROM +Comment[nso]=CD-ROM yeo e Nameleditswego +Comment[pa]=ਮਾਊਟ ਕੀਤੀ CD-ROM +Comment[pl]=Zamontowany CD-ROM +Comment[pt]=CD-ROM montado +Comment[pt_BR]=CD-ROM Montado +Comment[ro]=CD-ROM montat +Comment[ru]=Смонтированный CD +Comment[rw]=CD-ROM Yashyizwemo +Comment[se]=ÄŒatnon CD-ROM +Comment[sk]=Pripojený CD-ROM +Comment[sl]=Priklopljen CD-ROM +Comment[sr]=Монитран CD-ROM +Comment[sr@Latn]=Monitran CD-ROM +Comment[sv]=Monterad cd-rom +Comment[ta]=à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ சிடிராம௠+Comment[tg]=Монтаж шудаи CD-ROM +Comment[th]=ซีดีรà¸à¸¡à¸—ี่เม้านท์à¹à¸¥à¹‰à¸§ +Comment[tr]=BaÄŸlı CD-ROM +Comment[tt]=TotaÅŸqan CD-ROM +Comment[uk]=Змонтований КД +Comment[uz]=Ulangan kompakt-disk +Comment[uz@cyrillic]=Уланган компакт-диÑк +Comment[ven]=CD-ROM yo gonyiswaho +Comment[vi]=á»” CD ROM đã kết nối +Comment[wa]=Plake lazer CD montêye +Comment[xh]=CD-ROM Enyusiweyo +Comment[zh_CN]=挂载的 CD-ROM +Comment[zh_TW]=已掛載的 CD-ROM +Comment[zu]=I-CD-ROM eyenyusiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/cdrom_unmounted.desktop b/kioslave/media/mimetypes/cdrom_unmounted.desktop new file mode 100644 index 000000000..6107bd32f --- /dev/null +++ b/kioslave/media/mimetypes/cdrom_unmounted.desktop @@ -0,0 +1,86 @@ +[Desktop Entry] +Icon=cdrom_unmount +Type=MimeType +MimeType=media/cdrom_unmounted +Comment=Unmounted CD-ROM +Comment[af]=Ontkoppel CD-ROM +Comment[ar]=قرص مدمج غير Ù…Øمّل +Comment[az]=Ayrılmış CD-ROM +Comment[be]=Ðдмацаваны CD-ROM +Comment[bg]=Демонтиран CD-ROM +Comment[bn]=আনমাউনà§à¦Ÿ করা সিডি-রম +Comment[br]=CD-ROM divountet +Comment[bs]=Demontiran CD-ROM +Comment[ca]=CD-ROM desmuntat +Comment[cs]=Odpojená CDROM +Comment[csb]=Ã’dmòntowóny nëk CD +Comment[cy]=CD-ROM wedi'i ddadosod +Comment[da]=Afmonteret cd-rom +Comment[de]=Nicht eingebundene CD-ROM +Comment[el]=ΑποπÏοσαÏτημÎνο CD-ROM +Comment[eo]=Demetita nurlegebla LD +Comment[es]=CD-ROM desmontado +Comment[et]=Lahutatud CD-ROM +Comment[eu]=CD-ROM desmuntatua +Comment[fa]=CD-ROM پیاده‌شده +Comment[fi]=Irrotettu CD-ROM +Comment[fr]=CD-ROM non monté +Comment[fy]=Ofkeppele Kompaktskiif +Comment[ga]=CD-ROM neamhfheistithe +Comment[gl]=CD-ROM Non Montado +Comment[he]=תקליטור ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ सीडी-रोम +Comment[hr]=Nepristupljeni CD-ROM +Comment[hsb]=Njemontowany CDnik +Comment[hu]=Leválasztott CD-ROM +Comment[is]=Aftengt geisladrif +Comment[it]=CD-ROM smontato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ CD-ROM +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული CD-ROM +Comment[kk]=Тіркеуден шығарған CD-ROM +Comment[km]=CD-ROM មិន​បាន​រៀបចំ +Comment[lo]=ຫັງàºàº²àº¥àºµ +Comment[lt]=IÅ¡montuotas CD-ROM +Comment[lv]=NomontÄ“ts CD-ROM +Comment[mk]=Одмонтиран ЦДРОМ +Comment[mn]=Салгаатай КД-ROM +Comment[ms]=CD-ROM Nyahlekap +Comment[mt]=CDROM mhux immontat +Comment[nb]=Avmontert CD-plate +Comment[nds]=Afhangt CD-ROM +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ सीडी-रोम +Comment[nl]=Afgekoppelde cd-romschijf +Comment[nn]=Avmontert CD-ROM +Comment[nso]=CD-ROM yeo e Theositswego +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ CD-ROM +Comment[pl]=Odmontowany CD-ROM +Comment[pt]=CD-ROM desmontado +Comment[pt_BR]=CD-ROM Desmontado +Comment[ro]=CD-ROM nemontat +Comment[ru]=Отмонтированный CD +Comment[rw]=CD-ROM Yakuwemo +Comment[se]=Gálgajuvvon CD-ROM +Comment[sk]=Nepripojený CD-ROM +Comment[sl]=Odklopljen CD-ROM +Comment[sr]=Демонтиран CD-ROM +Comment[sr@Latn]=Demontiran CD-ROM +Comment[sv]=Avmonterad cd-rom +Comment[ta]=வெளியேறà¯à®±à®¿à®¯ கà¯à®±à¯à®¨à¯à®¤à®•à®Ÿà¯ +Comment[tg]=Ҷудо шудаи CD-ROM +Comment[th]=ซีดีรà¸à¸¡à¸—ี่ไม่ได้เม้านท์ +Comment[tr]=Ayrılmış CD-ROM +Comment[tt]=TotaÅŸmaÄŸan CD-ROM +Comment[uk]=Демонтований КД +Comment[uz]=Ulanmagan kompakt-disk +Comment[uz@cyrillic]=Уланмаган компакт-диÑк +Comment[ven]=CD-ROM i songo gonyiswaho +Comment[vi]=á»” CD-ROM đã gỡ ra +Comment[wa]=Plake lazer CD dismontêye +Comment[xh]=CD-ROM Enganyuswanga +Comment[zh_CN]=未挂载的 CD-ROM +Comment[zh_TW]=未掛載的 CD-ROM +Comment[zu]=I-CD-ROM eyehlisiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/cdwriter_mounted.desktop b/kioslave/media/mimetypes/cdwriter_mounted.desktop new file mode 100644 index 000000000..110e8f0a5 --- /dev/null +++ b/kioslave/media/mimetypes/cdwriter_mounted.desktop @@ -0,0 +1,86 @@ +[Desktop Entry] +Icon=cdwriter_mount +Type=MimeType +MimeType=media/cdwriter_mounted +Comment=Mounted CD Writer +Comment[af]=Gekoppelde Cd Skrywer +Comment[ar]=ناسخ أقراص Ù…Øمّل +Comment[az]=BaÄŸlanmış CD Yazıcı +Comment[be]=Прымацаваны запіÑвальнік CD +Comment[bg]=Монтиран компактдиÑк +Comment[bn]=মাউনà§à¦Ÿ করা সিডি-রাইটার +Comment[br]=Engraver CD marc'het +Comment[bs]=Montiran CD pisaÄ +Comment[ca]=CD Writer muntat +Comment[cs]=PÅ™ipojená vypalovaÄka CD +Comment[csb]=Zamòntowóny wëpôlôrz CD +Comment[cy]=Ysgrifennwr CD wedi'i osod +Comment[da]=Monteret cd-skriver +Comment[de]=Eingebundener CD-Brenner +Comment[el]=Î ÏοσαÏτημÎνο CD Writer +Comment[eo]=Surmetita skribebla LD +Comment[es]=Escritor de CDs montado +Comment[et]=Ãœhendatud CD kirjutaja +Comment[eu]=CD erretzaile muntatua +Comment[fa]=CD Writer سوارشده +Comment[fi]=Liitetty CD-kirjoitin +Comment[fr]=Graveur de CD monté +Comment[fy]=Oankeppele Kompaktskiif +Comment[ga]=ScrÃbhneoir Feistithe CDanna +Comment[gl]=Grabadora de CD Montada +Comment[he]=צורב מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ सीडी-राइटर +Comment[hr]=Pristupljeni CD snimaÄ +Comment[hsb]=Montowany CD-palak +Comment[hu]=Csatlakoztatott CD-Ãró +Comment[is]=Tengdur geislaskrifari +Comment[it]=Masterizzatore montato +Comment[ja]=マウントã•ã‚Œã¦ã„ã‚‹ CD ライター +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული CD Writer +Comment[kk]=Тіркеген CD Writer +Comment[km]=ឧបករណáŸâ€‹ážŸážšážŸáŸážšâ€‹ážŸáŸŠáž¸ážŒáž¸â€‹áž”ាន​រៀបចំ +Comment[lo]= ຈà»àºžàº²àºš +Comment[lt]=Sumontuotas CD raÅ¡ymo įrenginys +Comment[lv]=PiemontÄ“ts CD rakstÄ«tÄjs +Comment[mk]=Монтиран ЦД-Ñнимач +Comment[mn]=ЗалгагдÑан КД-шарагч +Comment[ms]=Penulis CD Terlekap +Comment[mt]=CDWriter immontat +Comment[nb]=Montert CD-brenner +Comment[nds]=Inhangt CD-Brenner +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ सीडी राइटर +Comment[nl]=Aangekoppelde cd-writer +Comment[nn]=Montert CD-brennar +Comment[nso]=Sengwadi sa CD seo se Nameleditswego +Comment[pa]=ਮਾਊਟ CD ਰਾਇਟਰ +Comment[pl]=Zamontowana nagrywarka CD +Comment[pt]=Gravador de CDs montado +Comment[pt_BR]=Gravador de CD Montado +Comment[ro]=CD Writer montat +Comment[ru]=Смонтированный CD (Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñтью запиÑи) +Comment[rw]=Mwandika CD Yashyizwemo +Comment[se]=ÄŒatnon CD-Äálli +Comment[sk]=Pripojená napaľovacia mechanika CD +Comment[sl]=Priklopljen zapisovalnik CD-jev +Comment[sr]=Монтиран CD резач +Comment[sr@Latn]=Montiran CD rezaÄ +Comment[sv]=Monterad cd-brännare +Comment[ta]=à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ கà¯à®±à¯à®¨à¯à®¤à®•à®Ÿà¯ எழà¯à®¤à®¿ +Comment[tg]=Монтажшудаи CD Writer +Comment[th]=เครื่à¸à¸‡à¹€à¸‚ียนซีดีที่เม้านท์à¹à¸¥à¹‰à¸§ +Comment[tr]=BaÄŸlı CD Yazıcı +Comment[tt]=TotaÅŸqan CD Yazdırğıç +Comment[uk]=Змонтований гравер КД +Comment[uz]=Ulangan kompakt-disk yozuvchi +Comment[uz@cyrillic]=Уланган компакт-диÑк ёзувчи +Comment[ven]=Tshinwali tsha CD tsho gonyiswaho +Comment[vi]=á»” ghi CD đã kết nối +Comment[wa]=Plake lazer CD sol broûleu montêye +Comment[xh]=CD Writer Enyusiweyo +Comment[zh_CN]=挂载的刻录机 +Comment[zh_TW]=已掛載的 CD Writer +Comment[zu]=I-CD-Writer eyenyusiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/cdwriter_unmounted.desktop b/kioslave/media/mimetypes/cdwriter_unmounted.desktop new file mode 100644 index 000000000..7e2ea5933 --- /dev/null +++ b/kioslave/media/mimetypes/cdwriter_unmounted.desktop @@ -0,0 +1,86 @@ +[Desktop Entry] +Icon=cdwriter_unmount +Type=MimeType +MimeType=media/cdwriter_unmounted +Comment=Unmounted CD Writer +Comment[af]=Ontkoppelde CD Skrywer +Comment[ar]=ناسخ أقراص غير Ù…Øمّل +Comment[az]=Ayrılmış CD Yazıcı +Comment[be]=Ðдмацаваны запіÑвальнік CD +Comment[bg]=Демонтиран компактдиÑк +Comment[bn]=আনমাউনà§à¦Ÿ করা সিডি-রাইটার +Comment[br]=Engraver CD divarc'het +Comment[bs]=Demontiran CD pisaÄ +Comment[ca]=CD Writer desmuntat +Comment[cs]=Odpojená vypalovaÄka CD +Comment[csb]=Ã’dmòntowóny wëpôlôrz CD +Comment[cy]=Ysgrifennwr CD wedi'i ddadosod +Comment[da]=Afmonteret cd-skriver +Comment[de]=Nicht eingebundener CD-Brenner +Comment[el]=ΑποπÏοσαÏτημÎνο CD Writer +Comment[eo]=Demetita skribebla LD +Comment[es]=Escritor de CDs desmontado +Comment[et]=Lahutatud CD kirjutaja +Comment[eu]=CD erretzaile desmuntatua +Comment[fa]=CD Writer پیاده‌شده +Comment[fi]=Irrotettu CD-kirjoitin +Comment[fr]=Graveur de CD non monté +Comment[fy]=Ofkeppele Kompaktskiif +Comment[ga]=ScrÃbhneoir Neamhfheistithe CDanna +Comment[gl]=Grabadora de CD Non Montada +Comment[he]=צורב ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ सीडी-राइटर +Comment[hr]=Nepristupljeni CD snimaÄ +Comment[hsb]=Njemontowany CD-palak +Comment[hu]=Leválasztott CD-Ãró +Comment[is]=Aftengdur geislaskrifari +Comment[it]=Masterizzatore smontato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ CD ライター +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული CD Writer +Comment[kk]=Ðжыратылған CD Writer +Comment[km]=ឧបករណáŸâ€‹ážŸážšážŸáŸážšâ€‹ážŸáŸŠáž¸ážŒáž¸â€‹áž˜áž·áž“​បាន​រៀបចំ +Comment[lo]= ຫັງàºàº²àº¥àºµ +Comment[lt]=IÅ¡montuotas CD raÅ¡ymo įrenginys +Comment[lv]=NomontÄ“ts CD rakstÄ«tÄjs +Comment[mk]=Одмонтиран ЦД-Ñнимач +Comment[mn]=СалгагдÑан КД-шарагч +Comment[ms]=Penulis CD Nyahlekap +Comment[mt]=CDWriter mhux immontat +Comment[nb]=Avmontert CD-brenner +Comment[nds]=Afhangt CD-Brenner +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ सीडी राइटर +Comment[nl]=Afgekoppelde cd-writer +Comment[nn]=Avmontert CD-brennar +Comment[nso]=Sengwadi sa CD seo se Theositswego +Comment[pa]=ਅਨਮਾਊਟ CD ਰਾਇਟਰ +Comment[pl]=Odmontowana nagrywarka CD +Comment[pt]=Gravador de CDs desmontado +Comment[pt_BR]=Gravador de CD Desmontado +Comment[ro]=CD Writer nemontat +Comment[ru]=Отмонтированный CD (Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñтью запиÑи) +Comment[rw]=Mwandika CD Yakuwemo +Comment[se]=Gálgajuvvon CD-Äálli +Comment[sk]=Nepripojená napaľovacia mechanika CD +Comment[sl]=Odklopljen zapisovalnik CD-jev +Comment[sr]=Демонтиран CD резач +Comment[sr@Latn]=Demontiran CD rezaÄ +Comment[sv]=Avmonterad cd-brännare +Comment[ta]=வெளியேறà¯à®±à®¿à®¯ கà¯à®±à¯à®¨à¯à®¤à®•à®Ÿà¯ எழà¯à®¤à®¿ +Comment[tg]=Ҷудо шудаи CD Writer +Comment[th]=เครื่à¸à¸‡à¹€à¸‚ียนซีดีที่ไม่ได้à¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=Ayrılmış CD Yazıcı +Comment[tt]=TotaÅŸmaÄŸan CD Yazdırğıç +Comment[uk]=Демонтований гравер КД +Comment[uz]=Ulanmagan kompakt-disk yozuvchi +Comment[uz@cyrillic]=Уланмаган компакт-диÑк ёзувчи +Comment[ven]=Tshinwali tsha CD tshi songo gonyiswaho +Comment[vi]=á»” ghi CD đã gỡ ra +Comment[wa]=Plake lazer CD sol broûleu dismontêye +Comment[xh]=CD Writer Enganyuswanga +Comment[zh_CN]=未挂载的刻录机 +Comment[zh_TW]=未掛載的 CD Writer +Comment[zu]=I-CD-Writer eyehlisiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/dvd_mounted.desktop b/kioslave/media/mimetypes/dvd_mounted.desktop new file mode 100644 index 000000000..2d9e0b512 --- /dev/null +++ b/kioslave/media/mimetypes/dvd_mounted.desktop @@ -0,0 +1,81 @@ +[Desktop Entry] +Icon=dvd_mount +Type=MimeType +MimeType=media/dvd_mounted +Comment=Mounted DVD +Comment[af]=Gekoppelde DVD +Comment[ar]=DVD Ù…Øمّل +Comment[az]=BaÄŸlanmış DVD +Comment[be]=Прымацаваны DVD +Comment[bg]=Монтиран DVD диÑк +Comment[bn]=মাউনà§à¦Ÿ করা ডিà¦à¦¿à¦¡à¦¿ +Comment[br]=DVD marc'het +Comment[bs]=Montiran DVD +Comment[ca]=DVD muntat +Comment[cs]=PÅ™ipojené DVD +Comment[csb]=Zamòntowóny nëk DVD +Comment[cy]=DVD wedi'i osod +Comment[da]=Monteret dvd +Comment[de]=Eingebundene DVD +Comment[el]=Î ÏοσαÏτημÎνο DVD +Comment[eo]=Surmetita DVD +Comment[es]=DVD montado +Comment[et]=Ãœhendatud DVD +Comment[eu]=DVD muntatua +Comment[fa]=DVD سوارشده +Comment[fi]=Liitetty DVD-levy +Comment[fr]=DVD-ROM monté(s) +Comment[fy]=Oankeppele Dûbelskiif +Comment[ga]=DVD feistithe +Comment[gl]=DVD Montado +Comment[he]=×›×•× ×Ÿ DVD מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ डीवीडी +Comment[hr]=Pristupljeni DVD +Comment[hu]=Csatlakoztatott DVD +Comment[is]=Tengt DVD +Comment[it]=DVD montato +Comment[ja]=マウントã•ã‚ŒãŸ DVD +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული DVD +Comment[kk]=Тіркеген DVD +Comment[km]=ឌីវីឌី​ដែល​បាន​រៀបចំ +Comment[lo]=ຈà»àºžàº²àºš +Comment[lt]=Sumontuotas DVD +Comment[lv]=PiemontÄ“ts DVD +Comment[mk]=Монтиран DVD +Comment[mn]=ЗалгагдÑан DVD +Comment[ms]=DVD Terlekap +Comment[mt]=DVD immontat +Comment[nb]=Montert DVD +Comment[nds]=Inhangt DVD +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ डी à¤à¥€ डी +Comment[nl]=Aangekoppelde dvd-romschijf +Comment[nn]=Montert DVD +Comment[pa]=ਮਾਊਟ ਕੀਤੀ DVD +Comment[pl]=Zamontowany DVD +Comment[pt]=DVD montado +Comment[pt_BR]=DVD Montado +Comment[ro]=DVD montat +Comment[ru]=Смонтированный DVD +Comment[rw]=DVD Yashyizwemo +Comment[se]=ÄŒatnon DVD +Comment[sk]=Pripojené DVD +Comment[sl]=Priklopljen DVD +Comment[sr]=Монтиран DVD +Comment[sr@Latn]=Montiran DVD +Comment[sv]=Monterad dvd +Comment[ta]=à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ சிடிராம௠+Comment[tg]=Монтажшудаи DVD +Comment[th]=ดีวีดีที่เม้านท์à¹à¸¥à¹‰à¸§ +Comment[tr]=BaÄŸlı DVD +Comment[tt]=TotaÅŸqan DVD +Comment[uk]=Змонтований DVD +Comment[uz]=Ulangan DVD +Comment[uz@cyrillic]=Уланган DVD +Comment[vi]=DVD đã kết nối +Comment[wa]=Plake lazer DVD montêye +Comment[zh_CN]=挂载的 DVD +Comment[zh_TW]=已掛載的 DVD-ROM +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/dvd_unmounted.desktop b/kioslave/media/mimetypes/dvd_unmounted.desktop new file mode 100644 index 000000000..918f7cd00 --- /dev/null +++ b/kioslave/media/mimetypes/dvd_unmounted.desktop @@ -0,0 +1,81 @@ +[Desktop Entry] +Icon=dvd_unmount +Type=MimeType +MimeType=media/dvd_unmounted +Comment=Unmounted DVD +Comment[af]=Ontkoppelde DVD +Comment[ar]=DVD غير Ù…Øمّل +Comment[az]=Ayrılmış DVD +Comment[be]=Ðдмацаваны DVD +Comment[bg]=Демонтиран DVD диÑк +Comment[bn]=আনমাউনà§à¦Ÿ করা ডিà¦à¦¿à¦¡à¦¿ +Comment[br]=DVD-ROM divountet +Comment[bs]=Demontiran DVD +Comment[ca]=DVD desmuntat +Comment[cs]=Odpojené DVD +Comment[csb]=Ã’dmòntowóny nëk DVD +Comment[cy]=DVD wedi'i ddadosod +Comment[da]=Afmonteret dvd +Comment[de]=Nicht eingebundene DVD +Comment[el]=ΑποπÏοσαÏτημÎνο DVD +Comment[eo]=Demetita DVD +Comment[es]=DVD desmontado +Comment[et]=Lahutatud DVD +Comment[eu]=DVD desmuntatua +Comment[fa]=DVD پیاده‌شده +Comment[fi]=Irrotettu DVD-levy +Comment[fr]=DVD-ROM non monté(s) +Comment[fy]=Ofkeppele Dûbelskiif +Comment[ga]=DVD neamhfheistithe +Comment[gl]=DVD non Montado +Comment[he]=×›×•× ×Ÿ DVD ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ डीवीडी +Comment[hr]=Nepristupljeni DVD +Comment[hu]=Leválasztott DVD +Comment[is]=Aftengt DVD +Comment[it]=DVD smontato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ DVD +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული DVD +Comment[kk]=Тіркеуден шығарған DVD +Comment[km]=ឌីវីឌី​ដែល​មិន​បាន​រៀបចំ +Comment[lo]=ຫັງàºàº²àº¥àºµ +Comment[lt]=IÅ¡montuotas DVD +Comment[lv]=NomontÄ“ts DVD +Comment[mk]=Одмонтиран DVD +Comment[mn]=Салгаатай DVD +Comment[ms]=DVD Nyahlekap +Comment[mt]=DVD mhux immontat +Comment[nb]=Avmontert DVD +Comment[nds]=Afhangt DVD +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ डी à¤à¥€ डी +Comment[nl]=Afgekoppelde dvd-romschijf +Comment[nn]=Avmontert DVD +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ DVD +Comment[pl]=Odmontowany DVD +Comment[pt]=DVD desmontado +Comment[pt_BR]=DVD Desmontado +Comment[ro]=DVD nemontat +Comment[ru]=Отмонтированный DVD +Comment[rw]=DVD Yakuwemo +Comment[se]=Gálgajuvvon DVD +Comment[sk]=Nepripojené DVD +Comment[sl]=Odklopljen DVD +Comment[sr]=Демонтиран DVD +Comment[sr@Latn]=Demontiran DVD +Comment[sv]=Avmonterad dvd +Comment[ta]=இறகà¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿ சிடிராம௠+Comment[tg]=Ғайри монтажшудаи DVD +Comment[th]=ดีวีดีที่ไม่ได้เม้านท์ +Comment[tr]=Ayrılmış DVD +Comment[tt]=TotaÅŸmaÄŸan DVD +Comment[uk]=Демонтований DVD +Comment[uz]=Ulanmagan DVD +Comment[uz@cyrillic]=Уланмаган DVD +Comment[vi]=DVD đã gỡ ra +Comment[wa]=Plake lazer DVD dismontêye +Comment[zh_CN]=未挂载的 DVD +Comment[zh_TW]=未掛載的 DVD-ROM +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/dvdvideo.desktop b/kioslave/media/mimetypes/dvdvideo.desktop new file mode 100644 index 000000000..756919293 --- /dev/null +++ b/kioslave/media/mimetypes/dvdvideo.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/dvdvideo +Comment=DVD Video Disk +Comment[af]=DVD Video skyf +Comment[ar]=قرص رقمي مرئي للمرئيات +Comment[be]=ВідÑадыÑк DVD +Comment[bg]=Видео DVD диÑк +Comment[bn]=ডিà¦à¦¿à¦¡à¦¿ à¦à¦¿à¦¡à¦¿à¦“ ডিসà§à¦• +Comment[br]=Pladenn DVD Video +Comment[bs]=DVD video disk +Comment[ca]=VÃdeo disc DVD +Comment[cs]=DVD video +Comment[csb]=Disk DVD Video +Comment[da]=Dvd video-disk +Comment[de]=DVD Video-Disk +Comment[el]=Δίσκος DVD (βίντεο) +Comment[eo]=DVDa videodisko +Comment[es]=Disco de video DVD +Comment[et]=DVD videoplaat +Comment[eu]=DVD bideo diska +Comment[fa]=دیسک ویدیویی DVD +Comment[fi]=DVD-videolevy +Comment[fr]=DVD vidéo +Comment[fy]=Dûbelskiif Fideoskiif +Comment[ga]=FÃsdiosca DVD +Comment[gl]=DVD de vÃdeo +Comment[he]=תקליטור ויד×ו של DVD +Comment[hi]=डीवीडी वीडियो डिसà¥à¤• +Comment[hr]=DVD video disk +Comment[hu]=DVD-videolemez +Comment[is]=DVD vÃdeódiskur +Comment[it]=Disco video DVD +Comment[ja]=DVD ビデオディスク +Comment[ka]=ვიდერDVD დისკი +Comment[kk]=DVD бейне диÑкі +Comment[km]=ážáž¶ážŸâ€‹ážœáž¸ážŠáŸáž¢áž¼ ឌីវីឌី +Comment[lt]=DVD video diskas +Comment[lv]=DVD Video Disks +Comment[mk]=DVD ВидеодиÑк +Comment[ms]=Cakera Video DVD +Comment[nb]=DVD-videoplate +Comment[nds]=DVD-Videodisk +Comment[ne]=डी à¤à¥€ डी à¤à¤¿à¤¡à¤¿à¤¯à¥‹ डिसà¥à¤• +Comment[nl]=DVD Videodisk +Comment[nn]=DVD-videoplate +Comment[pa]=DVD ਵੀਡਿਓ ਡਿਸਕ +Comment[pl]=PÅ‚yta DVD Video +Comment[pt]=Disco DVD de VÃdeo +Comment[pt_BR]=DVD de vÃdeo +Comment[ro]=Disc video DVD +Comment[ru]=DVD Ñ Ð²Ð¸Ð´ÐµÐ¾ +Comment[rw]=Disiki Videwo DVD +Comment[se]=DVD-videoskearru +Comment[sk]=DVD Video disk +Comment[sl]=DVD Video disk +Comment[sr]=DVD видео диÑк +Comment[sr@Latn]=DVD video disk +Comment[sv]=Dvd-videoskiva +Comment[ta]=டிவிடி படகà¯à®•à®¾à®Ÿà¯à®šà®¿ வடà¯à®Ÿà¯ +Comment[te]=డివిడి విడియొ à°¡à°¿à°¸à±à°•à± +Comment[th]=à¹à¸œà¹ˆà¸™à¸”ีวีดีภาพยนตร์ +Comment[tr]=DVD Video +Comment[uk]=Відео диÑк DVD +Comment[uz]=DVD video-disk +Comment[uz@cyrillic]=DVD видео-диÑк +Comment[vi]=ÄÄ©a Video DVD +Comment[wa]=Plake videyo DVD +Comment[zh_CN]=DVD 影碟 +Comment[zh_TW]=DVD 視訊光碟 +Icon=dvd_unmount + +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/floppy5_mounted.desktop b/kioslave/media/mimetypes/floppy5_mounted.desktop new file mode 100644 index 000000000..b0284cadb --- /dev/null +++ b/kioslave/media/mimetypes/floppy5_mounted.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Icon=5floppy_mount +Type=MimeType +MimeType=media/floppy5_mounted +Comment=Mounted 5¼" Floppy +Comment[af]=Gekoppelde 5¼" Sagteskyf +Comment[ar]=قرص مرن 5.25 Ù…Øمّل +Comment[az]=BaÄŸlanmış 5¼" Floppi +Comment[be]=ÐŸÑ€Ñ‹Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð´Ñ‹Ñкета 5¼" +Comment[bg]=Монтирано 5¼" флопи +Comment[bn]=মাউনà§à¦Ÿ করা 5¼" ফà§à¦²à¦ªà¦¿ +Comment[br]=Pladennig 5¼" marc'het +Comment[bs]=Montirana 5?" disketa +Comment[ca]=Disquet 5¼" muntat +Comment[cs]=PÅ™ipojená disketa 5¼" +Comment[csb]=Zamòntowónô disczétka 5¼" +Comment[cy]=Disg meddal 5.25" wedi'i osod +Comment[da]=Monteret 5¼"-floppy +Comment[de]=Eingebundene 5¼"-Diskette +Comment[el]=Î ÏοσαÏτημÎνη δισκÎτα 5¼" +Comment[eo]=Surmetita 5¼"-Disketo +Comment[es]=Disquete 5¼" montado +Comment[et]=Ãœhendatud 5¼" flopi +Comment[eu]=5¼"ko diskete muntatua +Comment[fa]=Ùلاپی 5¼" سوارشده +Comment[fi]=Liitetty 5¼"-levyke +Comment[fr]=Disquette 5¼" montée +Comment[fy]=Oankeppele 5¼" Skiif +Comment[ga]=Diosca flapach 5¼" feistithe +Comment[gl]=Disquete 5¼" Montado +Comment[he]=תקליטון "¼5 מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ 5¼" फ़à¥à¤²à¥‰à¤ªà¥€ +Comment[hr]=Pristupljena 5¼" disketa +Comment[hu]=Csatlakoztatott 5¼"-es floppy +Comment[is]=Tengdur 5¼" disklingur +Comment[it]=Dischetto da 5¼" montato +Comment[ja]=マウントã•ã‚ŒãŸ 5 インãƒãƒ•ãƒãƒƒãƒ”ー +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული დისკწáƒáƒ›áƒ§áƒ•áƒáƒœáƒ˜ 5¼" +Comment[kk]=Тіркеген 5¼" иілгіш диÑк +Comment[km]=5¼" Floppy បាន​រៀបចំ +Comment[lo]=ຟà»à»àº¡àº”ຟàºàº›àºµà»‰ - K +Comment[lt]=Sumontuotas 5¼" lankstus diskelis +Comment[lv]=PiemontÄ“ta 5¼" diskete +Comment[mk]=Монтирана 5¼" диÑкета +Comment[mn]=ЗалгагдÑан 5¼"-УÑн диÑк +Comment[ms]=Liut 5¼" Terlekap +Comment[mt]=Flopi 5¼" immontat +Comment[nb]=Montert 5¼" diskett +Comment[nds]=Inhangt 5¼"-Diskett +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ 5¼" फà¥à¤²à¤ªà¥€ +Comment[nl]=Aangekoppelde 5¼" diskette +Comment[nn]=Montert 5¼"-diskett +Comment[nso]=Floppy yeo e Nameleditswego ya 5¼" +Comment[pa]=ਮਾਊਟ 5¼" ਫਲਾਪੀ +Comment[pl]=Zamontowana dyskietka 5¼" +Comment[pt]=Disquete de 5¼" montada +Comment[pt_BR]=Disquete 5¼" Montado +Comment[ro]=Dischetă 5¼" montată +Comment[ru]=Ð¡Ð¼Ð¾Ð½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð´Ð¸Ñкета 5¼" +Comment[rw]=Disikete 5¼" Yashyizwemo +Comment[se]=ÄŒatnon 5¼-dibmaskearru +Comment[sk]=Pripojená disketa 5¼" +Comment[sl]=Priklopljena disketna enota 5¼" +Comment[sr]=Монтиран 5¼" флопи +Comment[sr@Latn]=Montiran 5¼" flopi +Comment[sv]=Monterad 5¼"-diskett +Comment[ta]=à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ 5¼" நெகிழà¯à®µà®Ÿà¯à®Ÿà¯ +Comment[tg]=Монтажшудаи 5¼" Флоппи +Comment[th]=ฟล็à¸à¸›à¸›à¸µ 5" ที่เม้านท์à¹à¸¥à¹‰à¸§ +Comment[tr]=BaÄŸlı 5¼" Disket +Comment[tt]=TotaÅŸqan 5¼" Floppy +Comment[uk]=Змонтований флопі 5¼" +Comment[uz]=Ulangan 5¼"disket +Comment[uz@cyrillic]=Уланган 5¼"диÑкет +Comment[ven]=5¼" Floppy yo gonyiswaho +Comment[vi]=ÄÄ©a má»m 5¼" đã kết nối +Comment[wa]=Plakete 5" ¼ montêye +Comment[xh]=Elayishiweyo 5¼" Floppy +Comment[zh_CN]=挂载的 5.25 英寸软驱 +Comment[zh_TW]=已掛載的軟碟機 +Comment[zu]=I-5¼" Floppy eyenyusiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/floppy5_unmounted.desktop b/kioslave/media/mimetypes/floppy5_unmounted.desktop new file mode 100644 index 000000000..53e53b46f --- /dev/null +++ b/kioslave/media/mimetypes/floppy5_unmounted.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Icon=5floppy_unmount +Type=MimeType +MimeType=media/floppy5_unmounted +Comment=Unmounted 5¼" Floppy +Comment[af]=Ontkoppelde 5¼" Sagteskyf +Comment[ar]=قرص مرن 5.25 غير Ù…Øمّل +Comment[az]=Ayrılmış 5¼" Floppi +Comment[be]=ÐÐ´Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð´Ñ‹Ñкета 5¼" +Comment[bg]=Демонтирано 5¼" флопи +Comment[bn]=আনমাউনà§à¦Ÿ করা 5¼" ফà§à¦²à¦ªà¦¿ +Comment[br]=Pladennig 5¼" divountet +Comment[bs]=Demontirana 5?" disketa +Comment[ca]=Disquet 5¼" desmuntat +Comment[cs]=Odpojená disketa 5¼" +Comment[csb]=Ã’dmòntowónô disczétka 5¼" +Comment[cy]=Disg meddal 5.25" wedi'i ddadosod +Comment[da]=Afmonteret 5¼"-floppy +Comment[de]=Nicht eingebundene 5¼"-Diskette +Comment[el]=ΑποπÏοσαÏτημÎνη δισκÎτα 5¼" +Comment[eo]=Demetita 5¼"-Disketo +Comment[es]=Disquete 5¼" desmontado +Comment[et]=Lahutatud 5¼" flopi +Comment[eu]=5¼"ko diskete desmuntatua +Comment[fa]=Ùلاپی 5¼" پیاده‌شده +Comment[fi]=Irrotettu 5¼"-levyke +Comment[fr]=Disquette 5¼" non montée +Comment[fy]=Ofkeppele 5¼" Skiif +Comment[ga]=Diosca flapach 5¼" neamhfheistithe +Comment[gl]=Disquete 5¼" Non Montado +Comment[he]=תקליטון "¼5 ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ 5¼" फ़à¥à¤²à¥‰à¤ªà¥€ +Comment[hr]=Nepristupljena 5¼" disketa +Comment[hu]=Leválasztott 5¼"-es floppy +Comment[is]=Aftengdur 5¼" disklingur +Comment[it]=Dischetto da 5¼" non montato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ 5 インãƒãƒ•ãƒãƒƒãƒ”ー +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული დისკწáƒáƒ›áƒ§áƒ•áƒáƒœáƒ˜ 5¼" +Comment[kk]=Тіркеуден шығарған 5¼" иілгіш диÑкі +Comment[km]=5¼" Floppy មិន​បាន​រៀបចំ +Comment[lo]=ຫັງàºàº²àº¥àºµ +Comment[lt]=IÅ¡montuotas 5¼" lankstus diskelis +Comment[lv]=NomontÄ“ta 5¼" diskete +Comment[mk]=Одмонтирана 5¼" диÑкета +Comment[mn]=СалгагдÑан 5¼"-УÑн диÑк +Comment[ms]=Liut 5¼" Terlekap +Comment[mt]=Flopi 5¼" mhux immontat +Comment[nb]=Avmontert 5¼" diskett +Comment[nds]=Afhangt 5¼"-Diskett +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ 5¼" फà¥à¤²à¤ªà¥€ +Comment[nl]=Afgekoppelde 5¼" diskette +Comment[nn]=Avmontert 5¼"-diskett +Comment[nso]=Floppy yeo e Theositswego ya 5¼" +Comment[pa]=ਅਨਮਾਊਟ 5¼" ਫਲਾਪੀ +Comment[pl]=Odmontowana dyskietka 5¼" +Comment[pt]=Disquete de 5¼" desmontada +Comment[pt_BR]=Disquete 5¼" Desmontado +Comment[ro]=Dischetă 5¼" nemontată +Comment[ru]=ÐžÑ‚Ð¼Ð¾Ð½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð´Ð¸Ñкета 5¼" +Comment[rw]=Disikete 5¼" Yakuwemo +Comment[se]=Gálgajuvvon 5¼-dibmaskearru +Comment[sk]=Nepripojená disketa 5¼" +Comment[sl]=Odklopljena disketna enota 5¼" +Comment[sr]=Демонтиран 5¼" флопи +Comment[sr@Latn]=Demontiran 5¼" flopi +Comment[sv]=Avmonterad 5¼"-diskett +Comment[ta]=வெளியேறà¯à®±à®¿à®¯ 5¼" நெகிழà¯à®µà®Ÿà¯à®Ÿà¯ +Comment[tg]=Ҷудо шудаи 5¼" Флоппи +Comment[th]=ฟล็à¸à¸›à¸›à¸µ 5" ที่ไม่ได้เม้านท์ +Comment[tr]=BaÄŸlı olmayan 5¼" Disket +Comment[tt]=TotaÅŸqan 5¼" Floppy +Comment[uk]=Демонтований флопі 5¼" +Comment[uz]=Ulanmagan 5¼"disket +Comment[uz@cyrillic]=Уланмаган 5¼"диÑкет +Comment[ven]=5¼" Floppy i songo gonyiswaho +Comment[vi]=ÄÄ©a má»m 5¼" đã gỡ ra +Comment[wa]=Plakete 5" ¼ dismontêye +Comment[xh]=Enganyuswanga 5¼" Floppy +Comment[zh_CN]=未挂载的 5.25 英寸软驱 +Comment[zh_TW]=未掛載的軟碟機 +Comment[zu]=I-5¼" Floppy Yehlisiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/floppy_mounted.desktop b/kioslave/media/mimetypes/floppy_mounted.desktop new file mode 100644 index 000000000..0a51605fd --- /dev/null +++ b/kioslave/media/mimetypes/floppy_mounted.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Icon=3floppy_mount +Type=MimeType +MimeType=media/floppy_mounted +Comment=Mounted Floppy +Comment[af]=Gekoppelde Sagteskyf +Comment[ar]=قرص مرن Ù…Øمّل +Comment[az]=BaÄŸlanmış Floppi +Comment[be]=ÐŸÑ€Ñ‹Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð´Ñ‹Ñкета +Comment[bg]=Монтирано флопи +Comment[bn]=মাউনà§à¦Ÿ করা ফà§à¦²à¦ªà¦¿ +Comment[br]=Pladennig marc'het +Comment[bs]=Montirana disketa +Comment[ca]=Disquet muntat +Comment[cs]=PÅ™ipojená disketa +Comment[csb]=Zamòntowónô disczétka +Comment[cy]=Disg meddal wedi'i osod +Comment[da]=Monteret floppy +Comment[de]=Eingebundene Diskette +Comment[el]=Î ÏοσαÏτημÎνη δισκÎτα +Comment[eo]=Surmetita Disketo +Comment[es]=Disquete montado +Comment[et]=Ãœhendatud flopi +Comment[eu]=Diskete muntatua +Comment[fa]=Ùلاپی سوارشده +Comment[fi]=Liitetty levyke +Comment[fr]=Disquette montée +Comment[fy]=Oankeppele Skiif +Comment[ga]=Diosca flapach feistithe +Comment[gl]=Disquete Montado +Comment[he]=תקליטון מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ फ़à¥à¤²à¥‰à¤ªà¥€ +Comment[hr]=Pristupljena disketa +Comment[hu]=Csatlakoztatott floppy +Comment[is]=Tengdur disklingur +Comment[it]=Dischetto montato +Comment[ja]=マウントã•ã‚ŒãŸãƒ•ãƒãƒƒãƒ”ー +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული დრეკáƒáƒ“ი დისკი +Comment[kk]=Тііркеген иілгіш диÑк +Comment[km]=Floppy បាន​រៀបចំ +Comment[lo]=ຟà»à»àº¡àº”ຟàºàºšàº›àº´à»‰à»‰ - K +Comment[lt]=Sumontuotas lankstus diskelis +Comment[lv]=PiemontÄ“ta diskete +Comment[mk]=Монтирана диÑкета +Comment[mn]=Залгаатай уÑн диÑк +Comment[ms]=Liut Terlekap +Comment[mt]=Flopi mmontat +Comment[nb]=Montert diskett +Comment[nds]=Inhangt Diskett +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ फà¥à¤²à¤ªà¥€ +Comment[nl]=Aangekoppelde diskette +Comment[nn]=Montert diskett +Comment[nso]=Floppy yeo e Nameleditswego +Comment[pa]=ਮਾਊਟ ਕੀਤੀ ਫਲਾਪੀ +Comment[pl]=Zamontowana dyskietka +Comment[pt]=Disquete montada +Comment[pt_BR]=Disquete Montado +Comment[ro]=Dischetă montată +Comment[ru]=Ð¡Ð¼Ð¾Ð½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð´Ð¸Ñкета +Comment[rw]=Disikete Yashyizwemo +Comment[se]=ÄŒatnon dibmaskearru +Comment[sk]=Pripojená disketa +Comment[sl]=Priklopljena disketna enota +Comment[sr]=Монтиран флопи +Comment[sr@Latn]=Montiran flopi +Comment[sv]=Monterad diskett +Comment[ta]=இறகà¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿ நெகிழà¯à®µà®Ÿà¯à®Ÿà¯ +Comment[tg]=Монтажшудаи Floppy +Comment[th]=ฟล็à¸à¸›à¸›à¸µà¸—ี่เม้านท์à¹à¸¥à¹‰à¸§ +Comment[tr]=BaÄŸlı Disket +Comment[tt]=TotaÅŸqan Floppy +Comment[uk]=Змонтований флопі +Comment[uz]=Ulangan disket +Comment[uz@cyrillic]=Уланган диÑкет +Comment[ven]=Floppy yo gonyiswaho +Comment[vi]=ÄÄ©a má»m đã kết nối +Comment[wa]=Plakete montêye +Comment[xh]=Floppy Enyusiweyo +Comment[zh_CN]=挂载的软驱 +Comment[zh_TW]=已掛載的軟碟機 +Comment[zu]=I-Floppy eyenyusiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/floppy_unmounted.desktop b/kioslave/media/mimetypes/floppy_unmounted.desktop new file mode 100644 index 000000000..e17ab5557 --- /dev/null +++ b/kioslave/media/mimetypes/floppy_unmounted.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Icon=3floppy_unmount +Type=MimeType +MimeType=media/floppy_unmounted +Comment=Unmounted Floppy +Comment[af]=Ontkoppelde Sagteskyf +Comment[ar]=قرص مرن غير Ù…Øمّل +Comment[az]=Ayrılmış FLoppi +Comment[be]=ÐÐ´Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð´Ñ‹Ñкета +Comment[bg]=Демонтирано флопи +Comment[bn]=আনমাউনà§à¦Ÿ করা ফà§à¦²à¦ªà¦¿ +Comment[br]=Pladennig divountet +Comment[bs]=Demontirana disketa +Comment[ca]=Disquet desmuntat +Comment[cs]=Odpojená disketa +Comment[csb]=Ã’dmòntowónô disczétka +Comment[cy]=Disg meddal wedi'i ddadosod +Comment[da]=Afmonteret floppy +Comment[de]=Nicht eingebundene Diskette +Comment[el]=ΑποπÏοσαÏτημÎνη δισκÎτα +Comment[eo]=Demetita Disketo +Comment[es]=Disquete desmontado +Comment[et]=Lahutatud flopi +Comment[eu]=Diskete desmuntatua +Comment[fa]=Ùلاپی پیاده‌شده +Comment[fi]=Irrotettu levyke +Comment[fr]=Disquette non montée +Comment[fy]=Ofkeppele Skiif +Comment[ga]=Diosca flapach neamhfheistithe +Comment[gl]=Disquete non Montado +Comment[he]=תקליטון ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ फ़à¥à¤²à¥‰à¤ªà¥€ +Comment[hr]=Nepristupljena disketa +Comment[hu]=Leválasztott floppy +Comment[is]=Aftengdur disklingur +Comment[it]=Dischetto non montato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„フãƒãƒƒãƒ”ー +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული დრეკáƒáƒ“ი დისკი +Comment[kk]=Тіркеуден шығарған иілгіш диÑк +Comment[km]=Floppy មិន​បាន​រៀបចំ +Comment[lo]=ຫັງàºàº²àº¥àºµ +Comment[lt]=IÅ¡montuotas lankstus diskelis +Comment[lv]=NomontÄ“ta diskete +Comment[mk]=Одмонтирана диÑкета +Comment[mn]=СалгагдÑан уÑн диÑкүүд +Comment[ms]=Liut Nyahlekap +Comment[mt]=Flopi mhux immontat +Comment[nb]=Avmontert diskett +Comment[nds]=Afhangt Diskett +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ फà¥à¤²à¤ªà¥€ +Comment[nl]=Afgekoppelde diskette +Comment[nn]=Avmontert diskett +Comment[nso]=Floppy yeo e Theositswego +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ ਫਲਾਪੀ +Comment[pl]=Odmontowana dyskietka +Comment[pt]=Disquete desmontada +Comment[pt_BR]=Disquete Desmontado +Comment[ro]=Dischetă nemontată +Comment[ru]=ÐžÑ‚Ð¼Ð¾Ð½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð´Ð¸Ñкета +Comment[rw]=Disikete Yakuwemo +Comment[se]=Gálgajuvvon dibmaskearru +Comment[sk]=Nepripojená disketa +Comment[sl]=Odklopljena disketna enota +Comment[sr]=Демонтиран флопи +Comment[sr@Latn]=Demontiran flopi +Comment[sv]=Avmonterad diskett +Comment[ta]=வெளியேறà¯à®±à®¿à®¯ நெகிழà¯à®µà®Ÿà¯à®Ÿà¯ +Comment[tg]=Ҷудо шудаи Floppy +Comment[th]=ฟล็à¸à¸›à¸›à¸µà¸—ี่ไม่ถูà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=BaÄŸlanmamış Disket +Comment[tt]=TotaÅŸmaÄŸan Floppy +Comment[uk]=Демонтований флопі +Comment[uz]=Ulanmagan disket +Comment[uz@cyrillic]=Уланмаган диÑкет +Comment[ven]=Floppy i songo gonyiswaho +Comment[vi]=ÄÄ©a má»m đã gỡ ra +Comment[wa]=Plakete dismontêye +Comment[xh]=Floppy Enganyuswanga +Comment[zh_CN]=未挂载的软驱 +Comment[zh_TW]=未掛載的軟碟機 +Comment[zu]=I-Floppy eyehlisiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/gphoto2camera.desktop b/kioslave/media/mimetypes/gphoto2camera.desktop new file mode 100644 index 000000000..613d69767 --- /dev/null +++ b/kioslave/media/mimetypes/gphoto2camera.desktop @@ -0,0 +1,76 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/gphoto2camera +Comment=Camera +Comment[af]=Kamera +Comment[ar]=كاميرا +Comment[az]=Kamera +Comment[be]=Камера +Comment[bg]=Фотоапарат +Comment[bn]=কà§à¦¯à¦¾à¦®à§‡à¦°à¦¾ +Comment[br]=Kamera +Comment[bs]=Kamera +Comment[ca]=Cà mera +Comment[cs]=Kamera +Comment[csb]=Ã’djimkòwô kamera +Comment[da]=Kamera +Comment[de]=Kamera +Comment[el]=ΚάμεÏα +Comment[eo]=Kamero +Comment[es]=Cámara +Comment[et]=Kaamera +Comment[eu]=Kamara +Comment[fa]=دوربین +Comment[fi]=Kamera +Comment[fr]=Nouvel appareil photo +Comment[fy]=Kamera +Comment[ga]=Ceamara +Comment[gl]=Cámara +Comment[he]=מצלמה +Comment[hi]=कैमरा +Comment[hr]=Kamera +Comment[hu]=FényképezÅ‘gép +Comment[is]=Myndavél +Comment[it]=Macchina fotografica +Comment[ja]=カメラ +Comment[ka]=კáƒáƒ›áƒ”რრ+Comment[kk]=Фотокамера +Comment[km]=ម៉ាស៊ីន​ážáž +Comment[lt]=Fotoaparatas +Comment[lv]=Kamera +Comment[mk]=Камера +Comment[ms]=Kamera +Comment[nb]=Kamera +Comment[nds]=Kamera +Comment[ne]=कà¥à¤¯à¤¾à¤®à¥‡à¤°à¤¾ +Comment[nn]=Kamera +Comment[pa]=ਕੈਮਰਾ +Comment[pl]=Aparat fotograficzny +Comment[pt]=Máquina fotográfica +Comment[pt_BR]=Câmera +Comment[ro]=Aparat foto +Comment[ru]=Камера +Comment[rw]=Kamera +Comment[se]=Govvanaperáhtta +Comment[sk]=Digitálny fotoaparát +Comment[sl]=Fotoaparat +Comment[sr]=Камера +Comment[sr@Latn]=Kamera +Comment[sv]=Kamera +Comment[ta]=பà¯à®•à¯ˆà®ªà¯à®ªà®Ÿ கரà¯à®µà®¿ +Comment[te]=కెమెరా +Comment[tg]=Камера +Comment[th]=à¸à¸¥à¹‰à¸à¸‡à¸”ิจิตà¸à¸¥ +Comment[tr]=Kamera +Comment[tt]=Kamera +Comment[uk]=Камера +Comment[uz]=Fotoaparat +Comment[uz@cyrillic]=Фотоапарат +Comment[vi]=Máy chụp ảnh số +Comment[zh_CN]=相机 +Comment[zh_TW]=相機 +Icon=camera_unmount + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory + diff --git a/kioslave/media/mimetypes/hdd_mounted.desktop b/kioslave/media/mimetypes/hdd_mounted.desktop new file mode 100644 index 000000000..2ebb0b1cc --- /dev/null +++ b/kioslave/media/mimetypes/hdd_mounted.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +Icon=hdd_mount +Type=MimeType +MimeType=media/hdd_mounted +Comment=Mounted Hard Disk Volume +Comment[af]=Gekoppelde Hard Skyf Partisie +Comment[ar]=تجزئة قرص صلب Ù…Øمّلة +Comment[az]=BaÄŸlanmış Sabit Disk BölmÉ™si +Comment[be]=Прымацаваны раздзел жорÑткага дыÑка +Comment[bg]=Монтиран дÑл на твърд диÑк +Comment[bn]=মাউনà§à¦Ÿ করা হারà§à¦¡ ডিসà§à¦• à¦à¦²à¦¿à¦‰à¦® +Comment[br]=Pladenn marc'het +Comment[bs]=Montirana hard disk particija +Comment[ca]=Disc dur muntat +Comment[cs]=PÅ™ipojený oddÃl pevného disku +Comment[csb]=Zamòntowónô particëjô cwiardi platë +Comment[da]=Monteret harddisk-volumen +Comment[de]=Eingebundene Festplattenpartition +Comment[el]=Î ÏοσαÏτημÎνη κατάτμηση σκληÏÎ¿Ï Î´Î¯ÏƒÎºÎ¿Ï… +Comment[eo]=Surmetita subdisko +Comment[es]=Volumen de disco duro montado +Comment[et]=Ãœhendatud kõvaketta partitsioon +Comment[eu]=Diska gogorraren zatiketa muntatua +Comment[fa]=Øجم دیسک سخت سوارشده +Comment[fi]=Liitetty kiintoleyosio +Comment[fr]=Partition de disque dur montée +Comment[fy]=Oankeppele Fêsteskiif folume +Comment[ga]=Imleabhar Feistithe Diosca Crua +Comment[gl]=Partición do Disco Duro Montada +Comment[he]=מחיצת ×›×•× ×Ÿ קשיח מחוברת +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ हारà¥à¤¡ डिसà¥à¤• वॉलà¥à¤¯à¥‚म +Comment[hr]=Pristupljeni volumen tvrdog diska +Comment[hu]=Csatlakoztatott merevlemezes partÃció +Comment[is]=Tengd disksneið +Comment[it]=Volume disco rigido montato +Comment[ja]=マウントã•ã‚ŒãŸãƒãƒ¼ãƒ‰ãƒ‡ã‚£ã‚¹ã‚¯ãƒœãƒªãƒ¥ãƒ¼ãƒ +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული ხისტი დისკის ტáƒáƒ›áƒ˜ +Comment[kk]=Тіркеген қатқыл диÑк томы +Comment[km]=ទំហំ​ážáž¶ážŸâ€‹ážšáž¹áž„​ដែល​បាន​រៀបចំ +Comment[lt]=Sumontuotas kieto disko skirsnis +Comment[lv]=PiemontÄ“ts cietais disks +Comment[mk]=Монтирана партиција на тврд диÑк +Comment[ms]=Volum Cakera Keras Terlekap +Comment[mt]=Partizzjoni ta' ħard-disk immuntata +Comment[nb]=Montert harddiskpartisjon +Comment[nds]=Inhangt Fastplaat-Partitschoon +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ हारà¥à¤¡ डिसà¥à¤• à¤à¥‹à¤²à¥à¤¯à¥à¤® +Comment[nl]=Aangekoppelde hardeschijfpartitie +Comment[nn]=Montert harddiskvolum +Comment[pa]=ਮਾਊਟ ਕੀਤਾ ਹਾਰਡ ਡਿਸਕ à¨à¨¾à¨— +Comment[pl]=Zamontowana partycja dysku twardego +Comment[pt]=Volume de disco rÃgido montado +Comment[pt_BR]=Volume do HD Montado +Comment[ro]=Volum de hard disc montat +Comment[ru]=Смонтированный раздел жёÑткого диÑка +Comment[rw]=Ububiko Disiki Bwashyizwemo +Comment[se]=ÄŒatnon garraskearrooassi +Comment[sk]=Pripojený oddiel pevného disku +Comment[sl]=Priklopljen pogon trdega diska +Comment[sr]=Монтирана партиција хард диÑка +Comment[sr@Latn]=Montirana particija hard diska +Comment[sv]=Monterad hÃ¥rdiskpartition +Comment[ta]=à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ வனà¯à®¤à®•à®Ÿà¯ வடà¯à®Ÿà¯ பிரிவ௠+Comment[th]=ฮาร์ดดิสà¸à¹Œà¸—ี่เม้านท์à¹à¸¥à¹‰à¸§ +Comment[tr]=BaÄŸlanmış Sabit Disk Bölümü +Comment[tt]=TotaÅŸqan Qatı Disk Töpläme +Comment[uk]=Змонтований розділ жорÑткого диÑку +Comment[uz]=Qattiq diskning ulangan qismi +Comment[uz@cyrillic]=Қаттиқ диÑкнинг уланган қиÑми +Comment[vi]=Các Phân vùng á»” cứng đã kết nối +Comment[wa]=Volume del deure plake monté +Comment[zh_CN]=æŒ‚è½½çš„ç¡¬ç›˜å· +Comment[zh_TW]=å·²æŽ›è¼‰çš„ç¡¬ç¢Ÿåˆ†å‰²å€ +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/hdd_unmounted.desktop b/kioslave/media/mimetypes/hdd_unmounted.desktop new file mode 100644 index 000000000..fe9fc4f88 --- /dev/null +++ b/kioslave/media/mimetypes/hdd_unmounted.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +Icon=hdd_unmount +Type=MimeType +MimeType=media/hdd_unmounted +Comment=Unmounted Hard Disk Volume +Comment[af]=Ontkoppelde Hard Skyf Partisie +Comment[ar]=تجزئة قرص صلب غير Ù…Øمّلة +Comment[az]=Ayrılmış Sabit Disk BölmÉ™si +Comment[be]=Ðдмацаваны раздзел жорÑткага дыÑка +Comment[bg]=Демонтиран дÑл на твърд диÑк +Comment[bn]=আনমাউনà§à¦Ÿ করা হারà§à¦¡ ডিসà§à¦• à¦à¦²à¦¿à¦‰à¦® +Comment[br]=Pladenn divarc'het +Comment[bs]=Demontirana hard disk particija +Comment[ca]=Disc dur desmuntat +Comment[cs]=Odpojený oddÃl pevného disku +Comment[csb]=Ã’dmòntowónô particëjô cwiardi platë +Comment[da]=Afmonteret harddisk-volumen +Comment[de]=Nicht eingebundene Festplattenpartition +Comment[el]=ΑποπÏοσαÏτημÎνη κατάτμηση σκληÏÎ¿Ï Î´Î¯ÏƒÎºÎ¿Ï… +Comment[eo]=Demetita subdisko +Comment[es]=Volumen de disco duro desmontado +Comment[et]=Lahutatud kõvaketta partitsioon +Comment[eu]=Diska gogorraren zatiketa desmuntatua +Comment[fa]=Øجم دیسک سخت پیاده‌شده +Comment[fi]=Irrotettu kiintolevyosio +Comment[fr]=Partition de disque dur non montée +Comment[fy]=Ofkeppele Fêsteskiif folume +Comment[ga]=Imleabhar Diosca Crua Gan Fheistiú +Comment[gl]=Partición do Disco Duro Non Montada +Comment[he]=מחיצת ×›×•× ×Ÿ קשיח ×ž× ×•×ª×§×ª +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ हारà¥à¤¡ डिसà¥à¤• वॉलà¥à¤¯à¥‚म +Comment[hr]=Nepristupljeni volumen tvrdog diska +Comment[hu]=Leválasztott merevlemezes partÃció +Comment[is]=Aftengd disksneið +Comment[it]=Volume disco rigido non montato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ãƒãƒ¼ãƒ‰ãƒ‡ã‚£ã‚¹ã‚¯ãƒœãƒªãƒ¥ãƒ¼ãƒ +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული ხისტი დისკის ტáƒáƒ›áƒ˜ +Comment[kk]=Тіркеуден шығарған қатқыл диÑк томы +Comment[km]=ទំហំ​ážáž¶ážŸâ€‹ážšáž¹áž„​ដែល​មិន​បាន​រៀបចំ +Comment[lt]=IÅ¡montuotas kieto disko skirsnis +Comment[lv]=NomontÄ“ts cietais disks +Comment[mk]=Одмонтирана партиција на тврд диÑк +Comment[ms]=Volum Cakera Keras Nyahlekap +Comment[mt]=Partizzjoni ta' ħard-disk mhux immuntata +Comment[nb]=Avmontert harddiskpartisjon +Comment[nds]=Afhangt Fastplaat-Partitschoon +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ हारà¥à¤¡ डिसà¥à¤• à¤à¥‹à¤²à¥à¤¯à¥à¤® +Comment[nl]=Afgekoppelde hardeschijfpartitie +Comment[nn]=Umontert harddiskvolum +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ ਹਾਰਡ ਡਿਸਕ à¨à¨¾à¨— +Comment[pl]=Odmontowana partycja dysku twardego +Comment[pt]=Volume de disco rÃgido desmontado +Comment[pt_BR]=Volume do HD Desmontado +Comment[ro]=Volum de hard disc nemontat +Comment[ru]=Отмонтированный раздел жёÑткого диÑка +Comment[rw]=Ububiko Disiki Bwakuwemo +Comment[se]=Gálgajuvvon garraskearrooassi +Comment[sk]=Odpojený oddiel pevného disku +Comment[sl]=Odklopljen pogon trdega diska +Comment[sr]=Демонтирана партиција хард диÑка +Comment[sr@Latn]=Demontirana particija hard diska +Comment[sv]=Avmonterad hÃ¥rdiskpartition +Comment[ta]=வெளியேறà¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ வனà¯à®µà®Ÿà¯à®Ÿà¯ பகà¯à®¤à®¿ +Comment[th]=ฮาร์ดดิสà¸à¹Œà¸—ี่ไม่ได้เม้านท์ +Comment[tr]=Ayrılmış Sabit Disk Bölümü +Comment[tt]=TotaÅŸmaÄŸan Qatı Disk Töpläme +Comment[uk]=Демонтований розділ жорÑткого диÑку +Comment[uz]=Qattiq diskning ulanmagan qismi +Comment[uz@cyrillic]=Қаттиқ диÑкнинг уланмаган қиÑми +Comment[vi]=Các Phân vùng á»” cứng đã gỡ ra +Comment[wa]=Volume del deure plake dismonté +Comment[zh_CN]=æœªæŒ‚è½½çš„ç¡¬ç›˜å· +Comment[zh_TW]=æœªæŽ›è¼‰çš„ç¡¬ç¢Ÿåˆ†å‰²å€ +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/nfs_mounted.desktop b/kioslave/media/mimetypes/nfs_mounted.desktop new file mode 100644 index 000000000..ea64bdca6 --- /dev/null +++ b/kioslave/media/mimetypes/nfs_mounted.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +Icon=nfs_mount +Type=MimeType +MimeType=media/nfs_mounted +Comment=Mounted NFS Share +Comment[af]=Gekoppelde NFS Hulpbron +Comment[ar]=مشاركة NFS مركبة +Comment[az]=BaÄŸlanmış NFS SahÉ™si +Comment[be]=ÐŸÑ€Ñ‹Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð°Ð³ÑƒÐ»ÑŒÐ½Ð°Ñ Ñ‚Ñчка NFS +Comment[bg]=Монтиран NFS реÑÑƒÑ€Ñ +Comment[bn]=মাউনà§à¦Ÿ করা à¦à¦¨-à¦à¦«-à¦à¦¸ শেয়ার +Comment[br]=Rennad NFS marc'het +Comment[bs]=Montiran NFS resurs +Comment[ca]=Recurs NFS muntat +Comment[cs]=PÅ™ipojený zdroj NFS +Comment[csb]=Zamòntowónô systema lopków NFS +Comment[da]=Monteret NFS-share +Comment[de]=Eingebundene NFS-Freigabe +Comment[el]=Î ÏοσαÏτημÎνος πόÏος NFS +Comment[eo]=Surmetita NFS-opuzaĵo +Comment[es]=Recurso NFS montado +Comment[et]=Ãœhendatud NFS ressurss +Comment[eu]=NFS baliabide muntatua +Comment[fa]=مشترک NFS سوارشده +Comment[fi]=Liitetty NFS-jako +Comment[fr]=Ressource NFS montée +Comment[fy]=Oankeppele NFS-boarne +Comment[ga]=Comhroinn Fheistithe NFS +Comment[gl]=Compartición NFS Montada +Comment[he]=מש×ב NFS מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ à¤à¤¨à¤à¤«à¤¼à¤à¤¸ साà¤à¤¾ +Comment[hr]=Pristupljeno NFS dijeljenje +Comment[hu]=Csatlakoztatott NFS-megosztás +Comment[is]=Tengd NFS auðlind +Comment[it]=Risorsa NFS montata +Comment[ja]=マウントã•ã‚ŒãŸ NFS 共有 +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული NFS რესურსი +Comment[kk]=Тіркеген NFS реÑурÑÑ‹ +Comment[km]=NFS Share បាន​រៀបចំ +Comment[lt]=Sumontuotas NFS bendro naudojimo resursas +Comment[lv]=PiemontÄ“ta NFS Å¡Äre +Comment[mk]=Монтиран NFS-реÑÑƒÑ€Ñ +Comment[ms]=Perkongsian NFS Terlekap +Comment[mt]=Riżors NFS immuntat +Comment[nb]=Montert NFS-ressurs +Comment[nds]=Inhangt NFS-Freegaav +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ NFS साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Comment[nl]=Aangekoppelde NFS-gegevensbron +Comment[nn]=Montert delt NFS-ressurs +Comment[pa]=ਮਾਊਟ ਕੀਤੀ NFS ਸਾਂਠ+Comment[pl]=Zamontowany system plików NFS +Comment[pt]=Partilha de NFS montada +Comment[pt_BR]=Volume NFS Montado +Comment[ro]=Partajare NFS montată +Comment[ru]=Смонтированный реÑÑƒÑ€Ñ NFS +Comment[rw]=Umugabane NFS Washyizwemo +Comment[se]=ÄŒatnon NFS-resursa +Comment[sk]=Pripojený zdroj NFS +Comment[sl]=Priklopljen vir NFS +Comment[sr]=Монтирано NFS дељење +Comment[sr@Latn]=Montirano NFS deljenje +Comment[sv]=Monterad NFS-resurs +Comment[ta]=à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ NFS பஙà¯à®•à¯ +Comment[th]=ทรัพยาà¸à¸£ NFS ที่ถูà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=BaÄŸlı NFS Paylaşımı +Comment[tt]=TotaÅŸqan NFS Urtağı +Comment[uk]=Змонтований реÑÑƒÑ€Ñ NFS +Comment[uz]=Ulangan NFS manba +Comment[uz@cyrillic]=Уланган NFS манба +Comment[vi]=Chia sẻ NFS đã kết nối +Comment[wa]=PÃ¥rtaedje NFS monté +Comment[zh_CN]=挂载的 NFS 共享 +Comment[zh_TW]=已掛載的 NFS è³‡æº +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/nfs_unmounted.desktop b/kioslave/media/mimetypes/nfs_unmounted.desktop new file mode 100644 index 000000000..d6ca31a68 --- /dev/null +++ b/kioslave/media/mimetypes/nfs_unmounted.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +Icon=nfs_unmount +Type=MimeType +MimeType=media/nfs_unmounted +Comment=Unmounted NFS Share +Comment[af]=Ontkoppelde NFS Hulpbron +Comment[ar]=مشاركة NFS غير مركبة +Comment[az]=Ayrılmış NFS SahÉ™si +Comment[be]=ÐÐ´Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð°Ð³ÑƒÐ»ÑŒÐ½Ð°Ñ Ñ‚Ñчка NFS +Comment[bg]=Демонтиран NFS реÑÑƒÑ€Ñ +Comment[bn]=আনমাউনà§à¦Ÿ করা à¦à¦¨-à¦à¦«-à¦à¦¸ শেয়ার +Comment[br]=Rennad NFS divarc'het +Comment[bs]=Demontiran NFS resurs +Comment[ca]=Recurs NFS desmuntat +Comment[cs]=Odpojený zdroj NFS +Comment[csb]=Ã’dmòntowónô systema lopków NFS +Comment[da]=Afmonteret NFS-share +Comment[de]=Nicht eingebundene NFS-Freigabe +Comment[el]=ΑποπÏοσαÏτημÎνος πόÏος NFS +Comment[eo]=Demetita NFS-opuzaĵo +Comment[es]=Recurso NFS desmontado +Comment[et]=Lahutatud NFS ressurss +Comment[eu]=NFS baliabide desmuntatua +Comment[fa]=مشترک NFS پیاده‌شده +Comment[fi]=Irrotettu NFS-jako +Comment[fr]=Ressource NFS non montée +Comment[fy]=Oankeppele NFS-boarne +Comment[ga]=Comhroinn NFS Gan Fheistiú +Comment[gl]=Compartición NFS non Montada +Comment[he]=מש×ב NFS ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ à¤à¤¨à¤à¤«à¤¼à¤à¤¸ साà¤à¤¾ +Comment[hr]=Nepristupljeno NFS dijeljenje +Comment[hu]=Leválasztott NFS-megosztás +Comment[is]=Aftengd NFS auðlind +Comment[it]=Risorsa NFS non montata +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ NFS 共有 +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული NFS რესურსი +Comment[kk]=Тіркеуден шығарған NFS реÑурÑÑ‹ +Comment[km]=NFS Share មិន​បាន​រៀបចំ +Comment[lt]=IÅ¡montuotas NFS bendro naudojimo resursas +Comment[lv]=NomontÄ“ta NFS Å¡Äre +Comment[mk]=Одмонтиран NFS-реÑÑƒÑ€Ñ +Comment[ms]=Perkongsian NFS Nyahlekap +Comment[mt]=Riżors NFS mhux immuntat +Comment[nb]=Avmontert NFS-ressurs +Comment[nds]=Afhangt NFS-Freegaav +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ NFS साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Comment[nl]=Afgekoppelde NFS-gegevensbron +Comment[nn]=Umontert delt NFS-ressurs +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ NFS ਸਾਂਠ+Comment[pl]=Odmontowany system plików NFS +Comment[pt]=Partilha de NFS desmontada +Comment[pt_BR]=Volume NFS Desmontado +Comment[ro]=Partajare NFS nemontată +Comment[ru]=Отмонтированный реÑÑƒÑ€Ñ NFS +Comment[rw]=Umugabane NFS Wakuwemo +Comment[se]=Gálgajuvvon NFS-resursa +Comment[sk]=Odpojený zdroj NFS +Comment[sl]=Odklopljen vir NFS +Comment[sr]=Демонтирано NFS дељење +Comment[sr@Latn]=Demontirano NFS deljenje +Comment[sv]=Avmonterad NFS-resurs +Comment[ta]=இறகà¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿ NFS பஙà¯à®•à¯ +Comment[th]=ทรัพยาà¸à¸£ NFS ที่ไม่ถูà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=BaÄŸlanmamış NFS Paylaşımı +Comment[tt]=TotaÅŸmaÄŸan NFS Urtağı +Comment[uk]=Демонтований реÑÑƒÑ€Ñ NFS +Comment[uz]=Ulanmagan NFS manba +Comment[uz@cyrillic]=Уланмаган NFS манба +Comment[vi]=Chia sẻ NFS đã gỡ ra +Comment[wa]=PÃ¥rtaedje NFS dismonté +Comment[zh_CN]=未挂载的 NFS 共享 +Comment[zh_TW]=未掛載的 NFS è³‡æº +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/removable_mounted.desktop b/kioslave/media/mimetypes/removable_mounted.desktop new file mode 100644 index 000000000..83fc34e41 --- /dev/null +++ b/kioslave/media/mimetypes/removable_mounted.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +Icon=usbpendrive_mount +Type=MimeType +MimeType=media/removable_mounted +Comment=Mounted Removable Medium +Comment[af]=Gekoppelde Verwyderbare Medium +Comment[ar]=وسيط قابل للإزالة مركب +Comment[az]=BaÄŸlanmış Çıxarıla BilÉ™n Mediyum +Comment[be]=Прымацаваны зменны ноÑьбіт +Comment[bg]=Монтиран преноÑим ноÑител +Comment[bn]=মাউনà§à¦Ÿ করা অপসারণযোগà§à¦¯ মিডিয়াম +Comment[br]=Medium lem-laka marc'het +Comment[bs]=Montiran izmjenjivi ureÄ‘aj +Comment[ca]=Suport extraïble muntat +Comment[cs]=PÅ™ipojené výmÄ›nné médium +Comment[csb]=Zamòntowóné òdÅ‚Ä…czalné medium +Comment[da]=Monteret medie der kan fjernes +Comment[de]=Eingebundenes Wechsellaufwerk +Comment[el]=Î ÏοσαÏτημÎνος αφαιÏοÏμενο μÎσο +Comment[eo]=Surmetita forigebla datumportilo +Comment[es]=Recurso extraÃble montado +Comment[et]=Ãœhendatud eemaldatav andmekandja +Comment[eu]=Euskarri aldagarria muntatua +Comment[fa]=رسانۀ برداشتنی سوارشده +Comment[fi]=Liitetty irrotettava resurssi +Comment[fr]=Média amovible monté +Comment[fy]=Oankeppele ferwiderber medium +Comment[ga]=Meán Feistithe Inbhainte +Comment[gl]=Dispositivo ExtraÃbel Montado +Comment[he]=מדיה ×—×™×¦×•× ×™×ª מחוברת +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ रीमूवेबल माधà¥à¤¯à¤® +Comment[hr]=Pristupljen izmjenjiv medij +Comment[hu]=Csatlakoztatott adathordozó +Comment[is]=Tengdur útskiptanlegur miðill +Comment[it]=Supporto rimovibile montato +Comment[ja]=マウントã•ã‚ŒãŸãƒªãƒ ーãƒãƒ–ルメディア +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული პáƒáƒ ტáƒáƒ¢áƒ˜áƒ£áƒšáƒ˜ მáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘რ+Comment[kk]=Тіркеген ауыÑтырмалы таÑушы +Comment[km]=ឧបករណáŸâ€‹áž…áž›áŸážâ€‹ážŠáŸ‚ល​បាន​រៀបចំ +Comment[lt]=Sumontuotas paÅ¡alinamas diskas +Comment[lv]=PiemontÄ“ts noņemamais datu nesÄ“js +Comment[mk]=Монтиран отÑтранлив ноÑач +Comment[ms]=Medium Boleh Buang Terlekap +Comment[mt]=Riżors NFS immuntat +Comment[nb]=Montert flyttbart medium +Comment[nds]=Inhangt tuuschbor Loopwark +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ हटाउन सकिने मेडिअम +Comment[nl]=Aangekoppeld verwijderbaar medium +Comment[nn]=Montert flyttbart medium +Comment[pa]=ਮਾਊਟ ਕੀਤੇ ਹਟਾਉਣਯੋਗ ਮਾਧਿਅਮ +Comment[pl]=Zamontowany noÅ›nik wymienny +Comment[pt]=Dispositivo amovÃvel montado +Comment[pt_BR]=MÃdia RemovÃvel Montada +Comment[ro]=Mediu amovibil montat +Comment[ru]=Смонтированный внешний диÑк +Comment[rw]=Igitangazamakuru Kivanwamo Cyashyizwemo +Comment[se]=Čátnon sirdehahtti medium +Comment[sk]=Pripojené vyberateľné médium +Comment[sl]=Priklopljen odstranljiv medij +Comment[sr]=Монтиран уклоњиви медијум +Comment[sr@Latn]=Montiran uklonjivi medijum +Comment[sv]=Monterad flyttbar enhet +Comment[ta]=à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿ நீகà¯à®•à®•à¯à®•à¯‚டிய மீடியம௠+Comment[th]=สื่à¸à¸šà¸±à¸™à¸—ึà¸à¹à¸šà¸šà¸–à¸à¸”à¸à¸à¸à¹„ด้ถูà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=BaÄŸlı Ayrılabilir Ortam +Comment[tt]=TotaÅŸqan Çığarulı Media +Comment[uk]=Змонтований переноÑний ноÑій +Comment[uz]=Ulangan saqlash uskunasi +Comment[uz@cyrillic]=Уланган Ñақлаш уÑкунаÑи +Comment[vi]=á»” lÆ°u trữ Di Ä‘á»™ng đã kết nối +Comment[wa]=OistÃ¥ve mediom monté +Comment[zh_CN]=挂载的å¯ç§»åŠ¨ä»‹è´¨ +Comment[zh_TW]=已掛載的å¯æ”œå¼åª’é«” +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/removable_unmounted.desktop b/kioslave/media/mimetypes/removable_unmounted.desktop new file mode 100644 index 000000000..6a8e3fada --- /dev/null +++ b/kioslave/media/mimetypes/removable_unmounted.desktop @@ -0,0 +1,77 @@ +[Desktop Entry] +Icon=usbpendrive_unmount +Type=MimeType +MimeType=media/removable_unmounted +Comment=Unmounted Removable Medium +Comment[af]=Ontkoppel Verwyderbare Medium +Comment[ar]=وسيط قابل للإزالة غير مركب +Comment[az]=Ayrılmış Çıxarıla BilÉ™n Mediyum +Comment[be]=Ðдмацаваны зменны ноÑьбіт +Comment[bg]=Демонтиран преноÑим ноÑител +Comment[bn]=আনমাউনà§à¦Ÿ করা অপসারণযোগà§à¦¯ মিডিয়াম +Comment[br]=Medium ar skoroù lem/laka divarc'het +Comment[bs]=Demontiran izmjenjivi ureÄ‘aj +Comment[ca]=Suport extraïble desmuntat +Comment[cs]=Odpojené výmÄ›nné médium +Comment[csb]=Ã’dmòntowóné òdÅ‚Ä…czalné medium +Comment[da]=Afmonteret medie der kan fjernes +Comment[de]=Nicht eingebundenes Wechsellaufwerk +Comment[el]=ΑποπÏοσαÏτημÎνο αφαιÏοÏμενο μÎσο +Comment[eo]=Demetita forigebla datumportilo +Comment[es]=Recurso extraÃble desmontado +Comment[et]=Lahutatud eemaldatav andmekandja +Comment[eu]=Euskarri aldagarria desmuntatua +Comment[fa]=رسانۀ برداشتنی پیاده‌شده +Comment[fi]=Irrotettu irrotettava resurssi +Comment[fr]=Média amovible non monté +Comment[fy]=Ofkeppele ferwiderber medium +Comment[ga]=Meán Inbhainte Gan Fheistiú +Comment[gl]=Dispositivo ExtraÃbel Non Montado +Comment[he]=מדייה ×—×™×¦×•× ×™×ª ×ž× ×•×ª×§×ª +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ रीमूवेबल माधà¥à¤¯à¤® +Comment[hr]=Nepristupljen izmjenjiv medij +Comment[hu]=Leválasztott adathordozó +Comment[is]=Aftengdur útskiptanlegur miðill +Comment[it]=Supporto rimovibile non montato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„リムーãƒãƒ–ルメディア +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული პáƒáƒ ტáƒáƒ¢áƒ˜áƒ£áƒšáƒ˜ მáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘რ+Comment[kk]=Тіркеуден шығарған ауыÑтырмалы таÑушы +Comment[km]=ឧបករណáŸâ€‹áž…áž›áŸážâ€‹ážŠáŸ‚ល​មិន​បាន​រៀបចំ +Comment[lt]=IÅ¡montuotas paÅ¡alinamas diskas +Comment[lv]=NomontÄ“ts noņemamais datu nesÄ“js +Comment[mk]=Одмонтиран отÑтранлив ноÑач +Comment[ms]=Medium Boleh Buang Nyahlekap +Comment[mt]=Riżors għall-ħażna tad-data mhux immuntat +Comment[nb]=Avmontert flyttbart medium +Comment[nds]=Afhangt tuuschbor Loopwark +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ हटाउन सकिने मेडिअम +Comment[nl]=Afgekoppeld verwijderbaar medium +Comment[nn]=Umontert flyttbart medium +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ ਹਟਾਉਣਯੋਗ ਮਾਧਿਅਮ +Comment[pl]=Odmontowany noÅ›nik wymienny +Comment[pt]=Dispositivo amovÃvel desmontado +Comment[pt_BR]=MÃdia RemovÃvel Desmontada +Comment[ro]=Mediu amovibil nemontat +Comment[ru]=Отмонтированный внешний диÑк +Comment[rw]=Igitangazamakuru Kivanwamo Cyavanwemo +Comment[se]=Gálgaduvvon sirdehahtti medium +Comment[sk]=Odpojené vyberateľné médium +Comment[sl]=Odklopljen odstranljiv medij +Comment[sr]=Демонтиран уклоњиви медијум +Comment[sr@Latn]=Demontiran uklonjivi medijum +Comment[sv]=Avmonterad flyttbar enhet +Comment[ta]=à®à®±à¯à®±à®¾à®¤ நீகà¯à®•à®•à¯à®•à¯‚டிய சாதனம௠+Comment[th]=สื่à¸à¸šà¸±à¸™à¸—ึà¸à¹à¸šà¸šà¸–à¸à¸”à¸à¸à¸à¹„ด้ไม่ได้เเม้านท์ +Comment[tr]=BaÄŸlanmamış Ayrılabilir Ortam +Comment[tt]=TotaÅŸmaÄŸan Çığarulı Medium +Comment[uk]=Демонтований переноÑний ноÑій +Comment[uz]=Ulanmagan saqlash uskunasi +Comment[uz@cyrillic]=Уланмаган Ñақлаш уÑкунаÑи +Comment[vi]=á»” lÆ°u trữ Di Ä‘á»™ng đã gỡ ra +Comment[wa]=OistÃ¥ve mediom dismonté +Comment[zh_CN]=未挂载的å¯ç§»åŠ¨ä»‹è´¨ +Comment[zh_TW]=未掛載的å¯æ”œå¼åª’é«” +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/smb_mounted.desktop b/kioslave/media/mimetypes/smb_mounted.desktop new file mode 100644 index 000000000..6f5bb0bff --- /dev/null +++ b/kioslave/media/mimetypes/smb_mounted.desktop @@ -0,0 +1,83 @@ +[Desktop Entry] +Icon=nfs_mount +Type=MimeType +MimeType=media/smb_mounted +Comment=Mounted Samba (Microsoft Network) Share +Comment[af]=Gekoppelde Samba (Microsoft Netwerk) Hulpbron +Comment[ar]=مشاركة Samba Ù…Øمّلة +Comment[az]=BaÄŸlanmış Samba (Microsoft ŞəbÉ™kÉ™si) SahÉ™si +Comment[be]=ÐŸÑ€Ñ‹Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð°Ð³ÑƒÐ»ÑŒÐ½Ð°Ñ Ñ‚Ñчка Samba (Сетка Microsoft) +Comment[bg]=Монтиран реÑÑƒÑ€Ñ Samba (Microsoft Network) +Comment[bn]=মাউনà§à¦Ÿ করা সামà§à¦¬à¦¾ (মাইকà§à¦°à§‹à¦¸à¦«à¦Ÿ নেটওয়ারà§à¦•) শেয়ার +Comment[br]=Rennad Samba (rouedad Microsoft) marc'het +Comment[bs]=Montiran Samba (Microsoft Network) Share +Comment[ca]=Recurs de Samba (xarxa Microsoft) muntat +Comment[cs]=PÅ™ipojený Samba (Microsoft Network) prostÅ™edek +Comment[csb]=Zamòntowóné ùdzélenié Sambë (Microsoft Network) +Comment[cy]=Cydranniad Samba (Rhwydwaith Microsoft) wedi'i osod +Comment[da]=Monteret Samba-share (Microsoft-netværk) +Comment[de]=Eingebundene Samba-Freigabe (Microsoft-Netzwerk) +Comment[el]=Î ÏοσαÏτημÎνος κοινόχÏηστος πόÏος Samba (Δίκτυο Microsoft) +Comment[eo]=Surmetita Sambo-opuzaĵo +Comment[es]=Recurso de Samba montado (Red de Microsoft) +Comment[et]=Ãœhendatud Samba (Microsoft Network) ressurss +Comment[eu]=Samba (Microsoft sarea) partekaketa muntatua +Comment[fa]=مشترک Samba (Microsoft Network) سوار‌شده +Comment[fi]=Liitetty Samba-jako (Microsoft-verkko) +Comment[fr]=Partage Samba (réseau Microsoft) monté +Comment[fy]=Oankeppele Samba-netwurkboarne (Microsoft-netwurken) +Comment[ga]=Comhroinn Fheistithe Samba (Microsoft Network) +Comment[gl]=Compartición Samba Montada (Rede de Microsoft) +Comment[he]=שיתוף Microsoft Network) Samba) מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ सामà¥à¤¬à¤¾ (माइकà¥à¤°à¥‹à¤¸à¤¾à¤«à¥à¤Ÿ नेटवरà¥à¤•) साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Comment[hr]=Pristupljeno Samba dijeljenje (Microsoft mreža) +Comment[hsb]=Montowany Samba-(Microsoft syć)-zapisk +Comment[hu]=Csatlakoztatott Samba-megosztás +Comment[is]=Tengd Samba (Microsoft Network) sameign +Comment[it]=Condivisione samba (Rete Microsoft) montata +Comment[ja]=マウントã•ã‚ŒãŸ Samba (マイクãƒã‚½ãƒ•ãƒˆã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯) 共有 +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული Samba რესურსი (Microsoft-ის ქსელი) +Comment[kk]=Тіркеген Samba реÑурÑÑ‹ (Microsoft желіÑÑ–) +Comment[km]=Samba (បណ្ដាញ Microsoft) Share បាន​រៀបចំ +Comment[lt]=Sumontuotas Samba (Microsoft Network) bendro naudojimo diskas +Comment[lv]=PiemontÄ“ta Samba (Microsoft tÄ«kla) Å¡Äre +Comment[mk]=Монтиран Samba заеднички реÑÑƒÑ€Ñ (Microsoft мрежа) +Comment[mn]=ЗалгагдÑан Samba-Ðөөцүүд (Microsoft-СүлжÑÑ) +Comment[ms]=Perkongsian Samba Terlekap (Rangkaian Microsoft) +Comment[mt]=Riżors Samba (Microsoft Network) immuntat +Comment[nb]=Montert Samba-katalog (Microsoft nettverk) +Comment[nds]=Inhangt Samba-Freegaav (Microsoft-Nettwark) +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ सामà¥à¤¬à¤¾ (माइकà¥à¤°à¥‹ सफà¥à¤Ÿ सञà¥à¤œà¤¾à¤²) साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Comment[nl]=Aangekoppelde Samba-netwerkbron (Microsoft-netwerken) +Comment[nn]=Montert Samba-ressurs (Microsoft-nettverk) +Comment[nso]=Kabagano yeo e Nameleditswego ya Samba (Kgokagano ya Microsoft) +Comment[pa]=ਮਾਊਟ ਕੀਤੀ ਸਾਂਬਾ (ਮਾਈਕਰੋਸਾਫਟ ਨੈੱਟਵਰਕ) ਸਾਂਠ+Comment[pl]=Zamontowany udziaÅ‚ Samby (sieci typu Microsoft Network) +Comment[pt]=Partilha de Samba (Microsoft Network) montada +Comment[pt_BR]=Compartilhamento do Samba (Rede Microsoft) montado +Comment[ro]=Partajare Samba (reÈ›ea Microsoft) montată +Comment[ru]=Смонтированный реÑÑƒÑ€Ñ Ñети Microsoft +Comment[rw]=Umugabane Samba (Urusobemiyoboro Microsoft) Washyizwemo +Comment[se]=ÄŒatnojuvvon Samba-resursa (Microsoft-fierbmi) +Comment[sk]=Pripojený disk Samba (Microsoft Network) +Comment[sl]=Priklopljen deljeni vir Sambe (Microsoftovo omrežje) +Comment[sr]=Монтирано Samba дељење (Microsoft-ова мрежа) +Comment[sr@Latn]=Montirano Samba deljenje (Microsoft-ova mreža) +Comment[sv]=Monterad utdelad Samba-katalog (Microsoft-nätverk) +Comment[ta]=சமà¯à®ªà®¾ à®à®±à¯à®±à®ªà¯à®ªà®Ÿà¯à®Ÿà®¤à¯ (மைகà¯à®°à¯‡à®šà®¾à®ªà¯à®Ÿà¯ வலை) பகிரà¯à®µà¯ +Comment[tg]=Монтажшудаи манбаъи Samba (шабакаи Microsoft) +Comment[th]=ทรัพยาà¸à¸£ Samba (เครืà¸à¸‚่ายวินโดว์ส) ที่ถูà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=BaÄŸlanmış Samba (Microsoft Network) Paylaşımı +Comment[tt]=TotaÅŸqan Samba (Microsoft Çeltäre) Urtağı +Comment[uk]=Змонтований Ñпільний реÑÑƒÑ€Ñ Samba (мережа Microsoft) +Comment[ven]=Samba yo gonyaho (Vhukwamani ha Microsoft) U kovhekana +Comment[vi]=Chia sẻ SAMBA (mạng của Microsoft) đã kết nối +Comment[wa]=PÃ¥rtaedje Samba (Rantoele Microsoft) monté +Comment[xh]=Samba Enyusiweyo (Umsebenzi womnatha we Microsoft) Ulwahlulo +Comment[zh_CN]=挂载的 Samba (Microsoft 网络)共享 +Comment[zh_TW]=已掛載的 Samba (Microsoft 網路) 分享 +Comment[zu]=Isabelo se-Samba esinyusiwe (Uxhumano olusakazekile le-Network) +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/smb_unmounted.desktop b/kioslave/media/mimetypes/smb_unmounted.desktop new file mode 100644 index 000000000..b59f063b2 --- /dev/null +++ b/kioslave/media/mimetypes/smb_unmounted.desktop @@ -0,0 +1,84 @@ +[Desktop Entry] +Icon=nfs_unmount +Type=MimeType +MimeType=media/smb_unmounted +Comment=Unmounted Samba (Microsoft Network) Share +Comment[af]=Ontkoppelde Samba (Microsoft Netwerk) Hulpbron +Comment[ar]=مشاركة Samba غير Ù…Øمّلة +Comment[az]=Ayrılmış Samba (Microsoft ŞəbÉ™kÉ™si) SahÉ™si +Comment[be]=ÐÐ´Ð¼Ð°Ñ†Ð°Ð²Ð°Ð½Ð°Ñ Ð°Ð³ÑƒÐ»ÑŒÐ½Ð°Ñ Ñ‚Ñчка Samba (Сетка Microsoft) +Comment[bg]=Демонтиран реÑÑƒÑ€Ñ Samba (Microsoft Network) +Comment[bn]=আনমাউনà§à¦Ÿ করা সামà§à¦¬à¦¾ (মাইকà§à¦°à§‹à¦¸à¦«à¦Ÿ নেটওয়ারà§à¦•) শেয়ার +Comment[br]=Rennad Samba (rouedad Microsoft) divarc'het +Comment[bs]=Demontiran Samba (Microsoft Network) Share +Comment[ca]=Recurs de Samba (xarxa Microsoft) desmuntat +Comment[cs]=Odpojený Samba (Microsoft Network) prostÅ™edek +Comment[csb]=Ã’dmòntowóné ùdzélenié Sambë (Microsoft Network) +Comment[cy]=Cydranniad Samba (Rhwydwaith Microsoft) wedi'i ddadosod +Comment[da]=Afmonteret Samba-share (Microsoft-netværk) +Comment[de]=Nicht eingebundene Samba-Freigabe (Microsoft-Netzwerk) +Comment[el]=ΑποπÏοσαÏτημÎνος κοινόχÏηστος πόÏος Samba (Δίκτυο Microsoft) +Comment[eo]=Demetita Sambo-opuzaĵo +Comment[es]=Recurso de Samba desmontado (Red de Microsoft) +Comment[et]=Lahutatud Samba (Microsoft Network) ressurss +Comment[eu]=Samba (Microsoft sarea) partekaketa desmuntatua +Comment[fa]=مشترک Samba (Microsoft Network) پیاده‌شده +Comment[fi]=Irrotettu Samba-jako (Microsoft-verkko) +Comment[fr]=Partage Samba (réseau Microsoft) non monté +Comment[fy]=Ofkeppele Samba-netwurkboarne (Microsoft-netwurken) +Comment[ga]=Comhroinn Samba (Microsoft Network) Gan Fheistiú +Comment[gl]=Compartición Samba Non Montada (Rede de Microsoft) +Comment[he]=שיתוף Microsoft Network) Samba) ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ सामà¥à¤¬à¤¾ (माइकà¥à¤°à¥‹à¤¸à¤¾à¤«à¥à¤Ÿ नेटवरà¥à¤•) साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Comment[hr]=Nepristupljeno Samba dijeljenje (Microsoft mreža) +Comment[hsb]=Njemontowany Samba-(Microsoft syć)-zapisk +Comment[hu]=Leválasztott Samba-megosztás +Comment[is]=Aftengd Samba (Microsoft Network) sameign +Comment[it]=Condivisione samba (Rete Microsoft) non montata +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ Samba (マイクãƒã‚½ãƒ•ãƒˆã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯) 共有 +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული Samba რესურსი (Microsoft-ის ქსელი) +Comment[kk]=Тіркеуден шығарған Samba реÑурÑÑ‹ (Microsoft желіÑÑ–) +Comment[km]=Samba (បណ្ដាញ Microsoft) Share មិន​ទាន់​រៀបចំ +Comment[lo]= ຫັງàºàº²àº¥àºµ +Comment[lt]=IÅ¡montuotas Samba (Microsoft Network) bendro naudojimo diskas +Comment[lv]=NomontÄ“ta Samba (Microsoft tÄ«kla) Å¡Äre +Comment[mk]=Одмонтиран Samba заеднички реÑÑƒÑ€Ñ (Microsoft мрежа) +Comment[mn]=Залгагдаагүй Samba-Ðөөцүүд (Microsoft-СүлжÑÑ) +Comment[ms]=Perkongsian Samba Nyahlekap (Rangkaian Microsoft) +Comment[mt]=Riżors Samba (Microsoft Network) mhux immuntat +Comment[nb]=Avmontert Samba-katalog (Microsoft nettverk) +Comment[nds]=Afhangt Samba-Freegaav (Microsoft-Nettwark) +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ सामà¥à¤¬à¤¾ (माइकà¥à¤°à¥‹ सफà¥à¤Ÿ सञà¥à¤œà¤¾à¤²) साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Comment[nl]=Afgekoppelde Samba-netwerkbron (Microsoft-netwerkbron) +Comment[nn]=Avmontert Samba-ressurs (Microsoft-nettverk) +Comment[nso]=Kabagano yeo e Theositswego ya Samba (Kgokagano ya Microsoft) +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ ਸਾਂਬਾ (ਮਾਈਕਰੋਸਾਫਟ ਨੈੱਟਵਰਕ) ਸਾਂਠ+Comment[pl]=Odmontowany udziaÅ‚ Samby (sieci typu Microsoft Network) +Comment[pt]=Partilha de Samba (Microsoft Network) desmontada +Comment[pt_BR]=Compartilhamento do Samba (Rede Microsoft) desmontado +Comment[ro]=Partajare Samba (reÈ›ea Microsoft) nemontată +Comment[ru]=Отмонтированный реÑÑƒÑ€Ñ Ñети Microsoft +Comment[rw]=Umugabane Samba (Urusobemiyoboro Microsoft) Wakuwemo +Comment[se]=Gálgajuvvon Samba-resursa (Microsoft-fierbmi) +Comment[sk]=Nepripojený disk Samba (Microsoft Network) +Comment[sl]=Odklopljen deljeni vir Sambe (Microsoftovo omrežje) +Comment[sr]=Демонтирано Samba дељење (Microsoft-ова мрежа) +Comment[sr@Latn]=Demontirano Samba deljenje (Microsoft-ova mreža) +Comment[sv]=Avmonterad utdelad Samba-katalog (Microsoft-nätverk) +Comment[ta]=சமà¯à®ªà®¾à®µà¯ˆ வெளியேறà¯à®±à¯(மைகà¯à®°à¯‡à®šà®¾à®ªà¯à®Ÿà¯ வலை) பகிரà¯à®µà¯ +Comment[tg]=Ҷудо шудаи манбаъи Samba (шабакаи Microsoft) +Comment[th]=ทรัพยาà¸à¸£ Samba (เครืà¸à¸‚่ายวินโดว์ส) ที่ไม่ถูà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=BaÄŸlanmış Samba (Microsoft Network) Paylaşımı +Comment[tt]=TotaÅŸmaÄŸan Samba (Microsoft Çeltäre) Urtağı +Comment[uk]=Демонтований Ñпільний реÑÑƒÑ€Ñ Samba (мережа Microsoft) +Comment[ven]=Samba i songo gonyaho (Vhukamani ha Microsoft) U kovhekana +Comment[vi]=Chia sẻ SAMBA (mạng của Microsoft) đã gỡ ra +Comment[wa]=PÃ¥rtaedje Samba (Rantoele Microsoft) dismonté +Comment[xh]=Samba Enganyuswanga (Umsebenzi womnatha we Microsoft) Ulwahlulo +Comment[zh_CN]=未挂载的 Samba (Microsoft 网络)共享 +Comment[zh_TW]=未掛載的 Samba (Microsoft 網路) 分享 +Comment[zu]=Isabelo se-Samba esehlisiwe (Uxhumano olusakazekile lwe-Microsoft) +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/svcd.desktop b/kioslave/media/mimetypes/svcd.desktop new file mode 100644 index 000000000..68c965a6f --- /dev/null +++ b/kioslave/media/mimetypes/svcd.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/svcd +Comment=Super Video CD +Comment[ar]=قرص مدمج مرئي Super +Comment[bg]=Супер VCD диÑк +Comment[bn]=সà§à¦ªà¦¾à¦° à¦à¦¿à¦¡à¦¿à¦“ সিডি +Comment[ca]=CD Súper VÃdeo +Comment[da]=Super Video-cd +Comment[eo]=Supervidea lumdisko +Comment[fa]=دیسک Ùشردۀ ابرویدیویی +Comment[fr]=Super CD vidéo +Comment[fy]=Super Fideo-kompaktskiif +Comment[gl]=Super VÃdeo CD +Comment[hi]=सà¥à¤ªà¤° वीडियो सीडी +Comment[hu]=Super Video-CD +Comment[is]=Súper vÃdeó CD +Comment[it]=Super video CD +Comment[ja]=スーパービデオ CD +Comment[ka]=სუპერვიდერCD +Comment[km]=ស៊ីឌី​វីដáŸáž¢áž¼â€‹áž‚ុណភាព​ážáŸ’ពស់ +Comment[mk]=Супер видео ЦД +Comment[ms]=CD Supervideo +Comment[nb]=Super video CD +Comment[nds]=Super-Video-CD +Comment[ne]=सà¥à¤ªà¤° à¤à¤¿à¤¡à¤¿à¤¯à¥‹ सीडी +Comment[nl]=Super Video-cd +Comment[nn]=Super Video-CD +Comment[pa]=ਸà©à¨ªà¨° ਵੀਡਿਓ CD +Comment[pl]=PÅ‚yta Super Video CD +Comment[pt]=Super VÃdeo CD +Comment[pt_BR]=Super vÃdeo CD (SVCD) +Comment[ro]=CD Super Video +Comment[rw]=CD Videwo Ihebuje +Comment[sr]=Супер видео CD +Comment[sr@Latn]=Super video CD +Comment[sv]=Supervideo-cd +Comment[ta]=சூபà¯à®ªà®°à¯ படகà¯à®•à®¾à®Ÿà¯à®šà®¿ கà¯à®±à¯à®¨à¯à®¤à®•à®Ÿà¯ +Comment[te]=సూపరౠవిడియొ సిడి +Comment[tg]=Супер Видео CD +Comment[th]=ซุเปà¸à¸£à¹Œà¸§à¸´à¸”ีโà¸à¸‹à¸µà¸”ี +Comment[tr]=Süper Video CD'si +Comment[uk]=Супер відео КД +Comment[uz]=Super-video kompakt-disk +Comment[uz@cyrillic]=Супер-видео компакт-диÑк +Comment[vi]=ÄÄ©a siêu video CD +Comment[wa]=Super plake lazer CD videyo +Comment[zh_CN]=超级 VCD +Comment[zh_TW]=超級視訊 CD +Icon=cdrom_unmount diff --git a/kioslave/media/mimetypes/vcd.desktop b/kioslave/media/mimetypes/vcd.desktop new file mode 100644 index 000000000..dae4ddcaf --- /dev/null +++ b/kioslave/media/mimetypes/vcd.desktop @@ -0,0 +1,50 @@ +[Desktop Entry] +Type=MimeType +MimeType=media/vcd +Comment=Video CD +Comment[ar]=قرص مدمج مرئي +Comment[bg]=VCD диÑк +Comment[bn]=à¦à¦¿à¦¡à¦¿à¦“ সিডি +Comment[br]=CD Video +Comment[ca]=CD VÃdeo +Comment[da]=Video-cd +Comment[el]=Βίντεο CD +Comment[eo]=Video-LD +Comment[fa]=دیسک Ùشردۀ ویدیویی +Comment[fr]=CD vidéo +Comment[fy]=Fideo-kompaktskiif +Comment[ga]=Dlúthdhiosca FÃse +Comment[gl]=VÃdeo CD +Comment[hi]=वीडियो सीडी +Comment[hu]=Video-CD +Comment[is]=VÃdeó CD +Comment[ja]=ビデオ CD +Comment[kk]=Бейне CD +Comment[km]=ស៊ីឌី​វីដáŸáž¢áž¼ +Comment[mk]=Видео ЦД +Comment[ms]=CD Video +Comment[nds]=Video-CD +Comment[ne]=à¤à¤¿à¤¡à¤¿à¤¯à¥‹ सीडी +Comment[nl]=Video-cd +Comment[nn]=Video-CD +Comment[pa]=ਵੀਡਿਓ CD +Comment[pl]=PÅ‚yta Video CD +Comment[pt]=VÃdeo CD +Comment[pt_BR]=VÃdeo CD (VCD) +Comment[ro]=CD Video +Comment[rw]=CD Videwo +Comment[sr]=Видео CD +Comment[sv]=Video-cd +Comment[ta]=படகà¯à®•à®¾à®Ÿà¯à®šà®¿ கà¯à®±à¯à®¨à¯à®¤à®•à®Ÿà¯ +Comment[te]=విడియొ సిడి +Comment[tg]=Видео CD +Comment[th]=วิดีโà¸à¸‹à¸µà¸”ี +Comment[tr]=Video CD'si +Comment[uk]=Відео КД +Comment[uz]=Video kompakt-disk +Comment[uz@cyrillic]=Видео компакт-диÑк +Comment[vi]=ÄÄ©a video CD +Comment[wa]=Plake lazer CD videyo +Comment[zh_CN]=VCD +Comment[zh_TW]=視訊 CD +Icon=cdrom_unmount diff --git a/kioslave/media/mimetypes/zip_mounted.desktop b/kioslave/media/mimetypes/zip_mounted.desktop new file mode 100644 index 000000000..3a9f97d30 --- /dev/null +++ b/kioslave/media/mimetypes/zip_mounted.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Icon=zip_mount +Type=MimeType +MimeType=media/zip_mounted +Comment=Mounted Zip Disk +Comment[af]=Gekoppelde Zip Disket +Comment[ar]=قرص Zip Ù…Øمّل +Comment[az]=BaÄŸlanmış Zip Disk +Comment[be]=Прымацаваны дыÑк Zip +Comment[bg]=Монтиран Zip диÑк +Comment[bn]=মাউনà§à¦Ÿ করা জিপ (Zip) ডিসà§à¦• +Comment[br]=Pladenn Zip marc'het +Comment[bs]=Montiran Zip disk +Comment[ca]=Disc Zip muntat +Comment[cs]=PÅ™ipojený disk Zip +Comment[csb]=Zamòntowóny disk Zip +Comment[cy]=Disg Zip wedi'i osod +Comment[da]=Monteret zip-disk +Comment[de]=Eingebundenes Zip-Medium +Comment[el]=Î ÏοσαÏτημÎνος δίσκος Zip +Comment[eo]=Surmetita ZIP-disko +Comment[es]=Disco Zip montado +Comment[et]=Ãœhendatud Zip-ketas +Comment[eu]=Zip diska muntatua +Comment[fa]=دیسک Ùشردۀ سوارشده +Comment[fi]=Liitetty Zip-levy +Comment[fr]=Disque Zip monté +Comment[fy]=Oankeppelee Zip-skiif +Comment[ga]=Diosca Zip feistithe +Comment[gl]=Disco Zip Montado +Comment[he]=תקליטון Zip מחובר +Comment[hi]=माउनà¥à¤Ÿà¥‡à¤¡ जिप डिसà¥à¤• +Comment[hr]=Pristupljeni Zip disk +Comment[hu]=Csatlakoztatott Zip-lemez +Comment[is]=Tengdur Zip diskur +Comment[it]=Disco Zip montato +Comment[ja]=マウントã•ã‚ŒãŸ Zip ディスク +Comment[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებული диÑк Zip +Comment[kk]=Тіркеген Zip диÑкі +Comment[km]=ážáž¶ážŸ Zip បាន​រៀបចំ +Comment[lo]=ຈà»àºžàº²àºš +Comment[lt]=Sumontuotas Zip diskas +Comment[lv]=PiemontÄ“ts Zip disks +Comment[mk]=Монтиран Зип-диÑк +Comment[mn]=ЗалгагдÑан Zip-ДиÑк +Comment[ms]=Cakera Zip Terlekap +Comment[mt]=Diska Zip immuntata +Comment[nb]=Montert Zip-disk +Comment[nds]=Inhangt Zip-Diskett +Comment[ne]=माउनà¥à¤Ÿ गरिà¤à¤•à¥‹ जिप डिसà¥à¤• +Comment[nl]=Aangekoppelde Zip-diskette +Comment[nn]=Montert Zip-disk +Comment[nso]=Disk yeo e Nameleditswego ya ZIP +Comment[pa]=ਮਾਊਟ ਕੀਤੀ ਜਿਪ ਡਿਸਕ +Comment[pl]=Zamontowana dyskietka Zip +Comment[pt]=Disco ZIP montado +Comment[pt_BR]=Disco Zip Montado +Comment[ro]=Disc ZIP montat +Comment[ru]=Смонтированный диÑк Zip +Comment[rw]=Disiki Zipu Yashyizwemo +Comment[se]=ÄŒatnojuvvon Zip-skearru +Comment[sk]=Pripojený disk Zip +Comment[sl]=Priklopljen disk Zip +Comment[sr]=Монтиран Zip диÑк +Comment[sr@Latn]=Montiran Zip disk +Comment[sv]=Monterad Zip-diskett +Comment[ta]=இறகà¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿ சà¯à®°à¯à®•à¯à®• வடà¯à®Ÿà¯ +Comment[tg]=Монтажшудаи диÑки Zip +Comment[th]=ดิสà¸à¹Œ Zip ที่ถูà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Comment[tr]=BaÄŸlı Zip Diski +Comment[tt]=TotaÅŸqan Zip Disk +Comment[uk]=Змонтований диÑк Zip +Comment[uz]=Ulangan ZIP-disk +Comment[uz@cyrillic]=Уланган ZIP-диÑк +Comment[ven]=Disk ya Zip yo gonyiswaho +Comment[vi]=ÄÄ©a ZIP đã kết nối +Comment[wa]=Plakete ZIP montêye +Comment[xh]=Diski Yoqokelelo ndawonye Kwediski +Comment[zh_CN]=挂载的 Zip ç£ç›˜ +Comment[zh_TW]=已掛載的 Zip ç£ç¢Ÿ +Comment[zu]=I-disk yokuhlanganiselwe ndawonye eyenyusiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mimetypes/zip_unmounted.desktop b/kioslave/media/mimetypes/zip_unmounted.desktop new file mode 100644 index 000000000..784fde7e4 --- /dev/null +++ b/kioslave/media/mimetypes/zip_unmounted.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Icon=zip_unmount +Type=MimeType +MimeType=media/zip_unmounted +Comment=Unmounted Zip Disk +Comment[af]=Ontkoppel Zip Disket +Comment[ar]=قرص Zip غير Ù…Øمّل +Comment[az]=Ayrılmış Zip Disk +Comment[be]=Ðдмацаваны дыÑк Zip +Comment[bg]=Демонтиран Zip диÑк +Comment[bn]=আনমাউনà§à¦Ÿ করা জিপ (Zip) ডিসà§à¦• +Comment[br]=Pladenn Zip divountet +Comment[bs]=Demontiran Zip disk +Comment[ca]=Disc Zip desmuntat +Comment[cs]=Odpojený disk Zip +Comment[csb]=Ã’dmòntowóny disk Zip +Comment[cy]=Disg Zip wedi'i ddadosod +Comment[da]=Afmonteret zip-disk +Comment[de]=Nicht eingebundenes Zip-Medium +Comment[el]=ΑποπÏοσαÏτημÎνος δίσκος Zip +Comment[eo]=Demetita ZIP-disko +Comment[es]=Disco Zip desmontado +Comment[et]=Lahutatud Zip-ketas +Comment[eu]=Zip diska desmuntatua +Comment[fa]=دیسک Ùشردۀ پیاده‌شده +Comment[fi]=Irrotettu Zip-levy +Comment[fr]=Disque Zip non monté +Comment[fy]=Ofkeppele Zip-skiif +Comment[ga]=Diosca Zip neamhfheistithe +Comment[gl]=Disco Zip non Montado +Comment[he]=תקליטון Zip ×ž× ×•×ª×§ +Comment[hi]=अनमाउनà¥à¤Ÿà¥‡à¤¡ जिप डिसà¥à¤• +Comment[hr]=Nepristupljeni Zip disk +Comment[hu]=Leválasztott Zip-lemez +Comment[is]=Aftengdur Zip diskur +Comment[it]=Disco Zip non montato +Comment[ja]=マウントã•ã‚Œã¦ã„ãªã„ Zip ディスク +Comment[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებული диÑк Zip +Comment[kk]=Тіркеуден шығарған Zip диÑкі +Comment[km]=ážáž¶ážŸ Zip មិន​បាន​រៀបចំ +Comment[lo]= ຫັງàºàº²àº¥àºµ +Comment[lt]=IÅ¡montuotas Zip diskas +Comment[lv]=NomontÄ“ts Zip disks +Comment[mk]=Одмонтиран Зип-диÑк +Comment[mn]=СалгагдÑан Zip-ДиÑк +Comment[ms]=Cakera Zip Nyahlekap +Comment[mt]=Diska Zip mhux immuntata +Comment[nb]=Avmontert Zip-disk +Comment[nds]=Afhangt Zip-Diskett +Comment[ne]=अनमाउनà¥à¤Ÿ गरिà¤à¤•à¥‹ जिप डिसà¥à¤• +Comment[nl]=Afgekoppelde Zip-diskette +Comment[nn]=Avmontert Zip-disk +Comment[nso]=Disk yeo e Theositswego ya ZIP +Comment[pa]=ਅਨਮਾਊਟ ਕੀਤੀ ਜ਼ਿਪ ਡਿਸਕ +Comment[pl]=Odmontowana dyskietka Zip +Comment[pt]=Disco ZIP desmontado +Comment[pt_BR]=Disco Zip Desmontado +Comment[ro]=Disc ZIP nemontat +Comment[ru]=Отмонтированный диÑк Zip +Comment[rw]=Disiki Zipu Yakuwemo +Comment[se]=Gálgajuvvon Zip-skearru +Comment[sk]=Nepripojený disk Zip +Comment[sl]=Odklopljen disk Zip +Comment[sr]=Демонтиран Zip диÑк +Comment[sr@Latn]=Demontiran Zip disk +Comment[sv]=Avmonterad Zip-diskett +Comment[ta]=வெளியேறà¯à®±à®¿à®¯ சà¯à®°à¯à®•à¯à®• தகட௠+Comment[tg]=Ҷудо шудаи диÑки Zip +Comment[th]=ดิสà¸à¹Œ Zip ที่ไม่ได้เม้านท์ +Comment[tr]=Ayrılmış Zip Diski +Comment[tt]=TotaÅŸmaÄŸan Zip Disk +Comment[uk]=Демонтований диÑк Zip +Comment[uz]=Ulanmagan ZIP-disk +Comment[uz@cyrillic]=Уланмаган ZIP-диÑк +Comment[ven]=Disk ya Zip i songo gonyiswaho +Comment[vi]=ÄÄ©a ZIP đã gỡ ra +Comment[wa]=Plakete ZIP dismontêye +Comment[xh]=Diski Yoqokelelo ndawone Enganyuswanga +Comment[zh_CN]=挂载的 Zip ç£ç›˜ +Comment[zh_TW]=未掛載的 Zip ç£ç¢Ÿ +Comment[zu]=I-disk yokuhlanganiselwe ndawonye eyehlisiwe +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/media/mounthelper/Makefile.am b/kioslave/media/mounthelper/Makefile.am new file mode 100644 index 000000000..9080ba81a --- /dev/null +++ b/kioslave/media/mounthelper/Makefile.am @@ -0,0 +1,12 @@ +bin_PROGRAMS = kio_media_mounthelper + +INCLUDES = -I$(srcdir)/../libmediacommon $(all_includes) +AM_LDFLAGS = $(all_libraries) + +kio_media_mounthelper_SOURCES = kio_media_mounthelper.cpp + +kio_media_mounthelper_LDFLAGS = $(KDE_RPATH) $(all_libraries) +kio_media_mounthelper_LDADD = ../libmediacommon/libmediacommon.la $(LIB_KIO) + +METASOURCES = AUTO + diff --git a/kioslave/media/mounthelper/kio_media_mounthelper.cpp b/kioslave/media/mounthelper/kio_media_mounthelper.cpp new file mode 100644 index 000000000..12743847f --- /dev/null +++ b/kioslave/media/mounthelper/kio_media_mounthelper.cpp @@ -0,0 +1,209 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + Parts of this file are + Copyright 2003 Waldo Bastian <bastian@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <kcmdlineargs.h> +#include <klocale.h> +#include <kapplication.h> +#include <kurl.h> +#include <kmessagebox.h> +#include <dcopclient.h> +#include <dcopref.h> +#include <qtimer.h> +#include <stdlib.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kprocess.h> +#include <kstartupinfo.h> + +#include "kio_media_mounthelper.h" + +const Medium MountHelper::findMedium(const KURL &url) +{ + DCOPRef mediamanager("kded", "mediamanager"); + + // Try filename first + DCOPReply reply = mediamanager.call( "properties", url.fileName() ); + if ( !reply.isValid() ) { + m_errorStr = i18n("The KDE mediamanager is not running.")+"\n"; + return Medium(QString::null, QString::null); + } + const Medium& medium = Medium::create(reply); + if ( medium.id().isEmpty() ) { + // Try full URL now + reply = mediamanager.call( "properties", url.prettyURL() ); + if ( !reply.isValid() ) { + m_errorStr = i18n("Internal Error"); + return Medium(QString::null, QString::null); + } + return Medium::create(reply); + } else { + return medium; + } +} + +MountHelper::MountHelper() : KApplication() +{ + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + m_errorStr = ""; + + KURL url(args->url(0)); + const Medium medium = findMedium(url); + + if ( medium.id().isEmpty() ) + { + if (m_errorStr.isEmpty()) + m_errorStr+= i18n("%1 cannot be found.").arg(url.prettyURL()); + QTimer::singleShot(0, this, SLOT(error()) ); + return; + } + + if ( !medium.isMountable() && !args->isSet("e") && !args->isSet("s")) + { + m_errorStr = i18n("%1 is not a mountable media.").arg(url.prettyURL()); + QTimer::singleShot(0, this, SLOT(error()) ); + return; + } + + QString device = medium.deviceNode(); + QString mount_point = medium.mountPoint(); + + m_isCdrom = medium.mimeType().find("dvd")!=-1 + || medium.mimeType().find("cd")!=-1; + + if (args->isSet("u")) + { + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "unmount", medium.id()); + if (reply.isValid()) + reply.get(m_errorStr); + kdDebug() << "medium unmount " << m_errorStr << endl; + if (m_errorStr.isNull()) + ::exit(0); + else + error(); + } + else if (args->isSet("s") || args->isSet("e")) + { + /* + * We want to call mediamanager unmount before invoking eject. That's + * because unmount would provide an informative error message in case of + * failure. However, there are cases when unmount would fail + * (supermount, slackware, see bug#116209) but eject would succeed. + * Thus if unmount fails, save unmount error message and invokeEject() + * anyway. Only if both unmount and eject fail, notify the user by + * displaying the saved error message (see ejectFinished()). + */ + if (medium.isMounted()) + { + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "unmount", medium.id()); + if (reply.isValid()) + reply.get(m_errorStr); + if (m_errorStr.isNull()) + invokeEject(device, true); + else + error(); + m_device = device; + } else + invokeEject(device, true); + } + else + { + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "mount", medium.id()); + if (reply.isValid()) + reply.get(m_errorStr); + if (m_errorStr.isNull()) + ::exit(0); + else + error(); + } +} + +void MountHelper::invokeEject(const QString &device, bool quiet) +{ + KProcess *proc = new KProcess(this); + *proc << "kdeeject"; + if (quiet) + { + *proc << "-q"; + } + *proc << device; + connect( proc, SIGNAL(processExited(KProcess *)), + this, SLOT( ejectFinished(KProcess *) ) ); + proc->start(); +} + +void MountHelper::ejectFinished(KProcess* proc) +{ + /* + * If eject failed, report the error stored in m_errorStr + */ + if (proc->normalExit() && proc->exitStatus() == 0) { + ::exit(0); + } else { + if (m_errorStr.isEmpty()) { + if (m_isCdrom) + m_errorStr = i18n("The device was successfully unmounted, but the tray could not be opened"); + else + m_errorStr = i18n("The device was successfully unmounted, but could not be ejected"); + } + QTimer::singleShot(0, this, SLOT(error())); + } +} + +void MountHelper::error() +{ + KMessageBox::error(0, m_errorStr); + ::exit(1); +} + +static KCmdLineOptions options[] = +{ + { "u", I18N_NOOP("Unmount given URL"), 0 }, + { "m", I18N_NOOP("Mount given URL (default)"), 0 }, + { "e", I18N_NOOP("Eject given URL via kdeeject"), 0}, + { "s", I18N_NOOP("Unmount and Eject given URL (necessary for some USB devices)"), 0}, + {"!+URL", I18N_NOOP("media:/ URL to mount/unmount/eject/remove"), 0 }, + KCmdLineLastOption +}; + + +int main(int argc, char **argv) +{ + KCmdLineArgs::init(argc, argv, "kio_media_mounthelper", + "kio_media_mounthelper", "kio_media_mounthelper", + "0.1"); + + KCmdLineArgs::addCmdLineOptions( options ); + KGlobal::locale()->setMainCatalogue("kio_media"); + KApplication::addCmdLineOptions(); + + if (KCmdLineArgs::parsedArgs()->count()==0) KCmdLineArgs::usage(); + KApplication *app = new MountHelper(); + + KStartupInfo::appStarted(); + app->dcopClient()->attach(); + return app->exec(); +} + +#include "kio_media_mounthelper.moc" diff --git a/kioslave/media/mounthelper/kio_media_mounthelper.h b/kioslave/media/mounthelper/kio_media_mounthelper.h new file mode 100644 index 000000000..478d802f9 --- /dev/null +++ b/kioslave/media/mounthelper/kio_media_mounthelper.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + Parts of this file are + Copyright 2003 Waldo Bastian <bastian@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _KIO_MEDIA_MOUNTHELPER_H_ +#define _KIO_MEDIA_MOUNTHELPER_H_ + +#include <kapplication.h> +#include <qstring.h> +#include <kio/job.h> + +#include "medium.h" + +class MountHelper : public KApplication +{ + Q_OBJECT +public: + MountHelper(); + +private: + const Medium findMedium(const KURL &url); + void invokeEject(const QString &device, bool quiet=false); + QString m_errorStr; + QString m_device; + bool m_isCdrom; + +private slots: + void ejectFinished(KProcess* proc); + void error(); +}; + +#endif diff --git a/kioslave/media/propsdlgplugin/Makefile.am b/kioslave/media/propsdlgplugin/Makefile.am new file mode 100644 index 000000000..5e3d8af83 --- /dev/null +++ b/kioslave/media/propsdlgplugin/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO + +kde_module_LTLIBRARIES = media_propsdlgplugin.la + +media_propsdlgplugin_la_LIBADD = ../libmediacommon/libmediacommon.la $(LIB_KIO) +media_propsdlgplugin_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +AM_CPPFLAGS = $(all_includes) + +kde_services_DATA = media_propsdlgplugin.desktop + +media_propsdlgplugin_la_SOURCES = propertiespage.cpp propertiespagegui.ui propsdlgshareplugin.cpp diff --git a/kioslave/media/propsdlgplugin/media_propsdlgplugin.desktop b/kioslave/media/propsdlgplugin/media_propsdlgplugin.desktop new file mode 100644 index 000000000..c0cdcbbba --- /dev/null +++ b/kioslave/media/propsdlgplugin/media_propsdlgplugin.desktop @@ -0,0 +1,102 @@ +[Desktop Entry] +Type=Service +Name=Media Properties Page +Name[bg]=Страница Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° ноÑÐ¸Ñ‚ÐµÐ»Ñ +Name[bn]=মিডিয়া বৈশিষà§à¦Ÿà§à¦¯à¦¾à¦¬à¦²à§€ পৃষà§à¦ া +Name[ca]=Pà gina de propietats de suport +Name[cs]=Stránka vlastnostà média +Name[csb]=Starna swòjiznë media +Name[da]=Medieegenskabsside +Name[de]=Eigenschaften von Medien +Name[el]=Σελίδα ιδιοτήτων μÎσων +Name[eo]=Medio-Ecoj PaÄo +Name[es]=Página de propiedades de medios +Name[et]=Andmekandjate omadused +Name[fa]=صÙØÛ€ ویژگیهای رسانه +Name[fi]=Mediaominaisuudet +Name[fr]=Page de propriétés du média +Name[fy]=Media eigenskip side +Name[gl]=Páxina de Propiedades dos Media +Name[he]=דף מ××¤×™×™× ×™ מדיה +Name[hr]=Stranica svojstva medija +Name[hu]=MédiajellemzÅ‘k lap +Name[is]=Eiginleikar miðils +Name[it]=Pagina delle proprietà dei dispositivi di archiviazione +Name[ja]=メディア属性ページ +Name[kk]=Медиа қаÑиеттер беті +Name[km]=ទំពáŸážšâ€‹áž›áž€áŸ’ážážŽáŸˆážŸáž˜áŸ’áž”ážáŸ’ážáž·â€‹áž˜áŸážŒáŸ€ +Name[lt]=Media įrenginio savybių puslapis +Name[nb]=Side for medievarsling +Name[nds]=Medienegenschappen-Siet +Name[ne]=मिडिया विशेषता पृषà¥à¤ +Name[nl]=Pagina met media-eigenschappen +Name[nn]=Side for medieeigenskapar +Name[pa]=ਮੀਡਿਆ ਵਿਸ਼ੇਸ਼ਤਾ ਸਫ਼ਾ +Name[pl]=Strona wÅ‚aÅ›ciwoÅ›ci noÅ›nika +Name[pt]=Página de Propriedades de Suportes FÃsicos +Name[pt_BR]=Página de Propriedades da MÃdia +Name[ro]=Pagina de proprietăți media +Name[ru]=СвойÑтва диÑка +Name[sk]=Strana vlastnostà média +Name[sl]=Stran z lastnostmi nosilca +Name[sr]=Страна Ñа ÑвојÑтвима медијума +Name[sr@Latn]=Strana sa svojstvima medijuma +Name[sv]=Sida för mediaegenskaper +Name[te]=మాధà±à°¯à°® à°—à±à°£à°¾à°² à°ªà±à°Ÿ +Name[th]=หน้าคุณสมบัติขà¸à¸‡à¸ªà¸·à¹ˆà¸ +Name[tr]=Ortam Özellikleri Sayfası +Name[uk]=Сторінка влаÑтивоÑтей ноÑіїв інформації +Name[uz]=Saqlash uskunalarning xossalari +Name[uz@cyrillic]=Сақлаш уÑкуналарнинг хоÑÑалари +Name[vi]=Trang tà i sản phÆ°Æ¡ng tiện +Name[wa]=PÃ¥dje des prôpietés media +Name[zh_CN]=ä»‹è´¨å±žæ€§é¡µé¢ +Name[zh_TW]=媒體內容é +Comment=Konqueror properties dialog plugin to configure mount behaviour +Comment[bg]=ПриÑтавка за наÑтройване на монтирането (Konqueror) +Comment[ca]=Dià leg de propietats de l'endollable Konqueror per a configurar el comportament de muntatge +Comment[cs]=Dialog nastavenà chovánà pÅ™ipojenà modulu pro Konqueror +Comment[csb]=Plugins òkna swòjiznë Konquerora do kònfigùracëji zachówaniô mòntowaniô +Comment[da]=Konqueror plugin med egenskabsdialog til at indstille monteringsopførsel +Comment[de]=Konqueror-Modul zum Einstellen des Verhaltens beim Einbinden +Comment[el]=Î Ïόσθετο διαλόγου ιδιοτήτων του Konqueror για τη ÏÏθμιση της συμπεÏιφοÏάς Ï€ÏοσάÏτησης +Comment[eo]=Konkeranto eco-dialogo kromaĵo por agordi surmeto-konduton +Comment[es]=Complemento de la ventana de propiedades de Konqueror para configurar el comportamiento del montaje +Comment[et]=Konquerori seadistustedialoogi plugin andmekandjate ühendamise seadistamiseks +Comment[fa]=وصلۀ Ù…Øاورۀ ویژگیهای Konqueror برای پیکربندی رÙتار سوار کردن +Comment[fi]=Konquerorin ominaisuussovelma, joka tarkkailee liitospisteitä +Comment[fr]=Module de Konqueror pour configurer le comportement du montage +Comment[fy]=Konqueror eigenskip-dialooch-plugin om keppelgedrach te konfigurearjen +Comment[gl]=Extensión de diálogo de propiedades para Konqueror para configurar o comportamento das montaxes +Comment[he]=תוסף חלון מ××¤×™×™× ×™× ×©×œ Konqueror לקביעת ×פשרויות חיבור +Comment[hr]=Konqueror dodatak dijaloga svojstava za konfiguriranje naÄina pristupanja +Comment[hu]=Konqueror beállÃtómodul a csatlakoztatási mód megadásához +Comment[is]=Konqueror eiginleikagluggi til að stilla tengihegðun +Comment[it]=Plugin della configurazione di Konqueror per configurare il montaggio +Comment[ja]=mount ã®æŒ™å‹•ã‚’è¨å®šã™ã‚‹ Konqueror プãƒãƒ‘ティダイアãƒã‚° プラグイン +Comment[kk]=Жалғау тәртібін баптайтын Konqueror қаÑиеттер диалогының плагин модулі +Comment[km]=កម្មវិធី​ជំនួយ​ឲ្យ​មាន​ប្រអប់​លក្ážážŽáŸˆážŸáž˜áŸ’áž”ážáŸ’ážáž· Konqueror ដើម្បី​កំណážáŸ‹â€‹ážšáž…នាសម្ពáŸáž“្ធ​ឥរិយាបážâ€‹áž˜áŸ‰áŸ„áž“ +Comment[lt]=Konqueror savybių dialogo priedas, leidžiantis konfigÅ«ruoti montavimo elgsenÄ… +Comment[nb]=Et programtillegg for Ã¥ sette opp montering av enheter i Konqueror +Comment[nds]=Egenschappendialoog-Plugin för Konqueror för't Instellen vun't Inhangbedregen +Comment[ne]=माउनà¥à¤Ÿ वà¥à¤¯à¤µà¤¹à¤¾à¤° कनà¥à¤«à¤¿à¤—र गरà¥à¤¨ कनà¥à¤•à¥à¤µà¥‡à¤°à¤° विशेषता संवाद पà¥à¤²à¤—इन +Comment[nl]=Dialoogvenster voor Konqueror om het aankoppelgedrag in te stellen +Comment[nn]=Programtillegg for oppsett av montering i Konqueror +Comment[pl]=Wtyczka okna wÅ‚aÅ›ciwoÅ›ci Konquerora do konfiguracji zachowania montowania +Comment[pt]='Plugin' da janela de propriedades do Konqueror, para configurar o comportamento da montagem +Comment[pt_BR]=Plug-in do diálogo de propriedades do Konqueror para configurar o comportamento da montagem +Comment[ro]=Plugin de dialog al proprietăților Konqueror pentru a configura comportarea lui mount +Comment[ru]=Модуль ÑвойÑтв Ð´Ð»Ñ Konqueror, отвечающий за поведение при подключении файловых ÑиÑтем +Comment[sk]=Modul Konqueror dialógu vlastnostà pre konfiguráciu správania pripojenia +Comment[sl]=Vstavek za Konqueror s katerim se nastavlja obnaÅ¡anje priklopa medijev +Comment[sr]=Прикључак Ñа ÑвојÑтвима Konqueror-а за подешавање понашања при монтирању +Comment[sr@Latn]=PrikljuÄak sa svojstvima Konqueror-a za podeÅ¡avanje ponaÅ¡anja pri montiranju +Comment[sv]=Konqueror insticksprogram med egenskapsdialogruta för att anpassa monteringsbeteende +Comment[th]=ปลั๊à¸à¸à¸´à¸™à¸à¸¥à¹ˆà¸à¸‡à¸•à¸à¸šà¹‚ต้ขà¸à¸‡ Konqueror ใช้เพื่à¸à¸›à¸£à¸±à¸šà¹à¸•à¹ˆà¸‡à¸žà¸¤à¸•à¸´à¸à¸£à¸£à¸¡à¸à¸²à¸£à¹€à¸¡à¸²à¸™à¸—์สื่ภ+Comment[uk]=Втулок вікна влаÑтивоÑтей Konqueror Ð´Ð»Ñ Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²ÐµÐ´Ñ–Ð½ÐºÐ¸ Ð¼Ð¾Ð½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ +Comment[vi]=bổ sung há»™p thoại tà i sản Konqueror để cấu hình ứng xá» gắn kết +Comment[wa]=Tchôke-divins di dvize di prôpietés Konqueror po-z apontyî l' dujhance do montaedje +Comment[zh_CN]=é…置挂载行为的 Konqueror 属性对è¯æ¡†æ’件 +Comment[zh_TW]=Konqueror 內容å°è©±æ¡†å¤–掛程å¼ï¼Œç”¨æ–¼è¨å®šæŽ›è¼‰çš„行為 +X-KDE-Library=media_propsdlgplugin +ServiceTypes=KPropsDlg/Plugin,media/audiocd,media/hdd_mounted,media/hdd_unmounted,media/cdrom_mounted,media/cdrom_unmounted,media/cdwriter_mounted,media/nfs_mounted,media/cdwriter_unmounted,media/nfs_unmounted,media/removable_mounted,media/dvd_mounted,media/removable_unmounted,media/dvd_unmounted,media/smb_mounted,media/dvdvideo,media/smb_unmounted,media/floppy5_mounted,media/floppy5_unmounted,media/floppy_mounted,media/zip_mounted,media/floppy_unmounted,media/zip_unmounted,media/camera_mounted,media/camera_unmounted diff --git a/kioslave/media/propsdlgplugin/propertiespage.cpp b/kioslave/media/propsdlgplugin/propertiespage.cpp new file mode 100644 index 000000000..fd1c098e0 --- /dev/null +++ b/kioslave/media/propsdlgplugin/propertiespage.cpp @@ -0,0 +1,217 @@ +/* + Copyright (c) 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include <qcheckbox.h> +#include <qtooltip.h> +#include <qbuttongroup.h> +#include <qlineedit.h> +#include <qfileinfo.h> +#include <qlabel.h> +#include <qregexp.h> +#include <kpushbutton.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <qcombobox.h> +#include <qtimer.h> +#include <kdebug.h> +#include "propertiespage.h" +#include <dcopref.h> + +// keep in sync with .ui and kded module +const char *short_names[] = {"lower", "win95", "winnt", "mixed", 0 }; +const char *journales[] = {"data", "ordered", "writeback", 0 }; + +PropertiesPage::PropertiesPage(QWidget* parent, const QString &_id) + : PropertiesPageGUI(parent), id(_id) +{ + kdDebug() << "props page " << id << endl; + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "mountoptions", id); + + QStringList list; + + if (reply.isValid()) + list = reply; + + if (list.size()) { + kdDebug() << "list " << list << endl; + + for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) + { + QString key = (*it).left((*it).find('=')); + QString value = (*it).mid((*it).find('=') + 1); + kdDebug() << "key '" << key << "' value '" << value << "'\n"; + options[key] = value; + } + + if (!options.contains("ro")) + option_ro->hide(); + else + option_ro->setChecked(options["ro"] == "true"); + connect( option_ro, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("quiet")) + option_quiet->hide(); + else + option_quiet->setChecked(options["quiet"] == "true"); + connect( option_quiet, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("sync")) + option_sync->hide(); + else + option_sync->setChecked(options["sync"] == "true"); + connect( option_sync, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("atime")) + option_atime->hide(); + else + option_atime->setChecked(options["atime"] == "true"); + connect( option_atime, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("flush")) + option_flush->hide(); + else + option_flush->setChecked(options["flush"] == "true"); + connect( option_flush, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("utf8")) + option_utf8->hide(); + else + option_utf8->setChecked(options["utf8"] == "true"); + connect( option_utf8, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("uid")) + option_uid->hide(); + else + option_uid->setChecked(options["uid"] == "true"); + connect( option_uid, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("shortname")) + { + option_shortname->hide(); + text_shortname->hide(); + } + else + { + for (int index = 0; short_names[index]; ++index) + if (options["shortname"] == short_names[index]) + { + option_shortname->setCurrentItem(index); + break; + } + connect( option_shortname, SIGNAL( activated(int) ), SIGNAL( changed() ) ); + } + + if (!options.contains("journaling")) + { + text_journaling->hide(); + option_journaling->hide(); + } + else + { + for (int index = 0; journales[index]; ++index) + if (options["journaling"] == journales[index]) + { + option_journaling->setCurrentItem(index); + break; + } + connect( option_journaling, SIGNAL( activated(int) ), SIGNAL( changed() ) ); + } + + label_filesystem->setText(i18n("Filesystem: %1").arg(options["filesystem"])); + option_mountpoint->setText(options["mountpoint"]); + connect( option_mountpoint, SIGNAL( textChanged( const QString &) ), SIGNAL( changed() ) ); + option_automount->setChecked(options["automount"] == "true"); + connect( option_automount, SIGNAL( stateChanged(int) ), SIGNAL( changed() ) ); + + if (!options.contains("journaling") && + !options.contains("shortname") && + !options.contains("uid") && + !options.contains("utf8") && + !options.contains("flush")) + groupbox_specific->hide(); + + } else { + + groupbox_generic->setEnabled(false); + groupbox_specific->setEnabled(false); + label_filesystem->hide(); + } +} + +PropertiesPage::~PropertiesPage() +{ +} + +bool PropertiesPage::save() +{ + QStringList result; + + if (options.contains("ro")) + result << QString("ro=%1").arg(option_ro->isChecked() ? "true" : "false"); + + if (options.contains("quiet")) + result << QString("quiet=%1").arg(option_quiet->isChecked() ? "true" : "false"); + + if (options.contains("sync")) + result << QString("sync=%1").arg(option_sync->isChecked() ? "true" : "false"); + + if (options.contains("atime")) + result << QString("atime=%1").arg(option_atime->isChecked() ? "true" : "false"); + + if (options.contains("flush")) + result << QString("flush=%1").arg(option_flush->isChecked() ? "true" : "false"); + + if (options.contains("utf8")) + result << QString("utf8=%1").arg(option_utf8->isChecked() ? "true" : "false"); + + if (options.contains("uid")) + result << QString("uid=%1").arg(option_uid->isChecked() ? "true" : "false"); + + if (options.contains("shortname")) + result << QString("shortname=%1").arg(short_names[option_shortname->currentItem()]); + + if (options.contains("journaling")) + result << QString("journaling=%1").arg(journales[option_journaling->currentItem()]); + + QString mp = option_mountpoint->text(); + if (!mp.startsWith("/media/")) + { + KMessageBox::sorry(this, i18n("Mountpoint has to be below /media")); + return false; + } + result << QString("mountpoint=%1").arg(mp); + result << QString("automount=%1").arg(option_automount->isChecked() ? "true" : "false"); + + kdDebug() << result << endl; + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "setMountoptions", id, result); + + if (reply.isValid()) + return (bool)reply; + else { + KMessageBox::sorry(this, + i18n("Saving the changes failed")); + + return false; + } +} + +#include "propertiespage.moc" diff --git a/kioslave/media/propsdlgplugin/propertiespage.h b/kioslave/media/propsdlgplugin/propertiespage.h new file mode 100644 index 000000000..7bc47599e --- /dev/null +++ b/kioslave/media/propsdlgplugin/propertiespage.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef PROPERTIESPAGE_H +#define PROPERTIESPAGE_H + +#include "propertiespagegui.h" +#include <qmap.h> + +class QCheckBox; +class Medium; + +class PropertiesPage : public PropertiesPageGUI +{ + Q_OBJECT + +public: + PropertiesPage(QWidget* parent, const QString &_id); + virtual ~PropertiesPage(); + + bool save(); + +protected: + + QMap<QString,QString> options; + QString id; + +}; + +#endif diff --git a/kioslave/media/propsdlgplugin/propertiespagegui.ui b/kioslave/media/propsdlgplugin/propertiespagegui.ui new file mode 100644 index 000000000..c4de4c059 --- /dev/null +++ b/kioslave/media/propsdlgplugin/propertiespagegui.ui @@ -0,0 +1,387 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>PropertiesPageGUI</class> +<widget class="QWidget"> + <property name="name"> + <cstring>PropertiesPageGUI</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>527</width> + <height>476</height> + </rect> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout17</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupbox_generic</cstring> + </property> + <property name="title"> + <string>Generic Mount Options</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout15</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_ro</cstring> + </property> + <property name="text"> + <string>Read only</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Mount the file system read-only.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_quiet</cstring> + </property> + <property name="text"> + <string>Quiet</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Attempts to chown or chmod files do not return errors, although they fail. Use with caution!</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_sync</cstring> + </property> + <property name="text"> + <string>Synchronous</string> + </property> + <property name="whatsThis" stdset="0"> + <string>All I/O to the file system should be done synchronously.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_atime</cstring> + </property> + <property name="text"> + <string>Access time updates</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Update inode access time for each access.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>Mountpoint:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>option_mountpoint</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Under what directory this file system shall be mounted. Please note that there is no guarantee that the system will respect your wish. For one the directory has to be below /media - and it does not yet have to exist.</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>option_mountpoint</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_automount</cstring> + </property> + <property name="text"> + <string>Mount automatically</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Mount this file system automatically.</string> + </property> + </widget> + </vbox> + </widget> + </vbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupbox_specific</cstring> + </property> + <property name="title"> + <string>Filesystem Specific Mount Options</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_flush</cstring> + </property> + <property name="text"> + <string>Flushed IO</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Always flush all data to the hot plug devices immediately and don't cache it.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_utf8</cstring> + </property> + <property name="text"> + <string>UTF-8 charset</string> + </property> + <property name="whatsThis" stdset="0"> + <string>UTF8 is the filesystem safe 8-bit encoding of Unicode that is used by the console. It can be be enabled for the filesystem with this option.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>option_uid</cstring> + </property> + <property name="text"> + <string>Mount as user</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Mount this file system as user.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>text_journaling</cstring> + </property> + <property name="text"> + <string>Journaling:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>option_journaling</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><h2>Specifies the journalling mode for file data. Metadata is always journaled. </h2> + +<h3><b>All Data</b></h3> + All data is committed into the journal prior to being written into the main file system. This is the slowest variant with the highest data security. + +<h3><b>Ordered</b></h3> + All data is forced directly out to the main file system prior to its metadata being committed to the journal. + +<h3><b>Write Back</b></h3> + Data ordering is not preserved - data may be written into the main file system after its metadata has been committed to the journal. This is rumoured to be the highest-throughput option. It guarantees internal file system integrity, however it can allow old data to appear in files after a crash and journal recovery.</string> + </property> + </widget> + <widget class="QComboBox"> + <item> + <property name="text"> + <string>All Data</string> + </property> + </item> + <item> + <property name="text"> + <string>Ordered</string> + </property> + </item> + <item> + <property name="text"> + <string>Write Back</string> + </property> + </item> + <property name="name"> + <cstring>option_journaling</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><h2>Specifies the journalling mode for file data. Metadata is always journaled. </h2> + +<h3><b>All Data</b></h3> + All data is committed into the journal prior to being written into the main file system. This is the slowest variant with the highest data security. + +<h3><b>Ordered</b></h3> + All data is forced directly out to the main file system prior to its metadata being committed to the journal. + +<h3><b>Write Back</b></h3> + Data ordering is not preserved - data may be written into the main file system after its metadata has been committed to the journal. This is rumoured to be the highest-throughput option. It guarantees internal file system integrity, however it can allow old data to appear in files after a crash and journal recovery.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>text_shortname</cstring> + </property> + <property name="text"> + <string>Short names:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>option_shortname</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><h2>Defines the behaviour for creation and display of filenames which fit into 8.3 characters. If a long name for a file exists, it will always be preferred display.</h2> + +<h3><b>Lower</b></h3> +Force the short name to lower case upon display; store a long name when the short name is not all upper case. + +<h3><b>Windows 95</b></h3> +Force the short name to upper case upon display; store a long name when the short name is not all upper case. + +<h3><b>Windows NT</b></h3> +Display the shortname as is; store a long name when the short name is not all lower case or all upper case. + +<h3><b>Mixed</b></h3> +Display the short name as is; store a long name when the short name is not all upper case.</string> + </property> + </widget> + <widget class="QComboBox"> + <item> + <property name="text"> + <string>Lower</string> + </property> + </item> + <item> + <property name="text"> + <string>Windows 95</string> + </property> + </item> + <item> + <property name="text"> + <string>Windows NT</string> + </property> + </item> + <item> + <property name="text"> + <string>Mixed</string> + </property> + </item> + <property name="name"> + <cstring>option_shortname</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><h2>Defines the behaviour for creation and display of filenames which fit into 8.3 characters. If a long name for a file exists, it will always be preferred display.</h2> + +<h3><b>Lower</b></h3> +Force the short name to lower case upon display; store a long name when the short name is not all upper case. + +<h3><b>Windows 95</b></h3> +Force the short name to upper case upon display; store a long name when the short name is not all upper case. + +<h3><b>Windows NT</b></h3> +Display the shortname as is; store a long name when the short name is not all lower case or all upper case. + +<h3><b>Mixed</b></h3> +Display the short name as is; store a long name when the short name is not all upper case.</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </vbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>label_filesystem</cstring> + </property> + <property name="text"> + <string>Filesystem: iso9660</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> +</widget> +<connections> + <connection> + <sender>option_ro</sender> + <signal>stateChanged(int)</signal> + <receiver>PropertiesPageGUI</receiver> + <slot>changedSlot()</slot> + </connection> +</connections> +<variables> + <variable>bool m_hasChanged;</variable> +</variables> +<signals> + <signal>changed()</signal> +</signals> +<slots> + <slot access="protected">changedSlot()</slot> +</slots> +<functions> + <function returnType="bool">hasChanged()</function> +</functions> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kioslave/media/propsdlgplugin/propertiespagegui.ui.h b/kioslave/media/propsdlgplugin/propertiespagegui.ui.h new file mode 100644 index 000000000..45a9ca2d7 --- /dev/null +++ b/kioslave/media/propsdlgplugin/propertiespagegui.ui.h @@ -0,0 +1,22 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void PropertiesPageGUI::changedSlot() +{ + m_hasChanged = true; + emit changed(); +} + +bool PropertiesPageGUI::hasChanged() +{ + return m_hasChanged; +} + + diff --git a/kioslave/media/propsdlgplugin/propsdlgshareplugin.cpp b/kioslave/media/propsdlgplugin/propsdlgshareplugin.cpp new file mode 100644 index 000000000..ebfd2c3de --- /dev/null +++ b/kioslave/media/propsdlgplugin/propsdlgshareplugin.cpp @@ -0,0 +1,100 @@ +/* + Copyright (c) 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include <qstring.h> +#include <qvbox.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qtimer.h> + +#include <kgenericfactory.h> +#include <kdebug.h> +#include <kpushbutton.h> +#include <kfileshare.h> +#include <kmessagebox.h> +#include <kprocess.h> +#include <kstandarddirs.h> +#include <kdialog.h> +#include <kglobal.h> +#include <dcopref.h> + +#include "propertiespage.h" +#include "propsdlgshareplugin.h" +#include "../libmediacommon/medium.h" + +typedef KGenericFactory<PropsDlgSharePlugin, KPropertiesDialog> PropsDlgSharePluginFactory; + +K_EXPORT_COMPONENT_FACTORY( media_propsdlgplugin, + PropsDlgSharePluginFactory("media_propsdlgplugin") ) + +class PropsDlgSharePlugin::Private +{ + public: + PropertiesPage* page; +}; + +PropsDlgSharePlugin::PropsDlgSharePlugin( KPropertiesDialog *dlg, + const char *, const QStringList & ) + : KPropsDlgPlugin(dlg), d(0) +{ + if (properties->items().count() != 1) + return; + + KFileItem *item = properties->items().first(); + + DCOPRef mediamanager("kded", "mediamanager"); + kdDebug() << "properties " << item->url() << endl; + DCOPReply reply = mediamanager.call( "properties", item->url().url() ); + + if ( !reply.isValid() ) + return; + + QVBox* vbox = properties->addVBoxPage(i18n("&Mounting")); + + d = new Private(); + + d->page = new PropertiesPage(vbox, Medium::create(reply).id()); + connect(d->page, SIGNAL(changed()), + SLOT(slotChanged())); + + // QTimer::singleShot(100, this, SLOT(slotChanged())); + +} + +void PropsDlgSharePlugin::slotChanged() +{ + kdDebug() << "slotChanged()\n"; + setDirty(true); +} + +PropsDlgSharePlugin::~PropsDlgSharePlugin() +{ + delete d; +} + +void PropsDlgSharePlugin::applyChanges() +{ + kdDebug() << "applychanges\n"; + if (!d->page->save()) { + properties->abortApplying(); + } +} + + +#include "propsdlgshareplugin.moc" + diff --git a/kioslave/media/propsdlgplugin/propsdlgshareplugin.h b/kioslave/media/propsdlgplugin/propsdlgshareplugin.h new file mode 100644 index 000000000..10ec47454 --- /dev/null +++ b/kioslave/media/propsdlgplugin/propsdlgshareplugin.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2004 Jan Schaefer <j_schaef@informatik.uni-kl.de> + + 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 WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef KONQFILESHAREPLUGIN_H +#define KONQFILESHAREPLUGIN_H + +#include <kpropertiesdialog.h> + +class PropsDlgSharePlugin : public KPropsDlgPlugin +{ +Q_OBJECT +public: + PropsDlgSharePlugin( KPropertiesDialog *dlg, const char *, const QStringList & ); + virtual ~PropsDlgSharePlugin(); + virtual void applyChanges(); + +public slots: + void slotChanged(); + +private: + class Private; + Private *d; + +}; + +#endif + + diff --git a/kioslave/media/services/Makefile.am b/kioslave/media/services/Makefile.am new file mode 100644 index 000000000..dded3c403 --- /dev/null +++ b/kioslave/media/services/Makefile.am @@ -0,0 +1,3 @@ +servicesdir = $(kde_datadir)/konqueror/servicemenus +services_DATA = media_mount.desktop media_unmount.desktop media_eject.desktop media_safelyremove.desktop + diff --git a/kioslave/media/services/media_eject.desktop b/kioslave/media/services/media_eject.desktop new file mode 100644 index 000000000..1ecd29fa8 --- /dev/null +++ b/kioslave/media/services/media_eject.desktop @@ -0,0 +1,90 @@ +[Desktop Entry] +ServiceTypes=media/cdrom_mounted,media/cdrom_unmounted,media/cdwriter_mounted,media/cdwriter_unmounted,media/dvd_mounted,media/dvd_unmounted,media/audiocd,media/blankcd,media/blankdvd,media/dvdvideo,media/svcd,media/vcd +Actions=MediaEject; +X-KDE-Priority=TopLevel +X-KDE-MediaNotifierHide=true + +[Desktop Action MediaEject] +Name=Eject +Name[af]=Uitskiet +Name[ar]=أقذ٠+Name[az]=Çıxart +Name[be]=Вызваліць +Name[bg]=Изваждане +Name[bn]=ইজেকà§à¦Ÿ +Name[br]=Stlepel +Name[bs]=Izbaci +Name[ca]=Expulsa +Name[cs]=Vysunout +Name[csb]=Wësënie +Name[cy]=Allfwrw +Name[da]=Skub ud +Name[de]=Auswerfen +Name[el]=Εξαγωγή +Name[eo]=Eligo +Name[es]=Expulsar +Name[et]=Väljastamine +Name[eu]=Egotzi +Name[fa]=پس زدن +Name[fi]=Poista +Name[fr]=Éjecter +Name[fy]=Utsmytknop +Name[ga]=DÃchuir +Name[gl]=Expulsar +Name[he]=×”×•×¦× +Name[hi]=बाहर +Name[hr]=Izbaci +Name[hu]=Kidobás +Name[is]=Henda út +Name[it]=Espelli +Name[ja]=å–り出㗠+Name[ka]=CD-ს áƒáƒ›áƒáƒ¦áƒ”ბრ+Name[kk]=Ðлып-шығару +Name[km]=ច្រាន​ចáŸáž‰ +Name[ko]=꺼내기 +Name[lo]=ເàºàº»àº²à»àºœà»ˆàº™àºàºàº +Name[lt]=IÅ¡mesti +Name[lv]=Izņemt +Name[mk]=Извади +Name[mn]=Гаргах +Name[ms]=Lenting +Name[mt]=Iftaħ +Name[nb]=Løs ut +Name[nds]=Rutsmieten +Name[ne]=निकालà¥à¤¨à¥à¤¹à¥‹à¤¸à¥ +Name[nl]=Uitwerpen +Name[nn]=Løys ut +Name[nso]=Ntsha +Name[oc]=Expulsa +Name[pa]=ਬਾਹਰ ਕੱਢੋ +Name[pl]=WysuÅ„ +Name[pt]=Ejectar +Name[pt_BR]=Ejetar +Name[ro]=Ejectează +Name[ru]=Извлечь CD +Name[rw]=Gusohora +Name[se]=Bálkes olggos +Name[sk]=Vysunúť +Name[sl]=Izvrzi +Name[sr]=Избаци +Name[sr@Latn]=Izbaci +Name[ss]=Khafuna +Name[sv]=Mata ut +Name[ta]=வெளிதà¯à®¤à®³à¯ +Name[te]=ఎజెకà±à°Ÿà± +Name[tg]=Ихроҷ +Name[th]=เà¸à¸²à¹à¸œà¹ˆà¸™à¸à¸à¸ +Name[tr]=Çıkart +Name[tt]=Çığar +Name[uk]=Виштовхнути +Name[uz]=Chiqarish +Name[uz@cyrillic]=Чиқариш +Name[ven]=Bvisa +Name[vi]=Äẩy Ä‘Ä©a ra +Name[wa]=Fé rexhe +Name[xh]=Khuphela ngaphandle +Name[zh_CN]=弹出 +Name[zh_TW]=退出 +Name[zu]=Khipha +Exec=kio_media_mounthelper -e %u + diff --git a/kioslave/media/services/media_mount.desktop b/kioslave/media/services/media_mount.desktop new file mode 100644 index 000000000..1779d2208 --- /dev/null +++ b/kioslave/media/services/media_mount.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +ServiceTypes=media/cdrom_unmounted,media/cdwriter_unmounted,media/dvd_unmounted,media/floppy5_unmounted,media/floppy_unmounted,media/hdd_unmounted,media/nfs_unmounted,media/removable_unmounted,media/smb_unmounted,media/zip_unmounted,media/camera_unmounted +Actions=MediaMount; +X-KDE-Priority=TopLevel +X-KDE-MediaNotifierHide=true + +[Desktop Action MediaMount] +Name=Mount +Name[af]=Koppel +Name[ar]=ركÙب +Name[az]=BaÄŸla +Name[be]=Прымацаваць +Name[bg]=Монтиране +Name[bn]=মাউনà§à¦Ÿ +Name[br]=Marc'hañ +Name[bs]=Montiraj +Name[ca]=Munta +Name[cs]=PÅ™ipojit +Name[csb]=Mòntujë +Name[cy]=Gosod +Name[da]=Montér +Name[de]=Laufwerk einbinden +Name[el]=Î ÏοσάÏτηση +Name[eo]=Surmeti +Name[es]=Montar +Name[et]=Ãœhenda +Name[eu]=Muntatu +Name[fa]=سوار کردن +Name[fi]=Liitä +Name[fr]=Monter +Name[fy]=Oankeppelje (mount) +Name[ga]=Feistigh +Name[gl]=Montar +Name[he]=חבר +Name[hi]=माउनà¥à¤Ÿ +Name[hr]=Pristupi +Name[hsb]=montować +Name[hu]=Csatlakoztatás +Name[is]=Tengja +Name[it]=Monta +Name[ja]=マウント +Name[ka]=მáƒáƒœáƒ¢áƒ˜áƒ ებრ+Name[kk]=Тіркеу +Name[km]=រៀបចំ +Name[lo]=ຈà»àºžàº²àºš +Name[lt]=Montuoti +Name[lv]=PiemontÄ“t +Name[mk]=Монтирај +Name[mn]=Залгах +Name[ms]=Lekap +Name[mt]=Immonta +Name[nb]=Monter +Name[nds]=Inhangen +Name[ne]=माउनà¥à¤Ÿ +Name[nl]=Aankoppelen (mount) +Name[nn]=Monter +Name[nso]=Nameletsa +Name[pa]=ਮਾਊਟ +Name[pl]=Zamontuj +Name[pt]=Montar +Name[pt_BR]=Montar +Name[ro]=Montează +Name[ru]=Подключить +Name[rw]=Gushyiramo +Name[se]=ÄŒana +Name[sk]=PripojiÅ¥ +Name[sl]=Priklopi +Name[sr]=Монтирај +Name[sr@Latn]=Montiraj +Name[sv]=Montera +Name[ta]=à®à®±à¯à®±à¯ +Name[tg]=ВаÑл кунӣ +Name[th]=เม้านท์ +Name[tr]=BaÄŸla +Name[tt]=Bäyläp quy +Name[uk]=Змонтувати +Name[uz]=Ulash +Name[uz@cyrillic]=Улаш +Name[ven]=Gonya +Name[vi]=Kết nối +Name[wa]=Monter +Name[xh]=Layisha +Name[zh_CN]=挂载 +Name[zh_TW]=掛載 +Name[zu]=Yenyusa +Exec=kio_media_mounthelper -m %u + diff --git a/kioslave/media/services/media_safelyremove.desktop b/kioslave/media/services/media_safelyremove.desktop new file mode 100644 index 000000000..298a5f2e5 --- /dev/null +++ b/kioslave/media/services/media_safelyremove.desktop @@ -0,0 +1,75 @@ +[Desktop Entry] +ServiceTypes=media/removable_mounted,media/removable_unmounted,media/camera_mounted,media/camera_unmounted +Actions=MediaSafelyRemove; +X-KDE-Priority=TopLevel +X-KDE-MediaNotifierHide=true + +[Desktop Action MediaSafelyRemove] +Name=Safely Remove +Name[af]=Verwyder veilig +Name[ar]=Ø£Øذ٠بأمان +Name[be]=БÑÑпечна адлучыць +Name[bg]=БезопаÑно изваждане +Name[bn]=নিরাপদà¦à¦¾à¦¬à§‡ সরাও +Name[bs]=Sigurno ukloni +Name[ca]=Extreu amb seguretat +Name[cs]=BezpeÄnÄ› odstranit +Name[csb]=Remôj na bezpieczny ôrt +Name[da]=Fjern sikkert +Name[de]=Sicher entfernen +Name[el]=Ασφαλής αφαίÏεση +Name[eo]=Sekura Forigo +Name[es]=Extracción segura +Name[et]=Eemalda turvaliselt +Name[eu]=Atera arriskurik gabe +Name[fa]=Øذ٠امن +Name[fi]=Poista turvallisesti +Name[fr]=Enlever en toute sécurité +Name[fy]=Feilich ferwiderje +Name[ga]=Bain Amach go Sábháilte +Name[gl]=Eliminar de Maneira Segura +Name[he]=שליפה בבטחה +Name[hr]=Slobodno uklonite +Name[hu]=Biztonságos leválasztás +Name[is]=Fjarlægja öruggt +Name[it]=Rimozione sicura +Name[ja]=安全ã«å–り除ã +Name[ka]=უსáƒáƒ¤áƒ თხáƒáƒ“ გáƒáƒ›áƒáƒ თვრ+Name[kk]=ҚауіпÑіз алып шығу +Name[km]=យកចáŸáž‰â€‹ážŠáŸ„យ​សុវážáŸ’ážáž·áž—ាព +Name[lt]=Saugiai paÅ¡alinti +Name[mk]=Безбедно отÑтрани +Name[ms]=Buang Dengan Selamat +Name[nb]=Sikker fjerning +Name[nds]=Seker rutnehmen +Name[ne]=सà¥à¤°à¤•à¥à¤·à¥€à¤¤ रà¥à¤ªà¤®à¤¾ हटाउनà¥à¤¹à¥‹à¤¸à¥ +Name[nl]=Veilig verwijderen +Name[nn]=Trygg fjerning +Name[pa]=ਸà©à¨°à©±à¨–ਿਅਤ ਹਟਾਓ +Name[pl]=UsuÅ„ w sposób bezpieczny +Name[pt]=Retirar com Segurança +Name[pt_BR]=Remover de Modo Seguro +Name[ro]=Scoate în siguranță +Name[ru]=БезопаÑно извлечь +Name[rw]=Gukuramo Neza +Name[se]=DorvvoÅ¡laÅ¡ eretváldin +Name[sk]=BezpeÄne odpojiÅ¥ +Name[sl]=Varno odstrani +Name[sr]=Безбедно уклони +Name[sr@Latn]=Bezbedno ukloni +Name[sv]=Säker urkoppling +Name[ta]=பாதà¯à®•à®¾à®ªà¯à®ªà®¾à®• நீகà¯à®•à¯ +Name[te]=జాగరతà±à°¤à°—à°¾ తియి +Name[tg]=Баровардани бехетар +Name[th]=ถà¸à¸”à¸à¸à¸à¸à¸¢à¹ˆà¸²à¸‡à¸›à¸¥à¸à¸”ภัย +Name[tr]=Güvenli Kaldır +Name[tt]=Ä°min Çığaru +Name[uk]=Безпечно вилучити +Name[uz]=Ehtiyotlik bilan uzish +Name[uz@cyrillic]=Ðҳтиётлик билан узиш +Name[vi]=Gỡ ra An toà n +Name[wa]=Ositer e sÃ¥vrité +Name[zh_CN]=å®‰å…¨åˆ é™¤ +Name[zh_TW]=安全的移除 +Exec=kio_media_mounthelper -s %u + diff --git a/kioslave/media/services/media_unmount.desktop b/kioslave/media/services/media_unmount.desktop new file mode 100644 index 000000000..537750095 --- /dev/null +++ b/kioslave/media/services/media_unmount.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +ServiceTypes=media/cdrom_mounted,media/cdwriter_mounted,media/dvd_mounted,media/floppy5_mounted,media/floppy_mounted,media/hdd_mounted,media/nfs_mounted,media/smb_mounted,media/zip_mounted,media/vcd,media/svcd,media/dvdvideo +Actions=MediaUnmount; +X-KDE-Priority=TopLevel +X-KDE-MediaNotifierHide=true + +[Desktop Action MediaUnmount] +Name=Unmount +Name[af]=Ontkoppel +Name[ar]=أزل التركيب +Name[az]=Ayır +Name[be]=Ðдмацаваць +Name[bg]=Демонтиране +Name[bn]=আনমাউনà§à¦Ÿ +Name[br]=Divarc'hañ +Name[bs]=Demontiraj +Name[ca]= Desmunta +Name[cs]=Odpojit +Name[csb]=Ã’dmòntujë +Name[cy]=Dadosod +Name[da]=Afmontér +Name[de]=Laufwerkeinbindung lösen +Name[el]=ΑποπÏοσάÏτηση +Name[eo]=Demeti +Name[es]=Desmontar +Name[et]=Lahuta +Name[eu]=Desmuntatu +Name[fa]=پیاده کردن +Name[fi]=Irrota +Name[fr]=Libérer +Name[fy]=Ofkeppelje (unmount) +Name[ga]=DÃfheistigh +Name[gl]=Desmontar +Name[he]=× ×ª×§ +Name[hi]=अनमाउनà¥à¤Ÿ +Name[hr]=Napusti +Name[hsb]=Wotmontować +Name[hu]=Leválasztás +Name[is]=Aftengja +Name[it]=Smonta +Name[ja]=マウント解除 +Name[ka]=დემáƒáƒœáƒ¢áƒ˜áƒ ებრ+Name[kk]=Тіркеуден шығару +Name[km]=មិន​រៀបចំ +Name[lo]=ຫັງàºàº²àº¥àºµ +Name[lt]=IÅ¡montuoti +Name[lv]=NomontÄ“t +Name[mk]=Одмонтирај +Name[mn]=Салгах +Name[ms]=Nyahlekap +Name[mt]=Å»monta +Name[nb]=Avmonter +Name[nds]=Afhangen +Name[ne]=अनमाउनà¥à¤Ÿ +Name[nl]=Afkoppelen (unmount) +Name[nn]=Avmonter +Name[nso]=Theosa +Name[pa]=ਅਨਮਾਉਟ +Name[pl]=Odmontuj +Name[pt]=Desmontar +Name[pt_BR]=Desmontar +Name[ro]=Demontează +Name[ru]=Отключить +Name[rw]=Gukuramo +Name[se]=Gálgga +Name[sk]=OdpojiÅ¥ +Name[sl]=Odklopi +Name[sr]=Демонтирај +Name[sr@Latn]=Demontiraj +Name[sv]=Avmontera +Name[ta]=வெளியேறà¯à®±à¯ +Name[tg]=Ҷудо кунӣ +Name[th]=เลิà¸à¹€à¸¡à¹‰à¸²à¸™à¸—์ +Name[tr]=Ayır +Name[tt]=Bäyläwne çiÅŸ +Name[uk]=Демонтувати +Name[uz]=Uzish +Name[uz@cyrillic]=Узиш +Name[ven]=Usa gonya +Name[vi]=Gỡ ra +Name[wa]=Dismonter +Name[xh]=Sukuyilayisha +Name[zh_CN]=å¸è½½ +Name[zh_TW]=å¸è¼‰ +Name[zu]=Yehlisa +Exec=kio_media_mounthelper -u %u + diff --git a/kioslave/media/testmedia.cpp b/kioslave/media/testmedia.cpp new file mode 100644 index 000000000..d3f19512c --- /dev/null +++ b/kioslave/media/testmedia.cpp @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kio_media.h" +#include "testmedia.h" + +#include <config.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#include <stdlib.h> + +static bool check(const QString& txt, QString a, QString b) +{ + if (a.isEmpty()) + a = QString::null; + if (b.isEmpty()) + b = QString::null; + if (a == b) { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +int main(int argc, char *argv[]) +{ + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc,argv,"testmedia", 0, 0, 0, 0); + KApplication app; + + TestMedia test; + test.setup(); + test.runAll(); + kdDebug() << "All tests OK." << endl; + return 0; // success. The exit(1) in check() is what happens in case of failure. +} + +void TestMedia::setup() +{ + +} + +void TestMedia::runAll() +{ + +} + diff --git a/kioslave/media/testmedia.h b/kioslave/media/testmedia.h new file mode 100644 index 000000000..dd2481bda --- /dev/null +++ b/kioslave/media/testmedia.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef TESTMEDIA_H +#define TESTMEDIA_H + +class TestMedia +{ +public: + TestMedia() {} + void setup(); + void runAll(); + + // tests + +}; + +#endif diff --git a/kioslave/nfs/AUTHORS b/kioslave/nfs/AUTHORS new file mode 100644 index 000000000..062f9d6cb --- /dev/null +++ b/kioslave/nfs/AUTHORS @@ -0,0 +1,2 @@ +Written and maintained by: +Alexander Neundorf, neundorf@kde.org diff --git a/kioslave/nfs/Makefile.am b/kioslave/nfs/Makefile.am new file mode 100644 index 000000000..2c4fc062c --- /dev/null +++ b/kioslave/nfs/Makefile.am @@ -0,0 +1,28 @@ +## Makefile.am of kdebase/kioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +####### Files + +kde_module_LTLIBRARIES = kio_nfs.la + +kio_nfs_la_SOURCES = kio_nfs.cpp mount_xdr.c nfs_prot_xdr.c +kio_nfs_la_LIBADD = -lkio $(LIBRPC) +kio_nfs_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = nfs_prot.h mount.h + +kdelnk_DATA = nfs.protocol +kdelnkdir = $(kde_servicesdir) + +METASOURCES = AUTO + +$(srcdir)/mount_xdr.c: $(srcdir)/mount.x + cd $(srcdir) && rpcgen ./mount.x + + +$(srcdir)/nfs_prot_xdr.c: $(srcdir)/nfs_prot.x + cd $(srcdir) && rpcgen ./nfs_prot.x + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_nfs.pot diff --git a/kioslave/nfs/README b/kioslave/nfs/README new file mode 100644 index 000000000..671934e2c --- /dev/null +++ b/kioslave/nfs/README @@ -0,0 +1,3 @@ +this is an ioslave for KDE 2 for NFS, version 2. + +Alex diff --git a/kioslave/nfs/TODO b/kioslave/nfs/TODO new file mode 100644 index 000000000..6e5525127 --- /dev/null +++ b/kioslave/nfs/TODO @@ -0,0 +1,7 @@ +-symlink stuff (listing and stating works already) +-honour the resume flag +-maybe use rpcgen ? +-cache handling: how long should file handles be cached ? + should the stat'ed structures be cached ? no, IMHO + +Alex diff --git a/kioslave/nfs/kio_nfs.cpp b/kioslave/nfs/kio_nfs.cpp new file mode 100644 index 000000000..18a7f1393 --- /dev/null +++ b/kioslave/nfs/kio_nfs.cpp @@ -0,0 +1,1615 @@ +/* This file is part of the KDE project + + Copyright (C) 2000 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <arpa/inet.h> + +// This is needed on Solaris so that rpc.h defines clnttcp_create etc. +#ifndef PORTMAP +#define PORTMAP +#endif +#include <rpc/rpc.h> // for rpc calls + +#include <errno.h> +#include <grp.h> +#include <memory.h> +#include <netdb.h> +#include <pwd.h> +#include <stdlib.h> +#include <strings.h> +#include <stdio.h> +#include <time.h> +#include <unistd.h> + +#include <qfile.h> +#include <qdir.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> + +#include <kio/global.h> +#include <iostream> + +#include "nfs_prot.h" +#define fhandle _fhandle +#include "mount.h" +#include "kio_nfs.h" + +#define MAXHOSTLEN 256 + +//#define MAXFHAGE 60*15 //15 minutes maximum age for file handles + +//this ioslave is for NFS version 2 +#define NFSPROG ((u_long)100003) +#define NFSVERS ((u_long)2) + +using namespace KIO; +using namespace std; + +//this is taken from kdelibs/kdecore/fakes.cpp +//#if !defined(HAVE_GETDOMAINNAME) + +int x_getdomainname(char *name, size_t len) +{ + struct utsname uts; + struct hostent *hent; + int rv = -1; + + if (name == 0L) + errno = EINVAL; + else + { + name[0] = '\0'; + if (uname(&uts) >= 0) + { + if ((hent = gethostbyname(uts.nodename)) != 0L) + { + char *p = strchr(hent->h_name, '.'); + if (p != 0L) + { + ++p; + if (strlen(p) > len-1) + errno = EINVAL; + else + { + strcpy(name, p); + rv = 0; + } + } + } + } + } + return rv; +} +//#endif + + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_nfs" ); + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_nfs protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + kdDebug(7121) << "NFS: kdemain: starting" << endl; + + NFSProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; +} + +static bool isRoot(const QString& path) +{ + return (path.isEmpty() || (path=="/")); +} + +static bool isAbsoluteLink(const QString& path) +{ + //hmm, don't know + if (path.isEmpty()) return TRUE; + if (path[0]=='/') return TRUE; + return FALSE; +} + +static void createVirtualDirEntry(UDSEntry & entry) +{ + UDSAtom atom; + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + entry.append( atom ); + + atom.m_uds = KIO::UDS_USER; + atom.m_str = "root"; + entry.append( atom ); + atom.m_uds = KIO::UDS_GROUP; + atom.m_str = "root"; + entry.append( atom ); + + //a dummy size + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = 1024; + entry.append( atom ); +} + + +static void stripTrailingSlash(QString& path) +{ + //if (path=="/") return; + if (path=="/") path=""; + else if (path[path.length()-1]=='/') path.truncate(path.length()-1); +} + +static void getLastPart(const QString& path, QString& lastPart, QString& rest) +{ + int slashPos=path.findRev("/"); + lastPart=path.mid(slashPos+1); + rest=path.left(slashPos+1); +} + +static QString removeFirstPart(const QString& path) +{ + QString result(""); + if (path.isEmpty()) return result; + result=path.mid(1); + int slashPos=result.find("/"); + return result.mid(slashPos+1); +} + +NFSFileHandle::NFSFileHandle() +:m_isInvalid(FALSE) +{ + memset(m_handle,'\0',NFS_FHSIZE+1); +// m_detectTime=time(0); +} + +NFSFileHandle::NFSFileHandle(const NFSFileHandle & handle) +:m_isInvalid(FALSE) +{ + m_handle[NFS_FHSIZE]='\0'; + memcpy(m_handle,handle.m_handle,NFS_FHSIZE); + m_isInvalid=handle.m_isInvalid; +// m_detectTime=handle.m_detectTime; +} + +NFSFileHandle::~NFSFileHandle() +{} + +NFSFileHandle& NFSFileHandle::operator= (const NFSFileHandle& src) +{ + memcpy(m_handle,src.m_handle,NFS_FHSIZE); + m_isInvalid=src.m_isInvalid; +// m_detectTime=src.m_detectTime; + return *this; +} + +NFSFileHandle& NFSFileHandle::operator= (const char* src) +{ + if (src==0) + { + m_isInvalid=TRUE; + return *this; + }; + memcpy(m_handle,src,NFS_FHSIZE); + m_isInvalid=FALSE; +// m_detectTime=time(0); + return *this; +} + +/*time_t NFSFileHandle::age() const +{ + return (time(0)-m_detectTime); +}*/ + + +NFSProtocol::NFSProtocol (const QCString &pool, const QCString &app ) +:SlaveBase( "nfs", pool, app ) +,m_client(0) +,m_sock(-1) +,m_lastCheck(time(0)) +{ + kdDebug(7121)<<"NFS::NFS: -"<<pool<<"-"<<endl; +} + +NFSProtocol::~NFSProtocol() +{ + closeConnection(); +} + +/*This one is currently unused, so it could be removed. + The intention was to keep handles around, and from time to time + remove handles which are too old. Alex + */ +/*void NFSProtocol::checkForOldFHs() +{ + kdDebug(7121)<<"checking for fhs older than "<<MAXFHAGE<<endl; + kdDebug(7121)<<"current items: "<<m_handleCache.count()<<endl; + NFSFileHandleMap::Iterator it=m_handleCache.begin(); + NFSFileHandleMap::Iterator lastIt=it; + while (it!=m_handleCache.end()) + { + kdDebug(7121)<<it.data().age()<<flush; + if (it.data().age()>MAXFHAGE) + { + kdDebug(7121)<<"removing"<<endl; + m_handleCache.remove(it); + if (it==lastIt) + { + it=m_handleCache.begin(); + lastIt=it; + } + else + it=lastIt; + } + lastIt=it; + it++; + }; + kdDebug(7121)<<"left items: "<<m_handleCache.count()<<endl; + m_lastCheck=time(0); +}*/ + +void NFSProtocol::closeConnection() +{ + close(m_sock); + m_sock=-1; + if (m_client==0) return; + CLNT_DESTROY(m_client); + + m_client=0; +} + +bool NFSProtocol::isExportedDir(const QString& path) +{ + return (m_exportedDirs.find(path.mid(1))!=m_exportedDirs.end()); +} + +/* This one works recursive. + It tries to get the file handle out of the file handle cache. + If this doesn't succeed, it needs to do a nfs rpc call + in order to obtain one. + */ +NFSFileHandle NFSProtocol::getFileHandle(QString path) +{ + if (m_client==0) openConnection(); + + //I'm not sure if this is useful + //if ((time(0)-m_lastCheck)>MAXFHAGE) checkForOldFHs(); + + stripTrailingSlash(path); + kdDebug(7121)<<"getting FH for -"<<path<<"-"<<endl; + //now the path looks like "/root/some/dir" or "" if it was "/" + NFSFileHandle parentFH; + //we didn't find it + if (path.isEmpty()) + { + kdDebug(7121)<<"path is empty, invalidating the FH"<<endl; + parentFH.setInvalid(); + return parentFH; + } + //check wether we have this filehandle cached + //the filehandles of the exported root dirs are always in the cache + if (m_handleCache.find(path)!=m_handleCache.end()) + { + kdDebug(7121)<<"path is in the cache, returning the FH -"<<m_handleCache[path]<<"-"<<endl; + return m_handleCache[path]; + } + QString rest, lastPart; + getLastPart(path,lastPart,rest); + kdDebug(7121)<<"splitting path into rest -"<<rest<<"- and lastPart -"<<lastPart<<"-"<<endl; + + parentFH=getFileHandle(rest); + //f*ck, it's invalid + if (parentFH.isInvalid()) + { + kdDebug(7121)<<"the parent FH is invalid"<<endl; + return parentFH; + } + // do the rpc call + diropargs dirargs; + diropres dirres; + memcpy(dirargs.dir.data,(const char*)parentFH,NFS_FHSIZE); + QCString tmpStr=QFile::encodeName(lastPart); + dirargs.name=tmpStr.data(); + + //cerr<<"calling rpc: FH: -"<<parentFH<<"- with name -"<<dirargs.name<<"-"<<endl; + + int clnt_stat = clnt_call(m_client, NFSPROC_LOOKUP, + (xdrproc_t) xdr_diropargs, (char*)&dirargs, + (xdrproc_t) xdr_diropres, (char*)&dirres,total_timeout); + + if ((clnt_stat!=RPC_SUCCESS) || (dirres.status!=NFS_OK)) + { + //we failed + kdDebug(7121)<<"lookup of filehandle failed"<<endl; + parentFH.setInvalid(); + return parentFH; + } + //everything went fine up to now :-) + parentFH=dirres.diropres_u.diropres.file.data; + //kdDebug(7121)<<"filesize: "<<dirres.diropres_u.diropres.attributes.size<<endl; + m_handleCache.insert(path,parentFH); + kdDebug(7121)<<"return FH -"<<parentFH<<"-"<<endl; + return parentFH; +} + +/* Open connection connects to the mount daemon on the server side. + In order to do this it needs authentication and calls auth_unix_create(). + Then it asks the mount daemon for the exported shares. Then it tries + to mount all these shares. If this succeeded for at least one of them, + a client for the nfs daemon is created. + */ +void NFSProtocol::openConnection() +{ + kdDebug(7121)<<"NFS::openConnection for -" << m_currentHost.latin1() << "-" << endl; + if (m_currentHost.isEmpty()) + { + error(ERR_UNKNOWN_HOST,""); + return; + } + struct sockaddr_in server_addr; + if (m_currentHost[0] >= '0' && m_currentHost[0] <= '9') + { + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = inet_addr(m_currentHost.latin1()); + } + else + { + struct hostent *hp=gethostbyname(m_currentHost.latin1()); + if (hp==0) + { + error( ERR_UNKNOWN_HOST, m_currentHost.latin1() ); + return; + } + server_addr.sin_family = AF_INET; + memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length); + } + + // create mount deamon client + closeConnection(); + server_addr.sin_port = 0; + m_sock = RPC_ANYSOCK; + m_client=clnttcp_create(&server_addr,MOUNTPROG, MOUNTVERS, &m_sock, 0, 0); + if (m_client==0) + { + server_addr.sin_port = 0; + m_sock = RPC_ANYSOCK; + pertry_timeout.tv_sec = 3; + pertry_timeout.tv_usec = 0; + m_client = clntudp_create(&server_addr,MOUNTPROG, MOUNTVERS, pertry_timeout, &m_sock); + if (m_client==0) + { + clnt_pcreateerror(const_cast<char *>("mount clntudp_create")); + error(ERR_COULD_NOT_CONNECT, m_currentHost.latin1()); + return; + } + } + QCString hostName("localhost"); + char nameBuffer[1024]; + nameBuffer[0] = '\0'; + if (gethostname(nameBuffer, 1024)==0) + { + nameBuffer[sizeof(nameBuffer)-1] = '\0'; + hostName=nameBuffer; + // I have the same problem here as Stefan Westerfeld, that's why I use + // the getdomainname() from fakes.cpp (renamed to x_getdomainname()), this one works + // taken from kdelibs/arts/mcopy/mcoputils.cc + nameBuffer[0] = '\0'; + if (x_getdomainname(nameBuffer, 1024)==0) + { + nameBuffer[sizeof(nameBuffer)-1] = '\0'; + /* + * I don't know why, but on my linux machine, the domainname + * always ends up being (none), which is certainly no valid + * domainname + */ + if(strcmp(nameBuffer,"(none)") != 0) { + hostName += "."; + hostName += nameBuffer; + } + } + } + kdDebug(7121) << "hostname is -" << hostName << "-" << endl; + m_client->cl_auth = authunix_create(hostName.data(), geteuid(), getegid(), 0, 0); + total_timeout.tv_sec = 20; + total_timeout.tv_usec = 0; + + exports exportlist; + //now do the stuff + memset(&exportlist, '\0', sizeof(exportlist)); + + int clnt_stat = clnt_call(m_client, MOUNTPROC_EXPORT,(xdrproc_t) xdr_void, NULL, + (xdrproc_t) xdr_exports, (char*)&exportlist,total_timeout); + if (!checkForError(clnt_stat, 0, m_currentHost.latin1())) return; + + fhstatus fhStatus; + bool atLeastOnceSucceeded(FALSE); + for(; exportlist!=0;exportlist = exportlist->ex_next) { + kdDebug(7121) << "found export: " << exportlist->ex_dir << endl; + + memset(&fhStatus, 0, sizeof(fhStatus)); + clnt_stat = clnt_call(m_client, MOUNTPROC_MNT,(xdrproc_t) xdr_dirpath, (char*)(&(exportlist->ex_dir)), + (xdrproc_t) xdr_fhstatus,(char*) &fhStatus,total_timeout); + if (fhStatus.fhs_status==0) { + atLeastOnceSucceeded=TRUE; + NFSFileHandle fh; + fh=fhStatus.fhstatus_u.fhs_fhandle; + QString fname; + if ( exportlist->ex_dir[0] == '/' ) + fname = KIO::encodeFileName(exportlist->ex_dir + 1); + else + fname = KIO::encodeFileName(exportlist->ex_dir); + m_handleCache.insert(QString("/")+fname,fh); + m_exportedDirs.append(fname); + // kdDebug() <<"appending file -"<<fname<<"- with FH: -"<<fhStatus.fhstatus_u.fhs_fhandle<<"-"<<endl; + } + } + if (!atLeastOnceSucceeded) + { + closeConnection(); + error( ERR_COULD_NOT_AUTHENTICATE, m_currentHost.latin1()); + return; + } + server_addr.sin_port = 0; + + //now create the client for the nfs daemon + //first get rid of the old one + closeConnection(); + m_sock = RPC_ANYSOCK; + m_client = clnttcp_create(&server_addr,NFSPROG,NFSVERS,&m_sock,0,0); + if (m_client == 0) + { + server_addr.sin_port = 0; + m_sock = RPC_ANYSOCK; + pertry_timeout.tv_sec = 3; + pertry_timeout.tv_usec = 0; + m_client = clntudp_create(&server_addr,NFSPROG, NFSVERS, pertry_timeout, &m_sock); + if (m_client==0) + { + clnt_pcreateerror(const_cast<char *>("NFS clntudp_create")); + error(ERR_COULD_NOT_CONNECT, m_currentHost.latin1()); + return; + } + } + m_client->cl_auth = authunix_create(hostName.data(),geteuid(),getegid(),0,0); + connected(); + kdDebug(7121)<<"openConnection succeeded"<<endl; +} + +void NFSProtocol::listDir( const KURL& _url) +{ + KURL url(_url); + QString path( QFile::encodeName(url.path())); + + if (path.isEmpty()) + { + url.setPath("/"); + redirection(url); + finished(); + return; + } + //open the connection + if (m_client==0) openConnection(); + //it failed + if (m_client==0) return; + if (isRoot(path)) + { + kdDebug(7121)<<"listing root"<<endl; + totalSize( m_exportedDirs.count()); + //in this case we don't need to do a real listdir + UDSEntry entry; + for (QStringList::Iterator it=m_exportedDirs.begin(); it!=m_exportedDirs.end(); it++) + { + UDSAtom atom; + entry.clear(); + atom.m_uds = KIO::UDS_NAME; + atom.m_str = (*it); + kdDebug(7121)<<"listing "<<(*it)<<endl; + entry.append( atom ); + createVirtualDirEntry(entry); + listEntry( entry, false); + } + listEntry( entry, true ); // ready + finished(); + return; + } + + QStringList filesToList; + kdDebug(7121)<<"getting subdir -"<<path<<"-"<<endl; + stripTrailingSlash(path); + NFSFileHandle fh=getFileHandle(path); + //cerr<<"this is the fh: -"<<fh<<"-"<<endl; + if (fh.isInvalid()) + { + error( ERR_DOES_NOT_EXIST, path); + return; + } + readdirargs listargs; + memset(&listargs,0,sizeof(listargs)); + listargs.count=1024*16; + memcpy(listargs.dir.data,fh,NFS_FHSIZE); + readdirres listres; + do + { + memset(&listres,'\0',sizeof(listres)); + int clnt_stat = clnt_call(m_client, NFSPROC_READDIR, (xdrproc_t) xdr_readdirargs, (char*)&listargs, + (xdrproc_t) xdr_readdirres, (char*)&listres,total_timeout); + if (!checkForError(clnt_stat,listres.status,path)) return; + for (entry *dirEntry=listres.readdirres_u.reply.entries;dirEntry!=0;dirEntry=dirEntry->nextentry) + { + if ((QString(".")!=dirEntry->name) && (QString("..")!=dirEntry->name)) + filesToList.append(dirEntry->name); + } + } while (!listres.readdirres_u.reply.eof); + totalSize( filesToList.count()); + + UDSEntry entry; + //stat all files in filesToList + for (QStringList::Iterator it=filesToList.begin(); it!=filesToList.end(); it++) + { + UDSAtom atom; + diropargs dirargs; + diropres dirres; + memcpy(dirargs.dir.data,fh,NFS_FHSIZE); + QCString tmpStr=QFile::encodeName(*it); + dirargs.name=tmpStr.data(); + + kdDebug(7121)<<"calling rpc: FH: -"<<fh<<"- with name -"<<dirargs.name<<"-"<<endl; + + int clnt_stat= clnt_call(m_client, NFSPROC_LOOKUP, + (xdrproc_t) xdr_diropargs, (char*)&dirargs, + (xdrproc_t) xdr_diropres, (char*)&dirres,total_timeout); + if (!checkForError(clnt_stat,dirres.status,(*it))) return; + + NFSFileHandle tmpFH; + tmpFH=dirres.diropres_u.diropres.file.data; + m_handleCache.insert(path+"/"+(*it),tmpFH); + + entry.clear(); + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = (*it); + entry.append( atom ); + + //is it a symlink ? + if (S_ISLNK(dirres.diropres_u.diropres.attributes.mode)) + { + kdDebug(7121)<<"it's a symlink !"<<endl; + //cerr<<"fh: "<<tmpFH<<endl; + nfs_fh nfsFH; + memcpy(nfsFH.data,dirres.diropres_u.diropres.file.data,NFS_FHSIZE); + //get the link dest + readlinkres readLinkRes; + char nameBuf[NFS_MAXPATHLEN]; + readLinkRes.readlinkres_u.data=nameBuf; + int clnt_stat=clnt_call(m_client, NFSPROC_READLINK, + (xdrproc_t) xdr_nfs_fh, (char*)&nfsFH, + (xdrproc_t) xdr_readlinkres, (char*)&readLinkRes,total_timeout); + if (!checkForError(clnt_stat,readLinkRes.status,(*it))) return; + kdDebug(7121)<<"link dest is -"<<readLinkRes.readlinkres_u.data<<"-"<<endl; + QCString linkDest(readLinkRes.readlinkres_u.data); + atom.m_uds = KIO::UDS_LINK_DEST; + atom.m_str = linkDest; + entry.append( atom ); + + bool isValid=isValidLink(path,linkDest); + if (!isValid) + { + completeBadLinkUDSEntry(entry,dirres.diropres_u.diropres.attributes); + } + else + { + if (isAbsoluteLink(linkDest)) + { + completeAbsoluteLinkUDSEntry(entry,linkDest); + } + else + { + tmpStr=QDir::cleanDirPath(path+QString("/")+QString(linkDest)).latin1(); + dirargs.name=tmpStr.data(); + tmpFH=getFileHandle(tmpStr); + memcpy(dirargs.dir.data,tmpFH,NFS_FHSIZE); + + attrstat attrAndStat; + + kdDebug(7121)<<"calling rpc: FH: -"<<fh<<"- with name -"<<dirargs.name<<"-"<<endl; + + clnt_stat = clnt_call(m_client, NFSPROC_GETATTR, + (xdrproc_t) xdr_diropargs, (char*)&dirargs, + (xdrproc_t) xdr_attrstat, (char*)&attrAndStat,total_timeout); + if (!checkForError(clnt_stat,attrAndStat.status,tmpStr)) return; + completeUDSEntry(entry,attrAndStat.attrstat_u.attributes); + } + } + } + else + completeUDSEntry(entry,dirres.diropres_u.diropres.attributes); + listEntry( entry, false); + } + listEntry( entry, true ); // ready + finished(); +} + +void NFSProtocol::stat( const KURL & url) +{ + QString path( QFile::encodeName(url.path())); + stripTrailingSlash(path); + kdDebug(7121)<<"NFS::stat for -"<<path<<"-"<<endl; + QString tmpPath=path; + if ((tmpPath.length()>1) && (tmpPath[0]=='/')) tmpPath=tmpPath.mid(1); + // We can't stat root, but we know it's a dir + if (isRoot(path) || isExportedDir(path)) + { + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = path; + entry.append( atom ); + createVirtualDirEntry(entry); + // no size + statEntry( entry ); + finished(); + kdDebug(7121)<<"succeeded"<<endl; + return; + } + + NFSFileHandle fh=getFileHandle(path); + if (fh.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,path); + return; + } + + diropargs dirargs; + attrstat attrAndStat; + memcpy(dirargs.dir.data,fh,NFS_FHSIZE); + QCString tmpStr=QFile::encodeName(path); + dirargs.name=tmpStr.data(); + + kdDebug(7121)<<"calling rpc: FH: -"<<fh<<"- with name -"<<dirargs.name<<"-"<<endl; + + int clnt_stat = clnt_call(m_client, NFSPROC_GETATTR, + (xdrproc_t) xdr_diropargs, (char*)&dirargs, + (xdrproc_t) xdr_attrstat, (char*)&attrAndStat,total_timeout); + if (!checkForError(clnt_stat,attrAndStat.status,path)) return; + UDSEntry entry; + entry.clear(); + + UDSAtom atom; + + QString fileName, parentDir; + getLastPart(path, fileName, parentDir); + stripTrailingSlash(parentDir); + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = fileName; + entry.append( atom ); + + //is it a symlink ? + if (S_ISLNK(attrAndStat.attrstat_u.attributes.mode)) + { + kdDebug(7121)<<"it's a symlink !"<<endl; + nfs_fh nfsFH; + memcpy(nfsFH.data,fh,NFS_FHSIZE); + //get the link dest + readlinkres readLinkRes; + char nameBuf[NFS_MAXPATHLEN]; + readLinkRes.readlinkres_u.data=nameBuf; + + int clnt_stat=clnt_call(m_client, NFSPROC_READLINK, + (xdrproc_t) xdr_nfs_fh, (char*)&nfsFH, + (xdrproc_t) xdr_readlinkres, (char*)&readLinkRes,total_timeout); + if (!checkForError(clnt_stat,readLinkRes.status,path)) return; + kdDebug(7121)<<"link dest is -"<<readLinkRes.readlinkres_u.data<<"-"<<endl; + QCString linkDest(readLinkRes.readlinkres_u.data); + atom.m_uds = KIO::UDS_LINK_DEST; + atom.m_str = linkDest; + entry.append( atom ); + + bool isValid=isValidLink(parentDir,linkDest); + if (!isValid) + { + completeBadLinkUDSEntry(entry,attrAndStat.attrstat_u.attributes); + } + else + { + if (isAbsoluteLink(linkDest)) + { + completeAbsoluteLinkUDSEntry(entry,linkDest); + } + else + { + + tmpStr=QDir::cleanDirPath(parentDir+QString("/")+QString(linkDest)).latin1(); + diropargs dirargs; + dirargs.name=tmpStr.data(); + NFSFileHandle tmpFH; + tmpFH=getFileHandle(tmpStr); + memcpy(dirargs.dir.data,tmpFH,NFS_FHSIZE); + + kdDebug(7121)<<"calling rpc: FH: -"<<fh<<"- with name -"<<dirargs.name<<"-"<<endl; + clnt_stat = clnt_call(m_client, NFSPROC_GETATTR, + (xdrproc_t) xdr_diropargs, (char*)&dirargs, + (xdrproc_t) xdr_attrstat, (char*)&attrAndStat,total_timeout); + if (!checkForError(clnt_stat,attrAndStat.status,tmpStr)) return; + completeUDSEntry(entry,attrAndStat.attrstat_u.attributes); + } + } + } + else + completeUDSEntry(entry,attrAndStat.attrstat_u.attributes); + statEntry( entry ); + finished(); +} + +void NFSProtocol::completeAbsoluteLinkUDSEntry(UDSEntry& entry, const QCString& path) +{ + //taken from file.cc + struct stat buff; + if ( ::stat( path.data(), &buff ) == -1 ) return; + + UDSAtom atom; + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = buff.st_mode & S_IFMT; // extract file type + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = buff.st_mode & 07777; // extract permissions + entry.append( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = buff.st_size; + entry.append( atom ); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = buff.st_mtime; + entry.append( atom ); + + atom.m_uds = KIO::UDS_USER; + uid_t uid = buff.st_uid; + QString *temp = m_usercache.find( uid ); + + if ( !temp ) + { + struct passwd *user = getpwuid( uid ); + if ( user ) + { + m_usercache.insert( uid, new QString(QString::fromLatin1(user->pw_name)) ); + atom.m_str = user->pw_name; + } + else + atom.m_str = "???"; + } + else + atom.m_str = *temp; + entry.append( atom ); + + atom.m_uds = KIO::UDS_GROUP; + gid_t gid = buff.st_gid; + temp = m_groupcache.find( gid ); + if ( !temp ) + { + struct group *grp = getgrgid( gid ); + if ( grp ) + { + m_groupcache.insert( gid, new QString(QString::fromLatin1(grp->gr_name)) ); + atom.m_str = grp->gr_name; + } + else + atom.m_str = "???"; + } + else + atom.m_str = *temp; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS_TIME; + atom.m_long = buff.st_atime; + entry.append( atom ); + + atom.m_uds = KIO::UDS_CREATION_TIME; + atom.m_long = buff.st_ctime; + entry.append( atom ); +} + +void NFSProtocol::completeBadLinkUDSEntry(UDSEntry& entry, fattr& attributes) +{ + // It is a link pointing to nowhere + completeUDSEntry(entry,attributes); + + UDSAtom atom; + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFMT - 1; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = S_IRWXU | S_IRWXG | S_IRWXO; + entry.append( atom ); + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = 0L; + entry.append( atom ); +} + +void NFSProtocol::completeUDSEntry(UDSEntry& entry, fattr& attributes) +{ + UDSAtom atom; + + atom.m_uds = KIO::UDS_SIZE; + atom.m_long = attributes.size; + entry.append(atom); + + atom.m_uds = KIO::UDS_MODIFICATION_TIME; + atom.m_long = attributes.mtime.seconds; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS_TIME; + atom.m_long = attributes.atime.seconds; + entry.append( atom ); + + atom.m_uds = KIO::UDS_CREATION_TIME; + atom.m_long = attributes.ctime.seconds; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = (attributes.mode & 07777); + entry.append( atom ); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long =attributes.mode & S_IFMT; // extract file type + entry.append( atom ); + + atom.m_uds = KIO::UDS_USER; + uid_t uid = attributes.uid; + QString *temp = m_usercache.find( uid ); + if ( !temp ) + { + struct passwd *user = getpwuid( uid ); + if ( user ) + { + m_usercache.insert( uid, new QString(user->pw_name) ); + atom.m_str = user->pw_name; + } + else + atom.m_str = "???"; + } + else + atom.m_str = *temp; + entry.append( atom ); + + atom.m_uds = KIO::UDS_GROUP; + gid_t gid = attributes.gid; + temp = m_groupcache.find( gid ); + if ( !temp ) + { + struct group *grp = getgrgid( gid ); + if ( grp ) + { + m_groupcache.insert( gid, new QString(grp->gr_name) ); + atom.m_str = grp->gr_name; + } + else + atom.m_str = "???"; + } + else + atom.m_str = *temp; + entry.append( atom ); + +/* KIO::UDSEntry::ConstIterator it = entry.begin(); + for( ; it != entry.end(); it++ ) { + switch ((*it).m_uds) { + case KIO::UDS_FILE_TYPE: + kdDebug(7121) << "File Type : " << (mode_t)((*it).m_long) << endl; + break; + case KIO::UDS_ACCESS: + kdDebug(7121) << "Access permissions : " << (mode_t)((*it).m_long) << endl; + break; + case KIO::UDS_USER: + kdDebug(7121) << "User : " << ((*it).m_str.ascii() ) << endl; + break; + case KIO::UDS_GROUP: + kdDebug(7121) << "Group : " << ((*it).m_str.ascii() ) << endl; + break; + case KIO::UDS_NAME: + kdDebug(7121) << "Name : " << ((*it).m_str.ascii() ) << endl; + //m_strText = decodeFileName( (*it).m_str ); + break; + case KIO::UDS_URL: + kdDebug(7121) << "URL : " << ((*it).m_str.ascii() ) << endl; + break; + case KIO::UDS_MIME_TYPE: + kdDebug(7121) << "MimeType : " << ((*it).m_str.ascii() ) << endl; + break; + case KIO::UDS_LINK_DEST: + kdDebug(7121) << "LinkDest : " << ((*it).m_str.ascii() ) << endl; + break; + } + }*/ +} + +void NFSProtocol::setHost(const QString& host, int /*port*/, const QString& /*user*/, const QString& /*pass*/) +{ + kdDebug(7121)<<"setHost: -"<<host<<"-"<<endl; + if (host.isEmpty()) + { + error(ERR_UNKNOWN_HOST,""); + return; + } + if (host==m_currentHost) return; + m_currentHost=host; + m_handleCache.clear(); + m_exportedDirs.clear(); + closeConnection(); +} + +void NFSProtocol::mkdir( const KURL& url, int permissions ) +{ + kdDebug(7121)<<"mkdir"<<endl; + QString thePath( QFile::encodeName(url.path())); + stripTrailingSlash(thePath); + QString dirName, parentDir; + getLastPart(thePath, dirName, parentDir); + stripTrailingSlash(parentDir); + kdDebug(7121)<<"path: -"<<thePath<<"- dir: -"<<dirName<<"- parentDir: -"<<parentDir<<"-"<<endl; + if (isRoot(parentDir)) + { + error(ERR_WRITE_ACCESS_DENIED,thePath); + return; + } + NFSFileHandle fh=getFileHandle(parentDir); + if (fh.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,thePath); + return; + } + + createargs createArgs; + memcpy(createArgs.where.dir.data,fh,NFS_FHSIZE); + QCString tmpName=QFile::encodeName(dirName); + createArgs.where.name=tmpName.data(); + if (permissions==-1) createArgs.attributes.mode=0755; + else createArgs.attributes.mode=permissions; + + diropres dirres; + + int clnt_stat = clnt_call(m_client, NFSPROC_MKDIR, + (xdrproc_t) xdr_createargs, (char*)&createArgs, + (xdrproc_t) xdr_diropres, (char*)&dirres,total_timeout); + if (!checkForError(clnt_stat,dirres.status,thePath)) return; + finished(); +} + +bool NFSProtocol::checkForError(int clientStat, int nfsStat, const QString& text) +{ + if (clientStat!=RPC_SUCCESS) + { + kdDebug(7121)<<"rpc error: "<<clientStat<<endl; + //does this mapping make sense ? + error(ERR_CONNECTION_BROKEN,i18n("An RPC error occurred.")); + return FALSE; + } + if (nfsStat!=NFS_OK) + { + kdDebug(7121)<<"nfs error: "<<nfsStat<<endl; + switch (nfsStat) + { + case NFSERR_PERM: + error(ERR_ACCESS_DENIED,text); + break; + case NFSERR_NOENT: + error(ERR_DOES_NOT_EXIST,text); + break; + //does this mapping make sense ? + case NFSERR_IO: + error(ERR_INTERNAL_SERVER,text); + break; + //does this mapping make sense ? + case NFSERR_NXIO: + error(ERR_DOES_NOT_EXIST,text); + break; + case NFSERR_ACCES: + error(ERR_ACCESS_DENIED,text); + break; + case NFSERR_EXIST: + error(ERR_FILE_ALREADY_EXIST,text); + break; + //does this mapping make sense ? + case NFSERR_NODEV: + error(ERR_DOES_NOT_EXIST,text); + break; + case NFSERR_NOTDIR: + error(ERR_IS_FILE,text); + break; + case NFSERR_ISDIR: + error(ERR_IS_DIRECTORY,text); + break; + //does this mapping make sense ? + case NFSERR_FBIG: + error(ERR_INTERNAL_SERVER,text); + break; + //does this mapping make sense ? + case NFSERR_NOSPC: + error(ERR_INTERNAL_SERVER,i18n("No space left on device")); + break; + case NFSERR_ROFS: + error(ERR_COULD_NOT_WRITE,i18n("Read only file system")); + break; + case NFSERR_NAMETOOLONG: + error(ERR_INTERNAL_SERVER,i18n("Filename too long")); + break; + case NFSERR_NOTEMPTY: + error(ERR_COULD_NOT_RMDIR,text); + break; + //does this mapping make sense ? + case NFSERR_DQUOT: + error(ERR_INTERNAL_SERVER,i18n("Disk quota exceeded")); + break; + case NFSERR_STALE: + error(ERR_DOES_NOT_EXIST,text); + break; + default: + error(ERR_UNKNOWN,text); + break; + } + return FALSE; + } + return TRUE; +} + +void NFSProtocol::del( const KURL& url, bool isfile) +{ + QString thePath( QFile::encodeName(url.path())); + stripTrailingSlash(thePath); + + QString fileName, parentDir; + getLastPart(thePath, fileName, parentDir); + stripTrailingSlash(parentDir); + kdDebug(7121)<<"del(): path: -"<<thePath<<"- file -"<<fileName<<"- parentDir: -"<<parentDir<<"-"<<endl; + if (isRoot(parentDir)) + { + error(ERR_ACCESS_DENIED,thePath); + return; + } + + NFSFileHandle fh=getFileHandle(parentDir); + if (fh.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,thePath); + return; + } + + if (isfile) + { + kdDebug(7121)<<"Deleting file "<<thePath<<endl; + diropargs dirOpArgs; + memcpy(dirOpArgs.dir.data,fh,NFS_FHSIZE); + QCString tmpName=QFile::encodeName(fileName); + dirOpArgs.name=tmpName.data(); + + nfsstat nfsStat; + + int clnt_stat = clnt_call(m_client, NFSPROC_REMOVE, + (xdrproc_t) xdr_diropargs, (char*)&dirOpArgs, + (xdrproc_t) xdr_nfsstat, (char*)&nfsStat,total_timeout); + if (!checkForError(clnt_stat,nfsStat,thePath)) return; + kdDebug(7121)<<"removing "<<thePath<<" from cache"<<endl; + m_handleCache.remove(m_handleCache.find(thePath)); + finished(); + } + else + { + kdDebug(7121)<<"Deleting directory "<<thePath<<endl; + diropargs dirOpArgs; + memcpy(dirOpArgs.dir.data,fh,NFS_FHSIZE); + QCString tmpName=QFile::encodeName(fileName); + dirOpArgs.name=tmpName.data(); + + nfsstat nfsStat; + + int clnt_stat = clnt_call(m_client, NFSPROC_RMDIR, + (xdrproc_t) xdr_diropargs, (char*)&dirOpArgs, + (xdrproc_t) xdr_nfsstat, (char*)&nfsStat,total_timeout); + if (!checkForError(clnt_stat,nfsStat,thePath)) return; + kdDebug(7121)<<"removing "<<thePath<<" from cache"<<endl; + m_handleCache.remove(m_handleCache.find(thePath)); + finished(); + } +} + +void NFSProtocol::chmod( const KURL& url, int permissions ) +{ + QString thePath( QFile::encodeName(url.path())); + stripTrailingSlash(thePath); + kdDebug( 7121 ) << "chmod -"<< thePath << "-"<<endl; + if (isRoot(thePath) || isExportedDir(thePath)) + { + error(ERR_ACCESS_DENIED,thePath); + return; + } + + NFSFileHandle fh=getFileHandle(thePath); + if (fh.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,thePath); + return; + } + + sattrargs sAttrArgs; + memcpy(sAttrArgs.file.data,fh,NFS_FHSIZE); + sAttrArgs.attributes.uid=(unsigned int)-1; + sAttrArgs.attributes.gid=(unsigned int)-1; + sAttrArgs.attributes.size=(unsigned int)-1; + sAttrArgs.attributes.atime.seconds=(unsigned int)-1; + sAttrArgs.attributes.atime.useconds=(unsigned int)-1; + sAttrArgs.attributes.mtime.seconds=(unsigned int)-1; + sAttrArgs.attributes.mtime.useconds=(unsigned int)-1; + + sAttrArgs.attributes.mode=permissions; + + nfsstat nfsStat; + + int clnt_stat = clnt_call(m_client, NFSPROC_SETATTR, + (xdrproc_t) xdr_sattrargs, (char*)&sAttrArgs, + (xdrproc_t) xdr_nfsstat, (char*)&nfsStat,total_timeout); + if (!checkForError(clnt_stat,nfsStat,thePath)) return; + + finished(); +} + +void NFSProtocol::get( const KURL& url ) +{ + QString thePath( QFile::encodeName(url.path())); + kdDebug(7121)<<"get() -"<<thePath<<"-"<<endl; + NFSFileHandle fh=getFileHandle(thePath); + if (fh.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,thePath); + return; + } + readargs readArgs; + memcpy(readArgs.file.data,fh,NFS_FHSIZE); + readArgs.offset=0; + readArgs.count=NFS_MAXDATA; + readArgs.totalcount=NFS_MAXDATA; + readres readRes; + int offset(0); + char buf[NFS_MAXDATA]; + readRes.readres_u.reply.data.data_val=buf; + + QByteArray array; + do + { + int clnt_stat = clnt_call(m_client, NFSPROC_READ, + (xdrproc_t) xdr_readargs, (char*)&readArgs, + (xdrproc_t) xdr_readres, (char*)&readRes,total_timeout); + if (!checkForError(clnt_stat,readRes.status,thePath)) return; + if (readArgs.offset==0) + totalSize(readRes.readres_u.reply.attributes.size); + + offset=readRes.readres_u.reply.data.data_len; + //kdDebug(7121)<<"read "<<offset<<" bytes"<<endl; + readArgs.offset+=offset; + if (offset>0) + { + array.setRawData(readRes.readres_u.reply.data.data_val, offset); + data( array ); + array.resetRawData(readRes.readres_u.reply.data.data_val, offset); + + processedSize(readArgs.offset); + } + + } while (offset>0); + data( QByteArray() ); + finished(); +} + +//TODO the partial putting thing is not yet implemented +void NFSProtocol::put( const KURL& url, int _mode, bool _overwrite, bool /*_resume*/ ) +{ + QString destPath( QFile::encodeName(url.path())); + kdDebug( 7121 ) << "Put -" << destPath <<"-"<<endl; + /*QString dest_part( dest_orig ); + dest_part += ".part";*/ + + stripTrailingSlash(destPath); + QString parentDir, fileName; + getLastPart(destPath,fileName, parentDir); + if (isRoot(parentDir)) + { + error(ERR_WRITE_ACCESS_DENIED,destPath); + return; + } + + NFSFileHandle destFH; + destFH=getFileHandle(destPath); + kdDebug(7121)<<"file handle for -"<<destPath<<"- is "<<destFH<<endl; + + //the file exists and we don't want to overwrite + if ((!_overwrite) && (!destFH.isInvalid())) + { + error(ERR_FILE_ALREADY_EXIST,destPath); + return; + } + //TODO: is this correct ? + //we have to "create" the file anyway, no matter if it already + //exists or not + //if we don't create it new, written text will be, hmm, "inserted" + //in the existing file, i.e. a file could not become smaller, since + //write only overwrites or extends, but doesn't remove stuff from a file (aleXXX) + + kdDebug(7121)<<"creating the file -"<<fileName<<"-"<<endl; + NFSFileHandle parentFH; + parentFH=getFileHandle(parentDir); + //cerr<<"fh for parent dir: "<<parentFH<<endl; + //the directory doesn't exist + if (parentFH.isInvalid()) + { + kdDebug(7121)<<"parent directory -"<<parentDir<<"- does not exist"<<endl; + error(ERR_DOES_NOT_EXIST,parentDir); + return; + } + createargs createArgs; + memcpy(createArgs.where.dir.data,(const char*)parentFH,NFS_FHSIZE); + QCString tmpName=QFile::encodeName(fileName); + createArgs.where.name=tmpName.data(); + + //the mode is apparently ignored if the file already exists + if (_mode==-1) createArgs.attributes.mode=0644; + else createArgs.attributes.mode=_mode; + createArgs.attributes.uid=geteuid(); + createArgs.attributes.gid=getegid(); + //this is required, otherwise we are not able to write shorter files + createArgs.attributes.size=0; + //hmm, do we need something here ? I don't think so + createArgs.attributes.atime.seconds=(unsigned int)-1; + createArgs.attributes.atime.useconds=(unsigned int)-1; + createArgs.attributes.mtime.seconds=(unsigned int)-1; + createArgs.attributes.mtime.useconds=(unsigned int)-1; + + diropres dirOpRes; + int clnt_stat = clnt_call(m_client, NFSPROC_CREATE, + (xdrproc_t) xdr_createargs, (char*)&createArgs, + (xdrproc_t) xdr_diropres, (char*)&dirOpRes,total_timeout); + if (!checkForError(clnt_stat,dirOpRes.status,fileName)) return; + //we created the file successfully + //destFH=getFileHandle(destPath); + destFH=dirOpRes.diropres_u.diropres.file.data; + kdDebug(7121)<<"file -"<<fileName<<"- in dir -"<<parentDir<<"- created successfully"<<endl; + //cerr<<"with fh "<<destFH<<endl; + + //now we can put + int result; + // Loop until we got 0 (end of data) + writeargs writeArgs; + memcpy(writeArgs.file.data,(const char*)destFH,NFS_FHSIZE); + writeArgs.beginoffset=0; + writeArgs.totalcount=0; + writeArgs.offset=0; + attrstat attrStat; + int bytesWritten(0); + kdDebug(7121)<<"starting to put"<<endl; + do + { + QByteArray buffer; + dataReq(); // Request for data + result = readData( buffer ); + //kdDebug(7121)<<"received "<<result<<" bytes for putting"<<endl; + char * data=buffer.data(); + int bytesToWrite=buffer.size(); + int writeNow(0); + if (result > 0) + { + do + { + if (bytesToWrite>NFS_MAXDATA) + { + writeNow=NFS_MAXDATA; + } + else + { + writeNow=bytesToWrite; + }; + writeArgs.data.data_val=data; + writeArgs.data.data_len=writeNow; + + int clnt_stat = clnt_call(m_client, NFSPROC_WRITE, + (xdrproc_t) xdr_writeargs, (char*)&writeArgs, + (xdrproc_t) xdr_attrstat, (char*)&attrStat,total_timeout); + //kdDebug(7121)<<"written"<<endl; + if (!checkForError(clnt_stat,attrStat.status,fileName)) return; + bytesWritten+=writeNow; + writeArgs.offset=bytesWritten; + + //adjust the pointer + data=data+writeNow; + //decrease the rest + bytesToWrite-=writeNow; + } while (bytesToWrite>0); + } + } while ( result > 0 ); + finished(); +} + +void NFSProtocol::rename( const KURL &src, const KURL &dest, bool _overwrite ) +{ + QString srcPath( QFile::encodeName(src.path())); + QString destPath( QFile::encodeName(dest.path())); + stripTrailingSlash(srcPath); + stripTrailingSlash(destPath); + kdDebug(7121)<<"renaming -"<<srcPath<<"- to -"<<destPath<<"-"<<endl; + + if (isRoot(srcPath) || isExportedDir(srcPath)) + { + error(ERR_CANNOT_RENAME,srcPath); + return; + } + + if (!_overwrite) + { + NFSFileHandle testFH; + testFH=getFileHandle(destPath); + if (!testFH.isInvalid()) + { + error(ERR_FILE_ALREADY_EXIST,destPath); + return; + } + } + + QString srcFileName, srcParentDir, destFileName, destParentDir; + + getLastPart(srcPath, srcFileName, srcParentDir); + NFSFileHandle srcFH=getFileHandle(srcParentDir); + if (srcFH.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,srcParentDir); + return; + } + renameargs renameArgs; + memcpy(renameArgs.from.dir.data,srcFH,NFS_FHSIZE); + QCString tmpName=QFile::encodeName(srcFileName); + renameArgs.from.name=tmpName.data(); + + getLastPart(destPath, destFileName, destParentDir); + NFSFileHandle destFH=getFileHandle(destParentDir); + if (destFH.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,destParentDir); + return; + } + memcpy(renameArgs.to.dir.data,destFH,NFS_FHSIZE); + QCString tmpName2=QFile::encodeName(destFileName); + renameArgs.to.name=tmpName2.data(); + nfsstat nfsStat; + + int clnt_stat = clnt_call(m_client, NFSPROC_RENAME, + (xdrproc_t) xdr_renameargs, (char*)&renameArgs, + (xdrproc_t) xdr_nfsstat, (char*)&nfsStat,total_timeout); + if (!checkForError(clnt_stat,nfsStat,destPath)) return; + finished(); +} + +void NFSProtocol::copy( const KURL &src, const KURL &dest, int _mode, bool _overwrite ) +{ + //prepare the source + QString thePath( QFile::encodeName(src.path())); + stripTrailingSlash(thePath); + kdDebug( 7121 ) << "Copy to -" << thePath <<"-"<<endl; + NFSFileHandle fh=getFileHandle(thePath); + if (fh.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,thePath); + return; + }; + + //create the destination + QString destPath( QFile::encodeName(dest.path())); + stripTrailingSlash(destPath); + QString parentDir, fileName; + getLastPart(destPath,fileName, parentDir); + if (isRoot(parentDir)) + { + error(ERR_ACCESS_DENIED,destPath); + return; + } + NFSFileHandle destFH; + destFH=getFileHandle(destPath); + kdDebug(7121)<<"file handle for -"<<destPath<<"- is "<<destFH<<endl; + + //the file exists and we don't want to overwrite + if ((!_overwrite) && (!destFH.isInvalid())) + { + error(ERR_FILE_ALREADY_EXIST,destPath); + return; + } + //TODO: is this correct ? + //we have to "create" the file anyway, no matter if it already + //exists or not + //if we don't create it new, written text will be, hmm, "inserted" + //in the existing file, i.e. a file could not become smaller, since + //write only overwrites or extends, but doesn't remove stuff from a file + + kdDebug(7121)<<"creating the file -"<<fileName<<"-"<<endl; + NFSFileHandle parentFH; + parentFH=getFileHandle(parentDir); + //the directory doesn't exist + if (parentFH.isInvalid()) + { + kdDebug(7121)<<"parent directory -"<<parentDir<<"- does not exist"<<endl; + error(ERR_DOES_NOT_EXIST,parentDir); + return; + }; + createargs createArgs; + memcpy(createArgs.where.dir.data,(const char*)parentFH,NFS_FHSIZE); + QCString tmpName=QFile::encodeName(fileName); + createArgs.where.name=tmpName.data(); + if (_mode==-1) createArgs.attributes.mode=0644; + else createArgs.attributes.mode=_mode; + createArgs.attributes.uid=geteuid(); + createArgs.attributes.gid=getegid(); + createArgs.attributes.size=0; + createArgs.attributes.atime.seconds=(unsigned int)-1; + createArgs.attributes.atime.useconds=(unsigned int)-1; + createArgs.attributes.mtime.seconds=(unsigned int)-1; + createArgs.attributes.mtime.useconds=(unsigned int)-1; + + diropres dirOpRes; + int clnt_stat = clnt_call(m_client, NFSPROC_CREATE, + (xdrproc_t) xdr_createargs, (char*)&createArgs, + (xdrproc_t) xdr_diropres, (char*)&dirOpRes,total_timeout); + if (!checkForError(clnt_stat,dirOpRes.status,destPath)) return; + //we created the file successfully + destFH=dirOpRes.diropres_u.diropres.file.data; + kdDebug(7121)<<"file -"<<fileName<<"- in dir -"<<parentDir<<"- created successfully"<<endl; + + char buf[NFS_MAXDATA]; + writeargs writeArgs; + memcpy(writeArgs.file.data,(const char*)destFH,NFS_FHSIZE); + writeArgs.beginoffset=0; + writeArgs.totalcount=0; + writeArgs.offset=0; + writeArgs.data.data_val=buf; + attrstat attrStat; + + readargs readArgs; + memcpy(readArgs.file.data,fh,NFS_FHSIZE); + readArgs.offset=0; + readArgs.count=NFS_MAXDATA; + readArgs.totalcount=NFS_MAXDATA; + readres readRes; + readRes.readres_u.reply.data.data_val=buf; + + int bytesRead(0); + do + { + //first read + int clnt_stat = clnt_call(m_client, NFSPROC_READ, + (xdrproc_t) xdr_readargs, (char*)&readArgs, + (xdrproc_t) xdr_readres, (char*)&readRes,total_timeout); + if (!checkForError(clnt_stat,readRes.status,thePath)) return; + if (readArgs.offset==0) + totalSize(readRes.readres_u.reply.attributes.size); + + bytesRead=readRes.readres_u.reply.data.data_len; + //kdDebug(7121)<<"read "<<bytesRead<<" bytes"<<endl; + //then write + if (bytesRead>0) + { + readArgs.offset+=bytesRead; + + writeArgs.data.data_len=bytesRead; + + clnt_stat = clnt_call(m_client, NFSPROC_WRITE, + (xdrproc_t) xdr_writeargs, (char*)&writeArgs, + (xdrproc_t) xdr_attrstat, (char*)&attrStat,total_timeout); + //kdDebug(7121)<<"written"<<endl; + if (!checkForError(clnt_stat,attrStat.status,destPath)) return; + writeArgs.offset+=bytesRead; + } + } while (bytesRead>0); + + finished(); +} + +//TODO why isn't this even called ? +void NFSProtocol::symlink( const QString &target, const KURL &dest, bool ) +{ + kdDebug(7121)<<"symlinking "<<endl; + QString destPath=dest.path(); + stripTrailingSlash(destPath); + + QString parentDir, fileName; + getLastPart(destPath,fileName, parentDir); + kdDebug(7121)<<"symlinking "<<parentDir<<" "<<fileName<<" to "<<target<<endl; + NFSFileHandle fh=getFileHandle(parentDir); + if (fh.isInvalid()) + { + error(ERR_DOES_NOT_EXIST,parentDir); + return; + } + if (isRoot(parentDir)) + { + error(ERR_ACCESS_DENIED,destPath); + return; + } + + kdDebug(7121)<<"tach"<<endl; + QCString tmpStr=target.latin1(); + symlinkargs symLinkArgs; + symLinkArgs.to=tmpStr.data(); + memcpy(symLinkArgs.from.dir.data,(const char*)fh,NFS_FHSIZE); + QCString tmpStr2=QFile::encodeName(destPath); + symLinkArgs.from.name=tmpStr2.data(); + + nfsstat nfsStat; + int clnt_stat = clnt_call(m_client, NFSPROC_SYMLINK, + (xdrproc_t) xdr_symlinkargs, (char*)&symLinkArgs, + (xdrproc_t) xdr_nfsstat, (char*)&nfsStat,total_timeout); + if (!checkForError(clnt_stat,nfsStat,destPath)) return; + + finished(); + +} + +bool NFSProtocol::isValidLink(const QString& parentDir, const QString& linkDest) +{ + kdDebug(7121)<<"isValidLink: parent: "<<parentDir<<" link: "<<linkDest<<endl; + if (linkDest.isEmpty()) return FALSE; + if (isAbsoluteLink(linkDest)) + { + kdDebug(7121)<<"is an absolute link"<<endl; + return QFile::exists(linkDest); + } + else + { + kdDebug(7121)<<"is a relative link"<<endl; + QString absDest=parentDir+"/"+linkDest; + kdDebug(7121)<<"pointing abs to "<<absDest<<endl; + absDest=removeFirstPart(absDest); + kdDebug(7121)<<"removed first part "<<absDest<<endl; + absDest=QDir::cleanDirPath(absDest); + kdDebug(7121)<<"simplified to "<<absDest<<endl; + if (absDest.find("../")==0) + return FALSE; + + kdDebug(7121)<<"is inside the nfs tree"<<endl; + absDest=parentDir+"/"+linkDest; + absDest=QDir::cleanDirPath(absDest); + kdDebug(7121)<<"getting file handle of "<<absDest<<endl; + NFSFileHandle fh=getFileHandle(absDest); + return (!fh.isInvalid()); + } + return FALSE; +} + diff --git a/kioslave/nfs/kio_nfs.h b/kioslave/nfs/kio_nfs.h new file mode 100644 index 000000000..23eb0b467 --- /dev/null +++ b/kioslave/nfs/kio_nfs.h @@ -0,0 +1,109 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef KIO_NFS_H +#define KIO_NFS_H + +#include <kio/slavebase.h> +#include <kio/global.h> + +#include <qmap.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qintdict.h> +#include <qtimer.h> + +#define PORTMAP //this seems to be required to compile on Solaris +#include <rpc/rpc.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/time.h> + +class NFSFileHandle +{ + public: + NFSFileHandle(); + NFSFileHandle(const NFSFileHandle & handle); + ~NFSFileHandle(); + NFSFileHandle& operator= (const NFSFileHandle& src); + NFSFileHandle& operator= (const char* src); + operator const char* () const {return m_handle;} + bool isInvalid() const {return m_isInvalid;} + void setInvalid() {m_isInvalid=TRUE;} +// time_t age() const; + protected: + char m_handle[NFS_FHSIZE+1]; + bool m_isInvalid; +// time_t m_detectTime; +}; + +//ostream& operator<<(ostream&, const NFSFileHandle&); + +typedef QMap<QString,NFSFileHandle> NFSFileHandleMap; + + +class NFSProtocol : public KIO::SlaveBase +{ + public: + NFSProtocol (const QCString &pool, const QCString &app ); + virtual ~NFSProtocol(); + + virtual void openConnection(); + virtual void closeConnection(); + + virtual void setHost( const QString& host, int port, const QString& user, const QString& pass ); + + virtual void put( const KURL& url, int _mode,bool _overwrite, bool _resume ); + virtual void get( const KURL& url ); + virtual void listDir( const KURL& url); + virtual void symlink( const QString &target, const KURL &dest, bool ); + virtual void stat( const KURL & url); + virtual void mkdir( const KURL& url, int permissions ); + virtual void del( const KURL& url, bool isfile); + virtual void chmod(const KURL& url, int permissions ); + virtual void rename(const KURL &src, const KURL &dest, bool overwrite); + virtual void copy( const KURL& src, const KURL &dest, int mode, bool overwrite ); + protected: +// void createVirtualDirEntry(KIO::UDSEntry & entry); + bool checkForError(int clientStat, int nfsStat, const QString& text); + bool isExportedDir(const QString& path); + void completeUDSEntry(KIO::UDSEntry& entry, fattr& attributes); + void completeBadLinkUDSEntry(KIO::UDSEntry& entry, fattr& attributes); + void completeAbsoluteLinkUDSEntry(KIO::UDSEntry& entry, const QCString& path); + bool isValidLink(const QString& parentDir, const QString& linkDest); +// bool isAbsoluteLink(const QString& path); + + NFSFileHandle getFileHandle(QString path); + + NFSFileHandleMap m_handleCache; + QIntDict<QString> m_usercache; // maps long ==> QString * + QIntDict<QString> m_groupcache; + + QStringList m_exportedDirs; + QString m_currentHost; + CLIENT *m_client; + CLIENT *m_nfsClient; + timeval total_timeout; + timeval pertry_timeout; + int m_sock; + time_t m_lastCheck; + void checkForOldFHs(); +}; + +#endif diff --git a/kioslave/nfs/mount.h b/kioslave/nfs/mount.h new file mode 100644 index 000000000..c3b8c217f --- /dev/null +++ b/kioslave/nfs/mount.h @@ -0,0 +1,325 @@ +/* + * Please do not edit this file. + * It was generated using rpcgen. + */ + +#ifndef _MOUNT_H_RPCGEN +#define _MOUNT_H_RPCGEN + +#include <rpc/rpc.h> + +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user or with the express written consent of + * Sun Microsystems, Inc. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +/* + * Copyright (c) 1985, 1990 by Sun Microsystems, Inc. + */ + +/* from @(#)mount.x 1.3 91/03/11 TIRPC 1.0 */ +#ifndef _rpcsvc_mount_h +#define _rpcsvc_mount_h +#define MNTPATHLEN 1024 +#define MNTNAMLEN 255 +#define FHSIZE 32 + +typedef char fhandle[FHSIZE]; +#ifdef __cplusplus +extern "C" bool_t xdr_fhandle(XDR *, fhandle); +#elif __STDC__ +extern bool_t xdr_fhandle(XDR *, fhandle); +#else /* Old Style C */ +bool_t xdr_fhandle(); +#endif /* Old Style C */ + + +struct fhstatus { + u_int fhs_status; + union { + fhandle fhs_fhandle; + } fhstatus_u; +}; +typedef struct fhstatus fhstatus; +#ifdef __cplusplus +extern "C" bool_t xdr_fhstatus(XDR *, fhstatus*); +#elif __STDC__ +extern bool_t xdr_fhstatus(XDR *, fhstatus*); +#else /* Old Style C */ +bool_t xdr_fhstatus(); +#endif /* Old Style C */ + + +typedef char *dirpath; +#ifdef __cplusplus +extern "C" bool_t xdr_dirpath(XDR *, dirpath*); +#elif __STDC__ +extern bool_t xdr_dirpath(XDR *, dirpath*); +#else /* Old Style C */ +bool_t xdr_dirpath(); +#endif /* Old Style C */ + + +typedef char *name; +#ifdef __cplusplus +extern "C" bool_t xdr_name(XDR *, name*); +#elif __STDC__ +extern bool_t xdr_name(XDR *, name*); +#else /* Old Style C */ +bool_t xdr_name(); +#endif /* Old Style C */ + + +typedef struct mountbody *mountlist; +#ifdef __cplusplus +extern "C" bool_t xdr_mountlist(XDR *, mountlist*); +#elif __STDC__ +extern bool_t xdr_mountlist(XDR *, mountlist*); +#else /* Old Style C */ +bool_t xdr_mountlist(); +#endif /* Old Style C */ + + +struct mountbody { + name ml_hostname; + dirpath ml_directory; + mountlist ml_next; +}; +typedef struct mountbody mountbody; +#ifdef __cplusplus +extern "C" bool_t xdr_mountbody(XDR *, mountbody*); +#elif __STDC__ +extern bool_t xdr_mountbody(XDR *, mountbody*); +#else /* Old Style C */ +bool_t xdr_mountbody(); +#endif /* Old Style C */ + + +typedef struct groupnode *groups; +#ifdef __cplusplus +extern "C" bool_t xdr_groups(XDR *, groups*); +#elif __STDC__ +extern bool_t xdr_groups(XDR *, groups*); +#else /* Old Style C */ +bool_t xdr_groups(); +#endif /* Old Style C */ + + +struct groupnode { + name gr_name; + groups gr_next; +}; +typedef struct groupnode groupnode; +#ifdef __cplusplus +extern "C" bool_t xdr_groupnode(XDR *, groupnode*); +#elif __STDC__ +extern bool_t xdr_groupnode(XDR *, groupnode*); +#else /* Old Style C */ +bool_t xdr_groupnode(); +#endif /* Old Style C */ + + +typedef struct exportnode *exports; +#ifdef __cplusplus +extern "C" bool_t xdr_exports(XDR *, exports*); +#elif __STDC__ +extern bool_t xdr_exports(XDR *, exports*); +#else /* Old Style C */ +bool_t xdr_exports(); +#endif /* Old Style C */ + + +struct exportnode { + dirpath ex_dir; + groups ex_groups; + exports ex_next; +}; +typedef struct exportnode exportnode; +#ifdef __cplusplus +extern "C" bool_t xdr_exportnode(XDR *, exportnode*); +#elif __STDC__ +extern bool_t xdr_exportnode(XDR *, exportnode*); +#else /* Old Style C */ +bool_t xdr_exportnode(); +#endif /* Old Style C */ + + +struct ppathcnf { + int pc_link_max; + short pc_max_canon; + short pc_max_input; + short pc_name_max; + short pc_path_max; + short pc_pipe_buf; + u_char pc_vdisable; + char pc_xxx; + short pc_mask[2]; +}; +typedef struct ppathcnf ppathcnf; +#ifdef __cplusplus +extern "C" bool_t xdr_ppathcnf(XDR *, ppathcnf*); +#elif __STDC__ +extern bool_t xdr_ppathcnf(XDR *, ppathcnf*); +#else /* Old Style C */ +bool_t xdr_ppathcnf(); +#endif /* Old Style C */ + +#endif /*!_rpcsvc_mount_h*/ + +#define MOUNTPROG ((u_long)100005) +#define MOUNTVERS ((u_long)1) + +#ifdef __cplusplus +#define MOUNTPROC_NULL ((u_long)0) +extern "C" void * mountproc_null_1(void *, CLIENT *); +extern "C" void * mountproc_null_1_svc(void *, struct svc_req *); +#define MOUNTPROC_MNT ((u_long)1) +extern "C" fhstatus * mountproc_mnt_1(dirpath *, CLIENT *); +extern "C" fhstatus * mountproc_mnt_1_svc(dirpath *, struct svc_req *); +#define MOUNTPROC_DUMP ((u_long)2) +extern "C" mountlist * mountproc_dump_1(void *, CLIENT *); +extern "C" mountlist * mountproc_dump_1_svc(void *, struct svc_req *); +#define MOUNTPROC_UMNT ((u_long)3) +extern "C" void * mountproc_umnt_1(dirpath *, CLIENT *); +extern "C" void * mountproc_umnt_1_svc(dirpath *, struct svc_req *); +#define MOUNTPROC_UMNTALL ((u_long)4) +extern "C" void * mountproc_umntall_1(void *, CLIENT *); +extern "C" void * mountproc_umntall_1_svc(void *, struct svc_req *); +#define MOUNTPROC_EXPORT ((u_long)5) +extern "C" exports * mountproc_export_1(void *, CLIENT *); +extern "C" exports * mountproc_export_1_svc(void *, struct svc_req *); +#define MOUNTPROC_EXPORTALL ((u_long)6) +extern "C" exports * mountproc_exportall_1(void *, CLIENT *); +extern "C" exports * mountproc_exportall_1_svc(void *, struct svc_req *); + +#elif __STDC__ +#define MOUNTPROC_NULL ((u_long)0) +extern void * mountproc_null_1(void *, CLIENT *); +extern void * mountproc_null_1_svc(void *, struct svc_req *); +#define MOUNTPROC_MNT ((u_long)1) +extern fhstatus * mountproc_mnt_1(dirpath *, CLIENT *); +extern fhstatus * mountproc_mnt_1_svc(dirpath *, struct svc_req *); +#define MOUNTPROC_DUMP ((u_long)2) +extern mountlist * mountproc_dump_1(void *, CLIENT *); +extern mountlist * mountproc_dump_1_svc(void *, struct svc_req *); +#define MOUNTPROC_UMNT ((u_long)3) +extern void * mountproc_umnt_1(dirpath *, CLIENT *); +extern void * mountproc_umnt_1_svc(dirpath *, struct svc_req *); +#define MOUNTPROC_UMNTALL ((u_long)4) +extern void * mountproc_umntall_1(void *, CLIENT *); +extern void * mountproc_umntall_1_svc(void *, struct svc_req *); +#define MOUNTPROC_EXPORT ((u_long)5) +extern exports * mountproc_export_1(void *, CLIENT *); +extern exports * mountproc_export_1_svc(void *, struct svc_req *); +#define MOUNTPROC_EXPORTALL ((u_long)6) +extern exports * mountproc_exportall_1(void *, CLIENT *); +extern exports * mountproc_exportall_1_svc(void *, struct svc_req *); + +#else /* Old Style C */ +#define MOUNTPROC_NULL ((u_long)0) +extern void * mountproc_null_1(); +extern void * mountproc_null_1_svc(); +#define MOUNTPROC_MNT ((u_long)1) +extern fhstatus * mountproc_mnt_1(); +extern fhstatus * mountproc_mnt_1_svc(); +#define MOUNTPROC_DUMP ((u_long)2) +extern mountlist * mountproc_dump_1(); +extern mountlist * mountproc_dump_1_svc(); +#define MOUNTPROC_UMNT ((u_long)3) +extern void * mountproc_umnt_1(); +extern void * mountproc_umnt_1_svc(); +#define MOUNTPROC_UMNTALL ((u_long)4) +extern void * mountproc_umntall_1(); +extern void * mountproc_umntall_1_svc(); +#define MOUNTPROC_EXPORT ((u_long)5) +extern exports * mountproc_export_1(); +extern exports * mountproc_export_1_svc(); +#define MOUNTPROC_EXPORTALL ((u_long)6) +extern exports * mountproc_exportall_1(); +extern exports * mountproc_exportall_1_svc(); +#endif /* Old Style C */ +#define MOUNTVERS_POSIX ((u_long)2) + +#ifdef __cplusplus +extern "C" void * mountproc_null_2(void *, CLIENT *); +extern "C" void * mountproc_null_2_svc(void *, struct svc_req *); +extern "C" fhstatus * mountproc_mnt_2(dirpath *, CLIENT *); +extern "C" fhstatus * mountproc_mnt_2_svc(dirpath *, struct svc_req *); +extern "C" mountlist * mountproc_dump_2(void *, CLIENT *); +extern "C" mountlist * mountproc_dump_2_svc(void *, struct svc_req *); +extern "C" void * mountproc_umnt_2(dirpath *, CLIENT *); +extern "C" void * mountproc_umnt_2_svc(dirpath *, struct svc_req *); +extern "C" void * mountproc_umntall_2(void *, CLIENT *); +extern "C" void * mountproc_umntall_2_svc(void *, struct svc_req *); +extern "C" exports * mountproc_export_2(void *, CLIENT *); +extern "C" exports * mountproc_export_2_svc(void *, struct svc_req *); +extern "C" exports * mountproc_exportall_2(void *, CLIENT *); +extern "C" exports * mountproc_exportall_2_svc(void *, struct svc_req *); +#define MOUNTPROC_PATHCONF ((u_long)7) +extern "C" ppathcnf * mountproc_pathconf_2(dirpath *, CLIENT *); +extern "C" ppathcnf * mountproc_pathconf_2_svc(dirpath *, struct svc_req *); + +#elif __STDC__ +extern void * mountproc_null_2(void *, CLIENT *); +extern void * mountproc_null_2_svc(void *, struct svc_req *); +extern fhstatus * mountproc_mnt_2(dirpath *, CLIENT *); +extern fhstatus * mountproc_mnt_2_svc(dirpath *, struct svc_req *); +extern mountlist * mountproc_dump_2(void *, CLIENT *); +extern mountlist * mountproc_dump_2_svc(void *, struct svc_req *); +extern void * mountproc_umnt_2(dirpath *, CLIENT *); +extern void * mountproc_umnt_2_svc(dirpath *, struct svc_req *); +extern void * mountproc_umntall_2(void *, CLIENT *); +extern void * mountproc_umntall_2_svc(void *, struct svc_req *); +extern exports * mountproc_export_2(void *, CLIENT *); +extern exports * mountproc_export_2_svc(void *, struct svc_req *); +extern exports * mountproc_exportall_2(void *, CLIENT *); +extern exports * mountproc_exportall_2_svc(void *, struct svc_req *); +#define MOUNTPROC_PATHCONF ((u_long)7) +extern ppathcnf * mountproc_pathconf_2(dirpath *, CLIENT *); +extern ppathcnf * mountproc_pathconf_2_svc(dirpath *, struct svc_req *); + +#else /* Old Style C */ +extern void * mountproc_null_2(); +extern void * mountproc_null_2_svc(); +extern fhstatus * mountproc_mnt_2(); +extern fhstatus * mountproc_mnt_2_svc(); +extern mountlist * mountproc_dump_2(); +extern mountlist * mountproc_dump_2_svc(); +extern void * mountproc_umnt_2(); +extern void * mountproc_umnt_2_svc(); +extern void * mountproc_umntall_2(); +extern void * mountproc_umntall_2_svc(); +extern exports * mountproc_export_2(); +extern exports * mountproc_export_2_svc(); +extern exports * mountproc_exportall_2(); +extern exports * mountproc_exportall_2_svc(); +#define MOUNTPROC_PATHCONF ((u_long)7) +extern ppathcnf * mountproc_pathconf_2(); +extern ppathcnf * mountproc_pathconf_2_svc(); +#endif /* Old Style C */ + +#endif /* !_MOUNT_H_RPCGEN */ diff --git a/kioslave/nfs/mount.x b/kioslave/nfs/mount.x new file mode 100644 index 000000000..4aaf97de9 --- /dev/null +++ b/kioslave/nfs/mount.x @@ -0,0 +1,255 @@ +%/* +% * Sun RPC is a product of Sun Microsystems, Inc. and is provided for +% * unrestricted use provided that this legend is included on all tape +% * media and as a part of the software program in whole or part. Users +% * may copy or modify Sun RPC without charge, but are not authorized +% * to license or distribute it to anyone else except as part of a product or +% * program developed by the user or with the express written consent of +% * Sun Microsystems, Inc. +% * +% * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE +% * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR +% * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. +% * +% * Sun RPC is provided with no support and without any obligation on the +% * part of Sun Microsystems, Inc. to assist in its use, correction, +% * modification or enhancement. +% * +% * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE +% * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC +% * OR ANY PART THEREOF. +% * +% * In no event will Sun Microsystems, Inc. be liable for any lost revenue +% * or profits or other special, indirect and consequential damages, even if +% * Sun has been advised of the possibility of such damages. +% * +% * Sun Microsystems, Inc. +% * 2550 Garcia Avenue +% * Mountain View, California 94043 +% */ + +%/* +% * Copyright (c) 1985, 1990 by Sun Microsystems, Inc. +% */ +% +%/* from @(#)mount.x 1.3 91/03/11 TIRPC 1.0 */ + +/* + * Protocol description for the mount program + */ + +#ifdef RPC_HDR +%#ifndef _rpcsvc_mount_h +%#define _rpcsvc_mount_h +#endif + +const MNTPATHLEN = 1024; /* maximum bytes in a pathname argument */ +const MNTNAMLEN = 255; /* maximum bytes in a name argument */ +const FHSIZE = 32; /* size in bytes of a file handle */ + +/* + * The fhandle is the file handle that the server passes to the client. + * All file operations are done using the file handles to refer to a file + * or a directory. The file handle can contain whatever information the + * server needs to distinguish an individual file. + */ +typedef opaque fhandle[FHSIZE]; + +/* + * If a status of zero is returned, the call completed successfully, and + * a file handle for the directory follows. A non-zero status indicates + * some sort of error. The status corresponds with UNIX error numbers. + */ +union fhstatus switch (unsigned fhs_status) { +case 0: + fhandle fhs_fhandle; +default: + void; +}; + +/* + * The type dirpath is the pathname of a directory + */ +typedef string dirpath<MNTPATHLEN>; + +/* + * The type name is used for arbitrary names (hostnames, groupnames) + */ +typedef string name<MNTNAMLEN>; + +/* + * A list of who has what mounted + */ +typedef struct mountbody *mountlist; +struct mountbody { + name ml_hostname; + dirpath ml_directory; + mountlist ml_next; +}; + +/* + * A list of netgroups + */ +typedef struct groupnode *groups; +struct groupnode { + name gr_name; + groups gr_next; +}; + +/* + * A list of what is exported and to whom + */ +typedef struct exportnode *exports; +struct exportnode { + dirpath ex_dir; + groups ex_groups; + exports ex_next; +}; + +/* + * POSIX pathconf information + */ +struct ppathcnf { + int pc_link_max; /* max links allowed */ + short pc_max_canon; /* max line len for a tty */ + short pc_max_input; /* input a tty can eat all at once */ + short pc_name_max; /* max file name length (dir entry) */ + short pc_path_max; /* max path name length (/x/y/x/.. ) */ + short pc_pipe_buf; /* size of a pipe (bytes) */ + u_char pc_vdisable; /* safe char to turn off c_cc[i] */ + char pc_xxx; /* alignment padding; cc_t == char */ + short pc_mask[2]; /* validity and boolean bits */ +}; + +program MOUNTPROG { + /* + * Version one of the mount protocol communicates with version two + * of the NFS protocol. The only connecting point is the fhandle + * structure, which is the same for both protocols. + */ + version MOUNTVERS { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + fhstatus + MOUNTPROC_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC_EXPORT(void) = 5; + + /* + * Identical to MOUNTPROC_EXPORT above + */ + exports + MOUNTPROC_EXPORTALL(void) = 6; + } = 1; + + /* + * Version two of the mount protocol communicates with version two + * of the NFS protocol. + * The only difference from version one is the addition of a POSIX + * pathconf call. + */ + version MOUNTVERS_POSIX { + /* + * Does no work. It is made available in all RPC services + * to allow server reponse testing and timing + */ + void + MOUNTPROC_NULL(void) = 0; + + /* + * If fhs_status is 0, then fhs_fhandle contains the + * file handle for the directory. This file handle may + * be used in the NFS protocol. This procedure also adds + * a new entry to the mount list for this client mounting + * the directory. + * Unix authentication required. + */ + fhstatus + MOUNTPROC_MNT(dirpath) = 1; + + /* + * Returns the list of remotely mounted filesystems. The + * mountlist contains one entry for each hostname and + * directory pair. + */ + mountlist + MOUNTPROC_DUMP(void) = 2; + + /* + * Removes the mount list entry for the directory + * Unix authentication required. + */ + void + MOUNTPROC_UMNT(dirpath) = 3; + + /* + * Removes all of the mount list entries for this client + * Unix authentication required. + */ + void + MOUNTPROC_UMNTALL(void) = 4; + + /* + * Returns a list of all the exported filesystems, and which + * machines are allowed to import it. + */ + exports + MOUNTPROC_EXPORT(void) = 5; + + /* + * Identical to MOUNTPROC_EXPORT above + */ + exports + MOUNTPROC_EXPORTALL(void) = 6; + + /* + * POSIX pathconf info (Sun hack) + */ + ppathcnf + MOUNTPROC_PATHCONF(dirpath) = 7; + } = 2; +} = 100005; + +#ifdef RPC_HDR +%#endif /*!_rpcsvc_mount_h*/ +#endif diff --git a/kioslave/nfs/mount_xdr.c b/kioslave/nfs/mount_xdr.c new file mode 100644 index 000000000..38a43ca28 --- /dev/null +++ b/kioslave/nfs/mount_xdr.c @@ -0,0 +1,335 @@ +/* + * Please do not edit this file. + * It was generated using rpcgen. + */ + +#include <rpc/types.h> +#include <rpc/xdr.h> +#include <arpa/inet.h> + +#include "mount.h" +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user or with the express written consent of + * Sun Microsystems, Inc. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +/* + * Copyright (c) 1985, 1990 by Sun Microsystems, Inc. + */ + +/* from @(#)mount.x 1.3 91/03/11 TIRPC 1.0 */ + +bool_t +xdr_fhandle(XDR *xdrs, fhandle objp) +{ + + register int32_t *buf=buf; + + if (!xdr_opaque(xdrs, objp, FHSIZE)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_fhstatus(XDR *xdrs, fhstatus *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_u_int(xdrs, &objp->fhs_status)) { + return (FALSE); + } + switch (objp->fhs_status) { + case 0: + if (!xdr_fhandle(xdrs, objp->fhstatus_u.fhs_fhandle)) { + return (FALSE); + } + break; + default: + break; + } + return (TRUE); +} + +bool_t +xdr_dirpath(XDR *xdrs, dirpath *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_string(xdrs, objp, MNTPATHLEN)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_name(XDR *xdrs, name *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_string(xdrs, objp, MNTNAMLEN)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_mountlist(XDR *xdrs, mountlist *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_pointer(xdrs, (char **)objp, sizeof(struct mountbody), (xdrproc_t)xdr_mountbody)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_mountbody(XDR *xdrs, mountbody *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_name(xdrs, &objp->ml_hostname)) { + return (FALSE); + } + if (!xdr_dirpath(xdrs, &objp->ml_directory)) { + return (FALSE); + } + if (!xdr_mountlist(xdrs, &objp->ml_next)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_groups(XDR *xdrs, groups *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_pointer(xdrs, (char **)objp, sizeof(struct groupnode), (xdrproc_t)xdr_groupnode)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_groupnode(XDR *xdrs, groupnode *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_name(xdrs, &objp->gr_name)) { + return (FALSE); + } + if (!xdr_groups(xdrs, &objp->gr_next)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_exports(XDR *xdrs, exports *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_pointer(xdrs, (char **)objp, sizeof(struct exportnode), (xdrproc_t)xdr_exportnode)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_exportnode(XDR *xdrs, exportnode *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_dirpath(xdrs, &objp->ex_dir)) { + return (FALSE); + } + if (!xdr_groups(xdrs, &objp->ex_groups)) { + return (FALSE); + } + if (!xdr_exports(xdrs, &objp->ex_next)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_ppathcnf(XDR *xdrs, ppathcnf *objp) +{ + + register int32_t *buf=buf; + + int i=i; + + if (xdrs->x_op == XDR_ENCODE) { + buf = XDR_INLINE(xdrs, 6 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_int(xdrs, &objp->pc_link_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_max_canon)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_max_input)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_name_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_path_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_pipe_buf)) { + return (FALSE); + } + + } + else { + IXDR_PUT_U_INT32(buf,objp->pc_link_max); + IXDR_PUT_SHORT(buf,objp->pc_max_canon); + IXDR_PUT_SHORT(buf,objp->pc_max_input); + IXDR_PUT_SHORT(buf,objp->pc_name_max); + IXDR_PUT_SHORT(buf,objp->pc_path_max); + IXDR_PUT_SHORT(buf,objp->pc_pipe_buf); + } + if (!xdr_u_char(xdrs, &objp->pc_vdisable)) { + return (FALSE); + } + if (!xdr_char(xdrs, &objp->pc_xxx)) { + return (FALSE); + } + buf = XDR_INLINE(xdrs, 2 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_vector(xdrs, (char *)objp->pc_mask, 2, sizeof(short), (xdrproc_t)xdr_short)) { + return (FALSE); + } + + } + else { + { register short *genp; + for ( i = 0,genp=objp->pc_mask; + i < 2; i++){ + IXDR_PUT_SHORT(buf,*genp++); + } + }; + } + + return (TRUE); + } else if (xdrs->x_op == XDR_DECODE) { + buf = XDR_INLINE(xdrs,6 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_int(xdrs, &objp->pc_link_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_max_canon)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_max_input)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_name_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_path_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_pipe_buf)) { + return (FALSE); + } + + } + else { + objp->pc_link_max = IXDR_GET_U_INT32(buf); + objp->pc_max_canon = IXDR_GET_SHORT(buf); + objp->pc_max_input = IXDR_GET_SHORT(buf); + objp->pc_name_max = IXDR_GET_SHORT(buf); + objp->pc_path_max = IXDR_GET_SHORT(buf); + objp->pc_pipe_buf = IXDR_GET_SHORT(buf); + } + if (!xdr_u_char(xdrs, &objp->pc_vdisable)) { + return (FALSE); + } + if (!xdr_char(xdrs, &objp->pc_xxx)) { + return (FALSE); + } + buf = XDR_INLINE(xdrs, 2 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_vector(xdrs, (char *)objp->pc_mask, 2, sizeof(short), (xdrproc_t)xdr_short)) { + return (FALSE); + } + + } + else { + { register short *genp; + for ( i = 0,genp=objp->pc_mask; + i < 2; i++){ + *genp++ = IXDR_GET_SHORT(buf); + } + }; + } + return(TRUE); + } + + if (!xdr_int(xdrs, &objp->pc_link_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_max_canon)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_max_input)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_name_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_path_max)) { + return (FALSE); + } + if (!xdr_short(xdrs, &objp->pc_pipe_buf)) { + return (FALSE); + } + if (!xdr_u_char(xdrs, &objp->pc_vdisable)) { + return (FALSE); + } + if (!xdr_char(xdrs, &objp->pc_xxx)) { + return (FALSE); + } + if (!xdr_vector(xdrs, (char *)objp->pc_mask, 2, sizeof(short), (xdrproc_t)xdr_short)) { + return (FALSE); + } + return (TRUE); +} diff --git a/kioslave/nfs/nfs.protocol b/kioslave/nfs/nfs.protocol new file mode 100644 index 000000000..377b01505 --- /dev/null +++ b/kioslave/nfs/nfs.protocol @@ -0,0 +1,14 @@ +[Protocol] +exec=kio_nfs +protocol=nfs +input=none +output=filesystem +listing=Name,Type,Size,Date,AccessDate,CreationDate,Access,Owner,Group,Link +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +DocPath=kioslave/nfs.html +Icon=nfs_mount diff --git a/kioslave/nfs/nfs_prot.h b/kioslave/nfs/nfs_prot.h new file mode 100644 index 000000000..5ed218f20 --- /dev/null +++ b/kioslave/nfs/nfs_prot.h @@ -0,0 +1,699 @@ +/* + * Please do not edit this file. + * It was generated using rpcgen. + */ + +#ifndef _NFS_PROT_H_RPCGEN +#define _NFS_PROT_H_RPCGEN + +#include <rpc/rpc.h> + +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user or with the express written consent of + * Sun Microsystems, Inc. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +/* + * Copyright (c) 1987, 1990 by Sun Microsystems, Inc. + */ + +/* from @(#)nfs_prot.x 1.3 91/03/11 TIRPC 1.0 */ +#ifndef _rpcsvc_nfs_prot_h +#define _rpcsvc_nfs_prot_h +#define NFS_PORT 2049 +#define NFS_MAXDATA 8192 +#define NFS_MAXPATHLEN 1024 +#define NFS_MAXNAMLEN 255 +#define NFS_FHSIZE 32 +#define NFS_COOKIESIZE 4 +#define NFS_FIFO_DEV -1 +#define NFSMODE_FMT 0170000 +#define NFSMODE_DIR 0040000 +#define NFSMODE_CHR 0020000 +#define NFSMODE_BLK 0060000 +#define NFSMODE_REG 0100000 +#define NFSMODE_LNK 0120000 +#define NFSMODE_SOCK 0140000 +#define NFSMODE_FIFO 0010000 + +enum nfsstat { + NFS_OK = 0, + NFSERR_PERM = 1, + NFSERR_NOENT = 2, + NFSERR_IO = 5, + NFSERR_NXIO = 6, + NFSERR_ACCES = 13, + NFSERR_EXIST = 17, + NFSERR_NODEV = 19, + NFSERR_NOTDIR = 20, + NFSERR_ISDIR = 21, + NFSERR_INVAL = 22, + NFSERR_FBIG = 27, + NFSERR_NOSPC = 28, + NFSERR_ROFS = 30, + NFSERR_NAMETOOLONG = 63, + NFSERR_NOTEMPTY = 66, + NFSERR_DQUOT = 69, + NFSERR_STALE = 70, + NFSERR_WFLUSH = 99 +}; +typedef enum nfsstat nfsstat; +#ifdef __cplusplus +extern "C" bool_t xdr_nfsstat(XDR *, nfsstat*); +#elif __STDC__ +extern bool_t xdr_nfsstat(XDR *, nfsstat*); +#else /* Old Style C */ +bool_t xdr_nfsstat(); +#endif /* Old Style C */ + + +enum ftype { + NFNON = 0, + NFREG = 1, + NFDIR = 2, + NFBLK = 3, + NFCHR = 4, + NFLNK = 5, + NFSOCK = 6, + NFBAD = 7, + NFFIFO = 8 +}; +typedef enum ftype ftype; +#ifdef __cplusplus +extern "C" bool_t xdr_ftype(XDR *, ftype*); +#elif __STDC__ +extern bool_t xdr_ftype(XDR *, ftype*); +#else /* Old Style C */ +bool_t xdr_ftype(); +#endif /* Old Style C */ + + +struct nfs_fh { + char data[NFS_FHSIZE]; +}; +typedef struct nfs_fh nfs_fh; +#ifdef __cplusplus +extern "C" bool_t xdr_nfs_fh(XDR *, nfs_fh*); +#elif __STDC__ +extern bool_t xdr_nfs_fh(XDR *, nfs_fh*); +#else /* Old Style C */ +bool_t xdr_nfs_fh(); +#endif /* Old Style C */ + + +struct nfstime { + u_int seconds; + u_int useconds; +}; +typedef struct nfstime nfstime; +#ifdef __cplusplus +extern "C" bool_t xdr_nfstime(XDR *, nfstime*); +#elif __STDC__ +extern bool_t xdr_nfstime(XDR *, nfstime*); +#else /* Old Style C */ +bool_t xdr_nfstime(); +#endif /* Old Style C */ + + +struct fattr { + ftype type; + u_int mode; + u_int nlink; + u_int uid; + u_int gid; + u_int size; + u_int blocksize; + u_int rdev; + u_int blocks; + u_int fsid; + u_int fileid; + nfstime atime; + nfstime mtime; + nfstime ctime; +}; +typedef struct fattr fattr; +#ifdef __cplusplus +extern "C" bool_t xdr_fattr(XDR *, fattr*); +#elif __STDC__ +extern bool_t xdr_fattr(XDR *, fattr*); +#else /* Old Style C */ +bool_t xdr_fattr(); +#endif /* Old Style C */ + + +struct sattr { + u_int mode; + u_int uid; + u_int gid; + u_int size; + nfstime atime; + nfstime mtime; +}; +typedef struct sattr sattr; +#ifdef __cplusplus +extern "C" bool_t xdr_sattr(XDR *, sattr*); +#elif __STDC__ +extern bool_t xdr_sattr(XDR *, sattr*); +#else /* Old Style C */ +bool_t xdr_sattr(); +#endif /* Old Style C */ + + +typedef char *filename; +#ifdef __cplusplus +extern "C" bool_t xdr_filename(XDR *, filename*); +#elif __STDC__ +extern bool_t xdr_filename(XDR *, filename*); +#else /* Old Style C */ +bool_t xdr_filename(); +#endif /* Old Style C */ + + +typedef char *nfspath; +#ifdef __cplusplus +extern "C" bool_t xdr_nfspath(XDR *, nfspath*); +#elif __STDC__ +extern bool_t xdr_nfspath(XDR *, nfspath*); +#else /* Old Style C */ +bool_t xdr_nfspath(); +#endif /* Old Style C */ + + +struct attrstat { + nfsstat status; + union { + fattr attributes; + } attrstat_u; +}; +typedef struct attrstat attrstat; +#ifdef __cplusplus +extern "C" bool_t xdr_attrstat(XDR *, attrstat*); +#elif __STDC__ +extern bool_t xdr_attrstat(XDR *, attrstat*); +#else /* Old Style C */ +bool_t xdr_attrstat(); +#endif /* Old Style C */ + + +struct sattrargs { + nfs_fh file; + sattr attributes; +}; +typedef struct sattrargs sattrargs; +#ifdef __cplusplus +extern "C" bool_t xdr_sattrargs(XDR *, sattrargs*); +#elif __STDC__ +extern bool_t xdr_sattrargs(XDR *, sattrargs*); +#else /* Old Style C */ +bool_t xdr_sattrargs(); +#endif /* Old Style C */ + + +struct diropargs { + nfs_fh dir; + filename name; +}; +typedef struct diropargs diropargs; +#ifdef __cplusplus +extern "C" bool_t xdr_diropargs(XDR *, diropargs*); +#elif __STDC__ +extern bool_t xdr_diropargs(XDR *, diropargs*); +#else /* Old Style C */ +bool_t xdr_diropargs(); +#endif /* Old Style C */ + + +struct diropokres { + nfs_fh file; + fattr attributes; +}; +typedef struct diropokres diropokres; +#ifdef __cplusplus +extern "C" bool_t xdr_diropokres(XDR *, diropokres*); +#elif __STDC__ +extern bool_t xdr_diropokres(XDR *, diropokres*); +#else /* Old Style C */ +bool_t xdr_diropokres(); +#endif /* Old Style C */ + + +struct diropres { + nfsstat status; + union { + diropokres diropres; + } diropres_u; +}; +typedef struct diropres diropres; +#ifdef __cplusplus +extern "C" bool_t xdr_diropres(XDR *, diropres*); +#elif __STDC__ +extern bool_t xdr_diropres(XDR *, diropres*); +#else /* Old Style C */ +bool_t xdr_diropres(); +#endif /* Old Style C */ + + +struct readlinkres { + nfsstat status; + union { + nfspath data; + } readlinkres_u; +}; +typedef struct readlinkres readlinkres; +#ifdef __cplusplus +extern "C" bool_t xdr_readlinkres(XDR *, readlinkres*); +#elif __STDC__ +extern bool_t xdr_readlinkres(XDR *, readlinkres*); +#else /* Old Style C */ +bool_t xdr_readlinkres(); +#endif /* Old Style C */ + + +struct readargs { + nfs_fh file; + u_int offset; + u_int count; + u_int totalcount; +}; +typedef struct readargs readargs; +#ifdef __cplusplus +extern "C" bool_t xdr_readargs(XDR *, readargs*); +#elif __STDC__ +extern bool_t xdr_readargs(XDR *, readargs*); +#else /* Old Style C */ +bool_t xdr_readargs(); +#endif /* Old Style C */ + + +struct readokres { + fattr attributes; + struct { + u_int data_len; + char *data_val; + } data; +}; +typedef struct readokres readokres; +#ifdef __cplusplus +extern "C" bool_t xdr_readokres(XDR *, readokres*); +#elif __STDC__ +extern bool_t xdr_readokres(XDR *, readokres*); +#else /* Old Style C */ +bool_t xdr_readokres(); +#endif /* Old Style C */ + + +struct readres { + nfsstat status; + union { + readokres reply; + } readres_u; +}; +typedef struct readres readres; +#ifdef __cplusplus +extern "C" bool_t xdr_readres(XDR *, readres*); +#elif __STDC__ +extern bool_t xdr_readres(XDR *, readres*); +#else /* Old Style C */ +bool_t xdr_readres(); +#endif /* Old Style C */ + + +struct writeargs { + nfs_fh file; + u_int beginoffset; + u_int offset; + u_int totalcount; + struct { + u_int data_len; + char *data_val; + } data; +}; +typedef struct writeargs writeargs; +#ifdef __cplusplus +extern "C" bool_t xdr_writeargs(XDR *, writeargs*); +#elif __STDC__ +extern bool_t xdr_writeargs(XDR *, writeargs*); +#else /* Old Style C */ +bool_t xdr_writeargs(); +#endif /* Old Style C */ + + +struct createargs { + diropargs where; + sattr attributes; +}; +typedef struct createargs createargs; +#ifdef __cplusplus +extern "C" bool_t xdr_createargs(XDR *, createargs*); +#elif __STDC__ +extern bool_t xdr_createargs(XDR *, createargs*); +#else /* Old Style C */ +bool_t xdr_createargs(); +#endif /* Old Style C */ + + +struct renameargs { + diropargs from; + diropargs to; +}; +typedef struct renameargs renameargs; +#ifdef __cplusplus +extern "C" bool_t xdr_renameargs(XDR *, renameargs*); +#elif __STDC__ +extern bool_t xdr_renameargs(XDR *, renameargs*); +#else /* Old Style C */ +bool_t xdr_renameargs(); +#endif /* Old Style C */ + + +struct linkargs { + nfs_fh from; + diropargs to; +}; +typedef struct linkargs linkargs; +#ifdef __cplusplus +extern "C" bool_t xdr_linkargs(XDR *, linkargs*); +#elif __STDC__ +extern bool_t xdr_linkargs(XDR *, linkargs*); +#else /* Old Style C */ +bool_t xdr_linkargs(); +#endif /* Old Style C */ + + +struct symlinkargs { + diropargs from; + nfspath to; + sattr attributes; +}; +typedef struct symlinkargs symlinkargs; +#ifdef __cplusplus +extern "C" bool_t xdr_symlinkargs(XDR *, symlinkargs*); +#elif __STDC__ +extern bool_t xdr_symlinkargs(XDR *, symlinkargs*); +#else /* Old Style C */ +bool_t xdr_symlinkargs(); +#endif /* Old Style C */ + + +typedef char nfscookie[NFS_COOKIESIZE]; +#ifdef __cplusplus +extern "C" bool_t xdr_nfscookie(XDR *, nfscookie); +#elif __STDC__ +extern bool_t xdr_nfscookie(XDR *, nfscookie); +#else /* Old Style C */ +bool_t xdr_nfscookie(); +#endif /* Old Style C */ + + +struct readdirargs { + nfs_fh dir; + nfscookie cookie; + u_int count; +}; +typedef struct readdirargs readdirargs; +#ifdef __cplusplus +extern "C" bool_t xdr_readdirargs(XDR *, readdirargs*); +#elif __STDC__ +extern bool_t xdr_readdirargs(XDR *, readdirargs*); +#else /* Old Style C */ +bool_t xdr_readdirargs(); +#endif /* Old Style C */ + + +struct entry { + u_int fileid; + filename name; + nfscookie cookie; + struct entry *nextentry; +}; +typedef struct entry entry; +#ifdef __cplusplus +extern "C" bool_t xdr_entry(XDR *, entry*); +#elif __STDC__ +extern bool_t xdr_entry(XDR *, entry*); +#else /* Old Style C */ +bool_t xdr_entry(); +#endif /* Old Style C */ + + +struct dirlist { + entry *entries; + bool_t eof; +}; +typedef struct dirlist dirlist; +#ifdef __cplusplus +extern "C" bool_t xdr_dirlist(XDR *, dirlist*); +#elif __STDC__ +extern bool_t xdr_dirlist(XDR *, dirlist*); +#else /* Old Style C */ +bool_t xdr_dirlist(); +#endif /* Old Style C */ + + +struct readdirres { + nfsstat status; + union { + dirlist reply; + } readdirres_u; +}; +typedef struct readdirres readdirres; +#ifdef __cplusplus +extern "C" bool_t xdr_readdirres(XDR *, readdirres*); +#elif __STDC__ +extern bool_t xdr_readdirres(XDR *, readdirres*); +#else /* Old Style C */ +bool_t xdr_readdirres(); +#endif /* Old Style C */ + + +struct statfsokres { + u_int tsize; + u_int bsize; + u_int blocks; + u_int bfree; + u_int bavail; +}; +typedef struct statfsokres statfsokres; +#ifdef __cplusplus +extern "C" bool_t xdr_statfsokres(XDR *, statfsokres*); +#elif __STDC__ +extern bool_t xdr_statfsokres(XDR *, statfsokres*); +#else /* Old Style C */ +bool_t xdr_statfsokres(); +#endif /* Old Style C */ + + +struct statfsres { + nfsstat status; + union { + statfsokres reply; + } statfsres_u; +}; +typedef struct statfsres statfsres; +#ifdef __cplusplus +extern "C" bool_t xdr_statfsres(XDR *, statfsres*); +#elif __STDC__ +extern bool_t xdr_statfsres(XDR *, statfsres*); +#else /* Old Style C */ +bool_t xdr_statfsres(); +#endif /* Old Style C */ + +#endif /*!_rpcsvc_nfs_prot_h*/ + +#define NFS_PROGRAM ((u_long)100003) +#define NFS_VERSION ((u_long)2) + +#ifdef __cplusplus +#define NFSPROC_NULL ((u_long)0) +extern "C" void * nfsproc_null_2(void *, CLIENT *); +extern "C" void * nfsproc_null_2_svc(void *, struct svc_req *); +#define NFSPROC_GETATTR ((u_long)1) +extern "C" attrstat * nfsproc_getattr_2(nfs_fh *, CLIENT *); +extern "C" attrstat * nfsproc_getattr_2_svc(nfs_fh *, struct svc_req *); +#define NFSPROC_SETATTR ((u_long)2) +extern "C" attrstat * nfsproc_setattr_2(sattrargs *, CLIENT *); +extern "C" attrstat * nfsproc_setattr_2_svc(sattrargs *, struct svc_req *); +#define NFSPROC_ROOT ((u_long)3) +extern "C" void * nfsproc_root_2(void *, CLIENT *); +extern "C" void * nfsproc_root_2_svc(void *, struct svc_req *); +#define NFSPROC_LOOKUP ((u_long)4) +extern "C" diropres * nfsproc_lookup_2(diropargs *, CLIENT *); +extern "C" diropres * nfsproc_lookup_2_svc(diropargs *, struct svc_req *); +#define NFSPROC_READLINK ((u_long)5) +extern "C" readlinkres * nfsproc_readlink_2(nfs_fh *, CLIENT *); +extern "C" readlinkres * nfsproc_readlink_2_svc(nfs_fh *, struct svc_req *); +#define NFSPROC_READ ((u_long)6) +extern "C" readres * nfsproc_read_2(readargs *, CLIENT *); +extern "C" readres * nfsproc_read_2_svc(readargs *, struct svc_req *); +#define NFSPROC_WRITECACHE ((u_long)7) +extern "C" void * nfsproc_writecache_2(void *, CLIENT *); +extern "C" void * nfsproc_writecache_2_svc(void *, struct svc_req *); +#define NFSPROC_WRITE ((u_long)8) +extern "C" attrstat * nfsproc_write_2(writeargs *, CLIENT *); +extern "C" attrstat * nfsproc_write_2_svc(writeargs *, struct svc_req *); +#define NFSPROC_CREATE ((u_long)9) +extern "C" diropres * nfsproc_create_2(createargs *, CLIENT *); +extern "C" diropres * nfsproc_create_2_svc(createargs *, struct svc_req *); +#define NFSPROC_REMOVE ((u_long)10) +extern "C" nfsstat * nfsproc_remove_2(diropargs *, CLIENT *); +extern "C" nfsstat * nfsproc_remove_2_svc(diropargs *, struct svc_req *); +#define NFSPROC_RENAME ((u_long)11) +extern "C" nfsstat * nfsproc_rename_2(renameargs *, CLIENT *); +extern "C" nfsstat * nfsproc_rename_2_svc(renameargs *, struct svc_req *); +#define NFSPROC_LINK ((u_long)12) +extern "C" nfsstat * nfsproc_link_2(linkargs *, CLIENT *); +extern "C" nfsstat * nfsproc_link_2_svc(linkargs *, struct svc_req *); +#define NFSPROC_SYMLINK ((u_long)13) +extern "C" nfsstat * nfsproc_symlink_2(symlinkargs *, CLIENT *); +extern "C" nfsstat * nfsproc_symlink_2_svc(symlinkargs *, struct svc_req *); +#define NFSPROC_MKDIR ((u_long)14) +extern "C" diropres * nfsproc_mkdir_2(createargs *, CLIENT *); +extern "C" diropres * nfsproc_mkdir_2_svc(createargs *, struct svc_req *); +#define NFSPROC_RMDIR ((u_long)15) +extern "C" nfsstat * nfsproc_rmdir_2(diropargs *, CLIENT *); +extern "C" nfsstat * nfsproc_rmdir_2_svc(diropargs *, struct svc_req *); +#define NFSPROC_READDIR ((u_long)16) +extern "C" readdirres * nfsproc_readdir_2(readdirargs *, CLIENT *); +extern "C" readdirres * nfsproc_readdir_2_svc(readdirargs *, struct svc_req *); +#define NFSPROC_STATFS ((u_long)17) +extern "C" statfsres * nfsproc_statfs_2(nfs_fh *, CLIENT *); +extern "C" statfsres * nfsproc_statfs_2_svc(nfs_fh *, struct svc_req *); + +#elif __STDC__ +#define NFSPROC_NULL ((u_long)0) +extern void * nfsproc_null_2(void *, CLIENT *); +extern void * nfsproc_null_2_svc(void *, struct svc_req *); +#define NFSPROC_GETATTR ((u_long)1) +extern attrstat * nfsproc_getattr_2(nfs_fh *, CLIENT *); +extern attrstat * nfsproc_getattr_2_svc(nfs_fh *, struct svc_req *); +#define NFSPROC_SETATTR ((u_long)2) +extern attrstat * nfsproc_setattr_2(sattrargs *, CLIENT *); +extern attrstat * nfsproc_setattr_2_svc(sattrargs *, struct svc_req *); +#define NFSPROC_ROOT ((u_long)3) +extern void * nfsproc_root_2(void *, CLIENT *); +extern void * nfsproc_root_2_svc(void *, struct svc_req *); +#define NFSPROC_LOOKUP ((u_long)4) +extern diropres * nfsproc_lookup_2(diropargs *, CLIENT *); +extern diropres * nfsproc_lookup_2_svc(diropargs *, struct svc_req *); +#define NFSPROC_READLINK ((u_long)5) +extern readlinkres * nfsproc_readlink_2(nfs_fh *, CLIENT *); +extern readlinkres * nfsproc_readlink_2_svc(nfs_fh *, struct svc_req *); +#define NFSPROC_READ ((u_long)6) +extern readres * nfsproc_read_2(readargs *, CLIENT *); +extern readres * nfsproc_read_2_svc(readargs *, struct svc_req *); +#define NFSPROC_WRITECACHE ((u_long)7) +extern void * nfsproc_writecache_2(void *, CLIENT *); +extern void * nfsproc_writecache_2_svc(void *, struct svc_req *); +#define NFSPROC_WRITE ((u_long)8) +extern attrstat * nfsproc_write_2(writeargs *, CLIENT *); +extern attrstat * nfsproc_write_2_svc(writeargs *, struct svc_req *); +#define NFSPROC_CREATE ((u_long)9) +extern diropres * nfsproc_create_2(createargs *, CLIENT *); +extern diropres * nfsproc_create_2_svc(createargs *, struct svc_req *); +#define NFSPROC_REMOVE ((u_long)10) +extern nfsstat * nfsproc_remove_2(diropargs *, CLIENT *); +extern nfsstat * nfsproc_remove_2_svc(diropargs *, struct svc_req *); +#define NFSPROC_RENAME ((u_long)11) +extern nfsstat * nfsproc_rename_2(renameargs *, CLIENT *); +extern nfsstat * nfsproc_rename_2_svc(renameargs *, struct svc_req *); +#define NFSPROC_LINK ((u_long)12) +extern nfsstat * nfsproc_link_2(linkargs *, CLIENT *); +extern nfsstat * nfsproc_link_2_svc(linkargs *, struct svc_req *); +#define NFSPROC_SYMLINK ((u_long)13) +extern nfsstat * nfsproc_symlink_2(symlinkargs *, CLIENT *); +extern nfsstat * nfsproc_symlink_2_svc(symlinkargs *, struct svc_req *); +#define NFSPROC_MKDIR ((u_long)14) +extern diropres * nfsproc_mkdir_2(createargs *, CLIENT *); +extern diropres * nfsproc_mkdir_2_svc(createargs *, struct svc_req *); +#define NFSPROC_RMDIR ((u_long)15) +extern nfsstat * nfsproc_rmdir_2(diropargs *, CLIENT *); +extern nfsstat * nfsproc_rmdir_2_svc(diropargs *, struct svc_req *); +#define NFSPROC_READDIR ((u_long)16) +extern readdirres * nfsproc_readdir_2(readdirargs *, CLIENT *); +extern readdirres * nfsproc_readdir_2_svc(readdirargs *, struct svc_req *); +#define NFSPROC_STATFS ((u_long)17) +extern statfsres * nfsproc_statfs_2(nfs_fh *, CLIENT *); +extern statfsres * nfsproc_statfs_2_svc(nfs_fh *, struct svc_req *); + +#else /* Old Style C */ +#define NFSPROC_NULL ((u_long)0) +extern void * nfsproc_null_2(); +extern void * nfsproc_null_2_svc(); +#define NFSPROC_GETATTR ((u_long)1) +extern attrstat * nfsproc_getattr_2(); +extern attrstat * nfsproc_getattr_2_svc(); +#define NFSPROC_SETATTR ((u_long)2) +extern attrstat * nfsproc_setattr_2(); +extern attrstat * nfsproc_setattr_2_svc(); +#define NFSPROC_ROOT ((u_long)3) +extern void * nfsproc_root_2(); +extern void * nfsproc_root_2_svc(); +#define NFSPROC_LOOKUP ((u_long)4) +extern diropres * nfsproc_lookup_2(); +extern diropres * nfsproc_lookup_2_svc(); +#define NFSPROC_READLINK ((u_long)5) +extern readlinkres * nfsproc_readlink_2(); +extern readlinkres * nfsproc_readlink_2_svc(); +#define NFSPROC_READ ((u_long)6) +extern readres * nfsproc_read_2(); +extern readres * nfsproc_read_2_svc(); +#define NFSPROC_WRITECACHE ((u_long)7) +extern void * nfsproc_writecache_2(); +extern void * nfsproc_writecache_2_svc(); +#define NFSPROC_WRITE ((u_long)8) +extern attrstat * nfsproc_write_2(); +extern attrstat * nfsproc_write_2_svc(); +#define NFSPROC_CREATE ((u_long)9) +extern diropres * nfsproc_create_2(); +extern diropres * nfsproc_create_2_svc(); +#define NFSPROC_REMOVE ((u_long)10) +extern nfsstat * nfsproc_remove_2(); +extern nfsstat * nfsproc_remove_2_svc(); +#define NFSPROC_RENAME ((u_long)11) +extern nfsstat * nfsproc_rename_2(); +extern nfsstat * nfsproc_rename_2_svc(); +#define NFSPROC_LINK ((u_long)12) +extern nfsstat * nfsproc_link_2(); +extern nfsstat * nfsproc_link_2_svc(); +#define NFSPROC_SYMLINK ((u_long)13) +extern nfsstat * nfsproc_symlink_2(); +extern nfsstat * nfsproc_symlink_2_svc(); +#define NFSPROC_MKDIR ((u_long)14) +extern diropres * nfsproc_mkdir_2(); +extern diropres * nfsproc_mkdir_2_svc(); +#define NFSPROC_RMDIR ((u_long)15) +extern nfsstat * nfsproc_rmdir_2(); +extern nfsstat * nfsproc_rmdir_2_svc(); +#define NFSPROC_READDIR ((u_long)16) +extern readdirres * nfsproc_readdir_2(); +extern readdirres * nfsproc_readdir_2_svc(); +#define NFSPROC_STATFS ((u_long)17) +extern statfsres * nfsproc_statfs_2(); +extern statfsres * nfsproc_statfs_2_svc(); +#endif /* Old Style C */ + +#endif /* !_NFS_PROT_H_RPCGEN */ diff --git a/kioslave/nfs/nfs_prot.x b/kioslave/nfs/nfs_prot.x new file mode 100644 index 000000000..cd21123c7 --- /dev/null +++ b/kioslave/nfs/nfs_prot.x @@ -0,0 +1,365 @@ +%/* +% * Sun RPC is a product of Sun Microsystems, Inc. and is provided for +% * unrestricted use provided that this legend is included on all tape +% * media and as a part of the software program in whole or part. Users +% * may copy or modify Sun RPC without charge, but are not authorized +% * to license or distribute it to anyone else except as part of a product or +% * program developed by the user or with the express written consent of +% * Sun Microsystems, Inc. +% * +% * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE +% * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR +% * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. +% * +% * Sun RPC is provided with no support and without any obligation on the +% * part of Sun Microsystems, Inc. to assist in its use, correction, +% * modification or enhancement. +% * +% * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE +% * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC +% * OR ANY PART THEREOF. +% * +% * In no event will Sun Microsystems, Inc. be liable for any lost revenue +% * or profits or other special, indirect and consequential damages, even if +% * Sun has been advised of the possibility of such damages. +% * +% * Sun Microsystems, Inc. +% * 2550 Garcia Avenue +% * Mountain View, California 94043 +% */ + +%/* +% * Copyright (c) 1987, 1990 by Sun Microsystems, Inc. +% */ +% +%/* from @(#)nfs_prot.x 1.3 91/03/11 TIRPC 1.0 */ + +#ifdef RPC_HDR +%#ifndef _rpcsvc_nfs_prot_h +%#define _rpcsvc_nfs_prot_h +#endif + +const NFS_PORT = 2049; +const NFS_MAXDATA = 8192; +const NFS_MAXPATHLEN = 1024; +const NFS_MAXNAMLEN = 255; +const NFS_FHSIZE = 32; +const NFS_COOKIESIZE = 4; +const NFS_FIFO_DEV = -1; /* size kludge for named pipes */ + +/* + * File types + */ +const NFSMODE_FMT = 0170000; /* type of file */ +const NFSMODE_DIR = 0040000; /* directory */ +const NFSMODE_CHR = 0020000; /* character special */ +const NFSMODE_BLK = 0060000; /* block special */ +const NFSMODE_REG = 0100000; /* regular */ +const NFSMODE_LNK = 0120000; /* symbolic link */ +const NFSMODE_SOCK = 0140000; /* socket */ +const NFSMODE_FIFO = 0010000; /* fifo */ + +/* + * Error status + */ +enum nfsstat { + NFS_OK= 0, /* no error */ + NFSERR_PERM=1, /* Not owner */ + NFSERR_NOENT=2, /* No such file or directory */ + NFSERR_IO=5, /* I/O error */ + NFSERR_NXIO=6, /* No such device or address */ + NFSERR_ACCES=13, /* Permission denied */ + NFSERR_EXIST=17, /* File exists */ + NFSERR_NODEV=19, /* No such device */ + NFSERR_NOTDIR=20, /* Not a directory*/ + NFSERR_ISDIR=21, /* Is a directory */ + NFSERR_INVAL=22, /* invalid argument */ + NFSERR_FBIG=27, /* File too large */ + NFSERR_NOSPC=28, /* No space left on device */ + NFSERR_ROFS=30, /* Read-only file system */ + NFSERR_NAMETOOLONG=63, /* File name too long */ + NFSERR_NOTEMPTY=66, /* Directory not empty */ + NFSERR_DQUOT=69, /* Disc quota exceeded */ + NFSERR_STALE=70, /* Stale NFS file handle */ + NFSERR_WFLUSH=99 /* write cache flushed */ +}; + +/* + * File types + */ +enum ftype { + NFNON = 0, /* non-file */ + NFREG = 1, /* regular file */ + NFDIR = 2, /* directory */ + NFBLK = 3, /* block special */ + NFCHR = 4, /* character special */ + NFLNK = 5, /* symbolic link */ + NFSOCK = 6, /* unix domain sockets */ + NFBAD = 7, /* unused */ + NFFIFO = 8 /* named pipe */ +}; + +/* + * File access handle + */ +struct nfs_fh { + opaque data[NFS_FHSIZE]; +}; + +/* + * Timeval + */ +struct nfstime { + unsigned seconds; + unsigned useconds; +}; + + +/* + * File attributes + */ +struct fattr { + ftype type; /* file type */ + unsigned mode; /* protection mode bits */ + unsigned nlink; /* # hard links */ + unsigned uid; /* owner user id */ + unsigned gid; /* owner group id */ + unsigned size; /* file size in bytes */ + unsigned blocksize; /* prefered block size */ + unsigned rdev; /* special device # */ + unsigned blocks; /* Kb of disk used by file */ + unsigned fsid; /* device # */ + unsigned fileid; /* inode # */ + nfstime atime; /* time of last access */ + nfstime mtime; /* time of last modification */ + nfstime ctime; /* time of last change */ +}; + +/* + * File attributes which can be set + */ +struct sattr { + unsigned mode; /* protection mode bits */ + unsigned uid; /* owner user id */ + unsigned gid; /* owner group id */ + unsigned size; /* file size in bytes */ + nfstime atime; /* time of last access */ + nfstime mtime; /* time of last modification */ +}; + + +typedef string filename<NFS_MAXNAMLEN>; +typedef string nfspath<NFS_MAXPATHLEN>; + +/* + * Reply status with file attributes + */ +union attrstat switch (nfsstat status) { +case NFS_OK: + fattr attributes; +default: + void; +}; + +struct sattrargs { + nfs_fh file; + sattr attributes; +}; + +/* + * Arguments for directory operations + */ +struct diropargs { + nfs_fh dir; /* directory file handle */ + filename name; /* name (up to NFS_MAXNAMLEN bytes) */ +}; + +struct diropokres { + nfs_fh file; + fattr attributes; +}; + +/* + * Results from directory operation + */ +union diropres switch (nfsstat status) { +case NFS_OK: + diropokres diropres; +default: + void; +}; + +union readlinkres switch (nfsstat status) { +case NFS_OK: + nfspath data; +default: + void; +}; + +/* + * Arguments to remote read + */ +struct readargs { + nfs_fh file; /* handle for file */ + unsigned offset; /* byte offset in file */ + unsigned count; /* immediate read count */ + unsigned totalcount; /* total read count (from this offset)*/ +}; + +/* + * Status OK portion of remote read reply + */ +struct readokres { + fattr attributes; /* attributes, need for pagin*/ + opaque data<NFS_MAXDATA>; +}; + +union readres switch (nfsstat status) { +case NFS_OK: + readokres reply; +default: + void; +}; + +/* + * Arguments to remote write + */ +struct writeargs { + nfs_fh file; /* handle for file */ + unsigned beginoffset; /* beginning byte offset in file */ + unsigned offset; /* current byte offset in file */ + unsigned totalcount; /* total write count (to this offset)*/ + opaque data<NFS_MAXDATA>; +}; + +struct createargs { + diropargs where; + sattr attributes; +}; + +struct renameargs { + diropargs from; + diropargs to; +}; + +struct linkargs { + nfs_fh from; + diropargs to; +}; + +struct symlinkargs { + diropargs from; + nfspath to; + sattr attributes; +}; + + +typedef opaque nfscookie[NFS_COOKIESIZE]; + +/* + * Arguments to readdir + */ +struct readdirargs { + nfs_fh dir; /* directory handle */ + nfscookie cookie; + unsigned count; /* number of directory bytes to read */ +}; + +struct entry { + unsigned fileid; + filename name; + nfscookie cookie; + entry *nextentry; +}; + +struct dirlist { + entry *entries; + bool eof; +}; + +union readdirres switch (nfsstat status) { +case NFS_OK: + dirlist reply; +default: + void; +}; + +struct statfsokres { + unsigned tsize; /* preferred transfer size in bytes */ + unsigned bsize; /* fundamental file system block size */ + unsigned blocks; /* total blocks in file system */ + unsigned bfree; /* free blocks in fs */ + unsigned bavail; /* free blocks avail to non-superuser */ +}; + +union statfsres switch (nfsstat status) { +case NFS_OK: + statfsokres reply; +default: + void; +}; + +/* + * Remote file service routines + */ +program NFS_PROGRAM { + version NFS_VERSION { + void + NFSPROC_NULL(void) = 0; + + attrstat + NFSPROC_GETATTR(nfs_fh) = 1; + + attrstat + NFSPROC_SETATTR(sattrargs) = 2; + + void + NFSPROC_ROOT(void) = 3; + + diropres + NFSPROC_LOOKUP(diropargs) = 4; + + readlinkres + NFSPROC_READLINK(nfs_fh) = 5; + + readres + NFSPROC_READ(readargs) = 6; + + void + NFSPROC_WRITECACHE(void) = 7; + + attrstat + NFSPROC_WRITE(writeargs) = 8; + + diropres + NFSPROC_CREATE(createargs) = 9; + + nfsstat + NFSPROC_REMOVE(diropargs) = 10; + + nfsstat + NFSPROC_RENAME(renameargs) = 11; + + nfsstat + NFSPROC_LINK(linkargs) = 12; + + nfsstat + NFSPROC_SYMLINK(symlinkargs) = 13; + + diropres + NFSPROC_MKDIR(createargs) = 14; + + nfsstat + NFSPROC_RMDIR(diropargs) = 15; + + readdirres + NFSPROC_READDIR(readdirargs) = 16; + + statfsres + NFSPROC_STATFS(nfs_fh) = 17; + } = 2; +} = 100003; + +#ifdef RPC_HDR +%#endif /*!_rpcsvc_nfs_prot_h*/ +#endif diff --git a/kioslave/nfs/nfs_prot_xdr.c b/kioslave/nfs/nfs_prot_xdr.c new file mode 100644 index 000000000..57d446489 --- /dev/null +++ b/kioslave/nfs/nfs_prot_xdr.c @@ -0,0 +1,886 @@ +/* + * Please do not edit this file. + * It was generated using rpcgen. + */ + +#include <rpc/types.h> +#include <rpc/xdr.h> +#include <arpa/inet.h> + +#include "nfs_prot.h" +/* + * Sun RPC is a product of Sun Microsystems, Inc. and is provided for + * unrestricted use provided that this legend is included on all tape + * media and as a part of the software program in whole or part. Users + * may copy or modify Sun RPC without charge, but are not authorized + * to license or distribute it to anyone else except as part of a product or + * program developed by the user or with the express written consent of + * Sun Microsystems, Inc. + * + * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE + * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun RPC is provided with no support and without any obligation on the + * part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ +/* + * Copyright (c) 1987, 1990 by Sun Microsystems, Inc. + */ + +/* from @(#)nfs_prot.x 1.3 91/03/11 TIRPC 1.0 */ + +bool_t +xdr_nfsstat(XDR *xdrs, nfsstat *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_enum(xdrs, (enum_t *)objp)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_ftype(XDR *xdrs, ftype *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_enum(xdrs, (enum_t *)objp)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_nfs_fh(XDR *xdrs, nfs_fh *objp) +{ + + register int32_t *buf=buf; + + int i=i; + if (!xdr_opaque(xdrs, objp->data, NFS_FHSIZE)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_nfstime(XDR *xdrs, nfstime *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_u_int(xdrs, &objp->seconds)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->useconds)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_fattr(XDR *xdrs, fattr *objp) +{ + + register int32_t *buf=buf; + + + if (xdrs->x_op == XDR_ENCODE) { + if (!xdr_ftype(xdrs, &objp->type)) { + return (FALSE); + } + buf = XDR_INLINE(xdrs,10 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->mode)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->nlink)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->uid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->gid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->size)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocksize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->rdev)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocks)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->fsid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->fileid)) { + return (FALSE); + } + + } + else { + IXDR_PUT_U_LONG(buf,objp->mode); + IXDR_PUT_U_LONG(buf,objp->nlink); + IXDR_PUT_U_LONG(buf,objp->uid); + IXDR_PUT_U_LONG(buf,objp->gid); + IXDR_PUT_U_LONG(buf,objp->size); + IXDR_PUT_U_LONG(buf,objp->blocksize); + IXDR_PUT_U_LONG(buf,objp->rdev); + IXDR_PUT_U_LONG(buf,objp->blocks); + IXDR_PUT_U_LONG(buf,objp->fsid); + IXDR_PUT_U_LONG(buf,objp->fileid); + } + if (!xdr_nfstime(xdrs, &objp->atime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->mtime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->ctime)) { + return (FALSE); + } + + return (TRUE); + } else if (xdrs->x_op == XDR_DECODE) { + if (!xdr_ftype(xdrs, &objp->type)) { + return (FALSE); + } + buf = XDR_INLINE(xdrs,10 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->mode)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->nlink)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->uid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->gid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->size)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocksize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->rdev)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocks)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->fsid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->fileid)) { + return (FALSE); + } + + } + else { + objp->mode = IXDR_GET_U_LONG(buf); + objp->nlink = IXDR_GET_U_LONG(buf); + objp->uid = IXDR_GET_U_LONG(buf); + objp->gid = IXDR_GET_U_LONG(buf); + objp->size = IXDR_GET_U_LONG(buf); + objp->blocksize = IXDR_GET_U_LONG(buf); + objp->rdev = IXDR_GET_U_LONG(buf); + objp->blocks = IXDR_GET_U_LONG(buf); + objp->fsid = IXDR_GET_U_LONG(buf); + objp->fileid = IXDR_GET_U_LONG(buf); + } + if (!xdr_nfstime(xdrs, &objp->atime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->mtime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->ctime)) { + return (FALSE); + } + return(TRUE); + } + + if (!xdr_ftype(xdrs, &objp->type)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->mode)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->nlink)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->uid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->gid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->size)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocksize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->rdev)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocks)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->fsid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->fileid)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->atime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->mtime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->ctime)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_sattr(XDR *xdrs, sattr *objp) +{ + + register int32_t *buf=buf; + + + if (xdrs->x_op == XDR_ENCODE) { + buf = XDR_INLINE(xdrs,4 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->mode)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->uid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->gid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->size)) { + return (FALSE); + } + + } + else { + IXDR_PUT_U_LONG(buf,objp->mode); + IXDR_PUT_U_LONG(buf,objp->uid); + IXDR_PUT_U_LONG(buf,objp->gid); + IXDR_PUT_U_LONG(buf,objp->size); + } + if (!xdr_nfstime(xdrs, &objp->atime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->mtime)) { + return (FALSE); + } + + return (TRUE); + } else if (xdrs->x_op == XDR_DECODE) { + buf = XDR_INLINE(xdrs,4 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->mode)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->uid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->gid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->size)) { + return (FALSE); + } + + } + else { + objp->mode = IXDR_GET_U_LONG(buf); + objp->uid = IXDR_GET_U_LONG(buf); + objp->gid = IXDR_GET_U_LONG(buf); + objp->size = IXDR_GET_U_LONG(buf); + } + if (!xdr_nfstime(xdrs, &objp->atime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->mtime)) { + return (FALSE); + } + return(TRUE); + } + + if (!xdr_u_int(xdrs, &objp->mode)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->uid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->gid)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->size)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->atime)) { + return (FALSE); + } + if (!xdr_nfstime(xdrs, &objp->mtime)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_filename(XDR *xdrs, filename *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_string(xdrs, objp, NFS_MAXNAMLEN)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_nfspath(XDR *xdrs, nfspath *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_string(xdrs, objp, NFS_MAXPATHLEN)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_attrstat(XDR *xdrs, attrstat *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfsstat(xdrs, &objp->status)) { + return (FALSE); + } + switch (objp->status) { + case NFS_OK: + if (!xdr_fattr(xdrs, &objp->attrstat_u.attributes)) { + return (FALSE); + } + break; + default: + break; + } + return (TRUE); +} + +bool_t +xdr_sattrargs(XDR *xdrs, sattrargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfs_fh(xdrs, &objp->file)) { + return (FALSE); + } + if (!xdr_sattr(xdrs, &objp->attributes)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_diropargs(XDR *xdrs, diropargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfs_fh(xdrs, &objp->dir)) { + return (FALSE); + } + if (!xdr_filename(xdrs, &objp->name)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_diropokres(XDR *xdrs, diropokres *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfs_fh(xdrs, &objp->file)) { + return (FALSE); + } + if (!xdr_fattr(xdrs, &objp->attributes)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_diropres(XDR *xdrs, diropres *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfsstat(xdrs, &objp->status)) { + return (FALSE); + } + switch (objp->status) { + case NFS_OK: + if (!xdr_diropokres(xdrs, &objp->diropres_u.diropres)) { + return (FALSE); + } + break; + default: + break; + } + return (TRUE); +} + +bool_t +xdr_readlinkres(XDR *xdrs, readlinkres *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfsstat(xdrs, &objp->status)) { + return (FALSE); + } + switch (objp->status) { + case NFS_OK: + if (!xdr_nfspath(xdrs, &objp->readlinkres_u.data)) { + return (FALSE); + } + break; + default: + break; + } + return (TRUE); +} + +bool_t +xdr_readargs(XDR *xdrs, readargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfs_fh(xdrs, &objp->file)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->offset)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->count)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->totalcount)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_readokres(XDR *xdrs, readokres *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_fattr(xdrs, &objp->attributes)) { + return (FALSE); + } + if (!xdr_bytes(xdrs, (char **)&objp->data.data_val, (u_int *)&objp->data.data_len, NFS_MAXDATA)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_readres(XDR *xdrs, readres *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfsstat(xdrs, &objp->status)) { + return (FALSE); + } + switch (objp->status) { + case NFS_OK: + if (!xdr_readokres(xdrs, &objp->readres_u.reply)) { + return (FALSE); + } + break; + default: + break; + } + return (TRUE); +} + +bool_t +xdr_writeargs(XDR *xdrs, writeargs *objp) +{ + + register int32_t *buf=buf; + + + if (xdrs->x_op == XDR_ENCODE) { + if (!xdr_nfs_fh(xdrs, &objp->file)) { + return (FALSE); + } + buf = XDR_INLINE(xdrs,3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->beginoffset)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->offset)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->totalcount)) { + return (FALSE); + } + + } + else { + IXDR_PUT_U_LONG(buf,objp->beginoffset); + IXDR_PUT_U_LONG(buf,objp->offset); + IXDR_PUT_U_LONG(buf,objp->totalcount); + } + if (!xdr_bytes(xdrs, (char **)&objp->data.data_val, (u_int *)&objp->data.data_len, NFS_MAXDATA)) { + return (FALSE); + } + + return (TRUE); + } else if (xdrs->x_op == XDR_DECODE) { + if (!xdr_nfs_fh(xdrs, &objp->file)) { + return (FALSE); + } + buf = XDR_INLINE(xdrs,3 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->beginoffset)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->offset)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->totalcount)) { + return (FALSE); + } + + } + else { + objp->beginoffset = IXDR_GET_U_LONG(buf); + objp->offset = IXDR_GET_U_LONG(buf); + objp->totalcount = IXDR_GET_U_LONG(buf); + } + if (!xdr_bytes(xdrs, (char **)&objp->data.data_val, (u_int *)&objp->data.data_len, NFS_MAXDATA)) { + return (FALSE); + } + return(TRUE); + } + + if (!xdr_nfs_fh(xdrs, &objp->file)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->beginoffset)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->offset)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->totalcount)) { + return (FALSE); + } + if (!xdr_bytes(xdrs, (char **)&objp->data.data_val, (u_int *)&objp->data.data_len, NFS_MAXDATA)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_createargs(XDR *xdrs, createargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_diropargs(xdrs, &objp->where)) { + return (FALSE); + } + if (!xdr_sattr(xdrs, &objp->attributes)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_renameargs(XDR *xdrs, renameargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_diropargs(xdrs, &objp->from)) { + return (FALSE); + } + if (!xdr_diropargs(xdrs, &objp->to)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_linkargs(XDR *xdrs, linkargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfs_fh(xdrs, &objp->from)) { + return (FALSE); + } + if (!xdr_diropargs(xdrs, &objp->to)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_symlinkargs(XDR *xdrs, symlinkargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_diropargs(xdrs, &objp->from)) { + return (FALSE); + } + if (!xdr_nfspath(xdrs, &objp->to)) { + return (FALSE); + } + if (!xdr_sattr(xdrs, &objp->attributes)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_nfscookie(XDR *xdrs, nfscookie objp) +{ + + register int32_t *buf=buf; + + if (!xdr_opaque(xdrs, objp, NFS_COOKIESIZE)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_readdirargs(XDR *xdrs, readdirargs *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfs_fh(xdrs, &objp->dir)) { + return (FALSE); + } + if (!xdr_nfscookie(xdrs, objp->cookie)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->count)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_entry(XDR *xdrs, entry *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_u_int(xdrs, &objp->fileid)) { + return (FALSE); + } + if (!xdr_filename(xdrs, &objp->name)) { + return (FALSE); + } + if (!xdr_nfscookie(xdrs, objp->cookie)) { + return (FALSE); + } + if (!xdr_pointer(xdrs, &objp->nextentry, sizeof(entry), (xdrproc_t)xdr_entry)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_dirlist(XDR *xdrs, dirlist *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_pointer(xdrs, (char **)&objp->entries, sizeof(entry), (xdrproc_t)xdr_entry)) { + return (FALSE); + } + if (!xdr_bool(xdrs, &objp->eof)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_readdirres(XDR *xdrs, readdirres *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfsstat(xdrs, &objp->status)) { + return (FALSE); + } + switch (objp->status) { + case NFS_OK: + if (!xdr_dirlist(xdrs, &objp->readdirres_u.reply)) { + return (FALSE); + } + break; + default: + break; + } + return (TRUE); +} + +bool_t +xdr_statfsokres(XDR *xdrs, statfsokres *objp) +{ + + register int32_t *buf=buf; + + + if (xdrs->x_op == XDR_ENCODE) { + buf = XDR_INLINE(xdrs,5 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->tsize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bsize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocks)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bfree)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bavail)) { + return (FALSE); + } + + } + else { + IXDR_PUT_U_LONG(buf,objp->tsize); + IXDR_PUT_U_LONG(buf,objp->bsize); + IXDR_PUT_U_LONG(buf,objp->blocks); + IXDR_PUT_U_LONG(buf,objp->bfree); + IXDR_PUT_U_LONG(buf,objp->bavail); + } + + return (TRUE); + } else if (xdrs->x_op == XDR_DECODE) { + buf = XDR_INLINE(xdrs,5 * BYTES_PER_XDR_UNIT); + if (buf == NULL) { + if (!xdr_u_int(xdrs, &objp->tsize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bsize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocks)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bfree)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bavail)) { + return (FALSE); + } + + } + else { + objp->tsize = IXDR_GET_U_LONG(buf); + objp->bsize = IXDR_GET_U_LONG(buf); + objp->blocks = IXDR_GET_U_LONG(buf); + objp->bfree = IXDR_GET_U_LONG(buf); + objp->bavail = IXDR_GET_U_LONG(buf); + } + return(TRUE); + } + + if (!xdr_u_int(xdrs, &objp->tsize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bsize)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->blocks)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bfree)) { + return (FALSE); + } + if (!xdr_u_int(xdrs, &objp->bavail)) { + return (FALSE); + } + return (TRUE); +} + +bool_t +xdr_statfsres(XDR *xdrs, statfsres *objp) +{ + + register int32_t *buf=buf; + + if (!xdr_nfsstat(xdrs, &objp->status)) { + return (FALSE); + } + switch (objp->status) { + case NFS_OK: + if (!xdr_statfsokres(xdrs, &objp->statfsres_u.reply)) { + return (FALSE); + } + break; + default: + break; + } + return (TRUE); +} diff --git a/kioslave/nntp/LICENSE b/kioslave/nntp/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/kioslave/nntp/LICENSE @@ -0,0 +1,16 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/kioslave/nntp/Makefile.am b/kioslave/nntp/Makefile.am new file mode 100644 index 000000000..b06d31600 --- /dev/null +++ b/kioslave/nntp/Makefile.am @@ -0,0 +1,19 @@ +INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(all_includes) + +####### Files + +kde_module_LTLIBRARIES = kio_nntp.la + +kio_nntp_la_SOURCES = nntp.cpp +kio_nntp_la_LIBADD = $(LIB_KIO) +kio_nntp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +METASOURCES = AUTO + +noinst_HEADERS = nntp.h + +kdelnk_DATA = nntp.protocol nntps.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_nntp.pot diff --git a/kioslave/nntp/nntp.cpp b/kioslave/nntp/nntp.cpp new file mode 100644 index 000000000..40b162868 --- /dev/null +++ b/kioslave/nntp/nntp.cpp @@ -0,0 +1,896 @@ +/* This file is part of KDE + Copyright (C) 2000 by Wolfram Diestel <wolfram@steloj.de> + Copyright (C) 2005 by Tim Way <tim@way.hrcoxmail.com> + Copyright (C) 2005 by Volker Krause <volker.krause@rwth-aachen.de> + + This 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. +*/ + +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> + +#include <qdir.h> +#include <qregexp.h> + +#include <kinstance.h> +#include <kdebug.h> +#include <kglobal.h> +#include <klocale.h> + +#include "nntp.h" + +#define NNTP_PORT 119 +#define NNTPS_PORT 563 + +#define UDS_ENTRY_CHUNK 50 // so much entries are sent at once in listDir + +#define DBG_AREA 7114 +#define DBG kdDebug(DBG_AREA) +#define ERR kdError(DBG_AREA) +#define WRN kdWarning(DBG_AREA) +#define FAT kdFatal(DBG_AREA) + +using namespace KIO; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +int kdemain(int argc, char **argv) { + + KInstance instance ("kio_nntp"); + if (argc != 4) { + fprintf(stderr, "Usage: kio_nntp protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + NNTPProtocol *slave; + + // Are we going to use SSL? + if (strcasecmp(argv[1], "nntps") == 0) { + slave = new NNTPProtocol(argv[2], argv[3], true); + } else { + slave = new NNTPProtocol(argv[2], argv[3], false); + } + + slave->dispatchLoop(); + delete slave; + + return 0; +} + +/****************** NNTPProtocol ************************/ + +NNTPProtocol::NNTPProtocol ( const QCString & pool, const QCString & app, bool isSSL ) + : TCPSlaveBase( (isSSL ? NNTPS_PORT : NNTP_PORT), (isSSL ? "nntps" : "nntp"), pool, + app, isSSL ) +{ + DBG << "=============> NNTPProtocol::NNTPProtocol" << endl; + + m_bIsSSL = isSSL; + readBufferLen = 0; + m_iDefaultPort = m_bIsSSL ? NNTPS_PORT : NNTP_PORT; + m_iPort = m_iDefaultPort; +} + +NNTPProtocol::~NNTPProtocol() { + DBG << "<============= NNTPProtocol::~NNTPProtocol" << endl; + + // close connection + nntp_close(); +} + +void NNTPProtocol::setHost ( const QString & host, int port, const QString & user, + const QString & pass ) +{ + DBG << "setHost: " << ( ! user.isEmpty() ? (user+"@") : QString("")) + << host << ":" << ( ( port == 0 ) ? m_iDefaultPort : port ) << endl; + + if ( isConnectionValid() && (mHost != host || m_iPort != port || + mUser != user || mPass != pass) ) + nntp_close(); + + mHost = host; + m_iPort = ( ( port == 0 ) ? m_iDefaultPort : port ); + mUser = user; + mPass = pass; +} + +void NNTPProtocol::get(const KURL& url) { + DBG << "get " << url.prettyURL() << endl; + QString path = QDir::cleanDirPath(url.path()); + QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false); + int pos; + QString group; + QString msg_id; + + // path should be like: /group/<msg_id> + if (regMsgId.search(path) != 0) { + error(ERR_DOES_NOT_EXIST,path); + return; + } + + pos = path.find('<'); + group = path.left(pos); + msg_id = KURL::decode_string( path.right(path.length()-pos) ); + if (group.left(1) == "/") group.remove(0,1); + if ((pos = group.find('/')) > 0) group = group.left(pos); + DBG << "get group: " << group << " msg: " << msg_id << endl; + + if ( !nntp_open() ) + return; + + // select group + int res_code = sendCommand( "GROUP " + group ); + if (res_code == 411){ + error(ERR_DOES_NOT_EXIST, path); + return; + } else if (res_code != 211) { + unexpected_response(res_code,"GROUP"); + return; + } + + // get article + res_code = sendCommand( "ARTICLE " + msg_id ); + if (res_code == 430) { + error(ERR_DOES_NOT_EXIST,path); + return; + } else if (res_code != 220) { + unexpected_response(res_code,"ARTICLE"); + return; + } + + // read and send data + QCString line; + QByteArray buffer; + char tmp[MAX_PACKET_LEN]; + int len = 0; + while ( true ) { + if ( !waitForResponse( readTimeout() ) ) { + error( ERR_SERVER_TIMEOUT, mHost ); + return; + } + memset( tmp, 0, MAX_PACKET_LEN ); + len = readLine( tmp, MAX_PACKET_LEN ); + line = tmp; + if ( len <= 0 ) + break; + if ( line == ".\r\n" ) + break; + if ( line.left(2) == ".." ) + line.remove( 0, 1 ); + // cannot use QCString, it would send the 0-terminator too + buffer.setRawData( line.data(), line.length() ); + data( buffer ); + buffer.resetRawData( line.data(), line.length() ); + } + // end of data + buffer.resize(0); + data(buffer); + + // finish + finished(); +} + +void NNTPProtocol::put( const KURL &/*url*/, int /*permissions*/, bool /*overwrite*/, bool /*resume*/ ) +{ + if ( !nntp_open() ) + return; + if ( post_article() ) + finished(); +} + +void NNTPProtocol::special(const QByteArray& data) { + // 1 = post article + int cmd; + QDataStream stream(data, IO_ReadOnly); + + if ( !nntp_open() ) + return; + + stream >> cmd; + if (cmd == 1) { + if (post_article()) finished(); + } else { + error(ERR_UNSUPPORTED_ACTION,i18n("Invalid special command %1").arg(cmd)); + } +} + +bool NNTPProtocol::post_article() { + DBG << "post article " << endl; + + // send post command + int res_code = sendCommand( "POST" ); + if (res_code == 440) { // posting not allowed + error(ERR_WRITE_ACCESS_DENIED, mHost); + return false; + } else if (res_code != 340) { // 340: ok, send article + unexpected_response(res_code,"POST"); + return false; + } + + // send article now + int result; + bool last_chunk_had_line_ending = true; + do { + QByteArray buffer; + QCString data; + dataReq(); + result = readData(buffer); + // treat the buffer data + if (result>0) { + data = QCString(buffer.data(),buffer.size()+1); + // translate "\r\n." to "\r\n.." + int pos=0; + if (last_chunk_had_line_ending && data[0] == '.') { + data.insert(0,'.'); + pos += 2; + } + last_chunk_had_line_ending = (data.right(2) == "\r\n"); + while ((pos = data.find("\r\n.",pos)) > 0) { + data.insert(pos+2,'.'); + pos += 4; + } + + // send data to socket, write() doesn't send the terminating 0 + write( data.data(), data.length() ); + } + } while (result>0); + + // error occurred? + if (result<0) { + ERR << "error while getting article data for posting" << endl; + nntp_close(); + return false; + } + + // send end mark + write( "\r\n.\r\n", 5 ); + + // get answer + res_code = evalResponse( readBuffer, readBufferLen ); + if (res_code == 441) { // posting failed + error(ERR_COULD_NOT_WRITE, mHost); + return false; + } else if (res_code != 240) { + unexpected_response(res_code,"POST"); + return false; + } + + return true; +} + + +void NNTPProtocol::stat( const KURL& url ) { + DBG << "stat " << url.prettyURL() << endl; + UDSEntry entry; + QString path = QDir::cleanDirPath(url.path()); + QRegExp regGroup = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",false); + QRegExp regMsgId = QRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false); + int pos; + QString group; + QString msg_id; + + // / = group list + if (path.isEmpty() || path == "/") { + DBG << "stat root" << endl; + fillUDSEntry(entry, QString::null, 0, postingAllowed, false); + + // /group = message list + } else if (regGroup.search(path) == 0) { + if (path.left(1) == "/") path.remove(0,1); + if ((pos = path.find('/')) > 0) group = path.left(pos); + else group = path; + DBG << "stat group: " << group << endl; + // postingAllowed should be ored here with "group not moderated" flag + // as size the num of messages (GROUP cmd) could be given + fillUDSEntry(entry, group, 0, postingAllowed, false); + + // /group/<msg_id> = message + } else if (regMsgId.search(path) == 0) { + pos = path.find('<'); + group = path.left(pos); + msg_id = KURL::decode_string( path.right(path.length()-pos) ); + if (group.left(1) == "/") group.remove(0,1); + if ((pos = group.find('/')) > 0) group = group.left(pos); + DBG << "stat group: " << group << " msg: " << msg_id << endl; + fillUDSEntry(entry, msg_id, 0, false, true); + + // invalid url + } else { + error(ERR_DOES_NOT_EXIST,path); + return; + } + + statEntry(entry); + finished(); +} + +void NNTPProtocol::listDir( const KURL& url ) { + DBG << "listDir " << url.prettyURL() << endl; + if ( !nntp_open() ) + return; + + QString path = QDir::cleanDirPath(url.path()); + + if (path.isEmpty()) + { + KURL newURL(url); + newURL.setPath("/"); + DBG << "listDir redirecting to " << newURL.prettyURL() << endl; + redirection(newURL); + finished(); + return; + } + else if ( path == "/" ) { + fetchGroups( url.queryItem( "since" ) ); + finished(); + } else { + // if path = /group + int pos; + QString group; + if (path.left(1) == "/") + path.remove(0,1); + if ((pos = path.find('/')) > 0) + group = path.left(pos); + else + group = path; + QString first = url.queryItem( "first" ); + if ( fetchGroup( group, first.toULong() ) ) + finished(); + } +} + +void NNTPProtocol::fetchGroups( const QString &since ) +{ + int expected; + int res; + if ( since.isEmpty() ) { + // full listing + res = sendCommand( "LIST" ); + expected = 215; + } else { + // incremental listing + res = sendCommand( "NEWGROUPS " + since ); + expected = 231; + } + if ( res != expected ) { + unexpected_response( res, "LIST" ); + return; + } + + // read newsgroups line by line + QCString line, group; + int pos, pos2; + long msg_cnt; + bool moderated; + UDSEntry entry; + UDSEntryList entryList; + + // read in data and process each group. one line at a time + while ( true ) { + if ( ! waitForResponse( readTimeout() ) ) { + error( ERR_SERVER_TIMEOUT, mHost ); + return; + } + memset( readBuffer, 0, MAX_PACKET_LEN ); + readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); + line = readBuffer; + if ( line == ".\r\n" ) + break; + + DBG << " fetchGroups -- data: " << line.stripWhiteSpace() << endl; + + // group name + if ((pos = line.find(' ')) > 0) { + + group = line.left(pos); + + // number of messages + line.remove(0,pos+1); + long last = 0; + if (((pos = line.find(' ')) > 0 || (pos = line.find('\t')) > 0) && + ((pos2 = line.find(' ',pos+1)) > 0 || (pos2 = line.find('\t',pos+1)) > 0)) { + last = line.left(pos).toLong(); + long first = line.mid(pos+1,pos2-pos-1).toLong(); + msg_cnt = abs(last-first+1); + // moderated group? + moderated = (line[pos2+1] == 'n'); + } else { + msg_cnt = 0; + moderated = false; + } + + fillUDSEntry(entry, group, msg_cnt, postingAllowed && !moderated, false); + // add the last serial number as UDS_EXTRA atom, this is needed for + // incremental article listing + UDSAtom atom; + atom.m_uds = UDS_EXTRA; + atom.m_str = QString::number( last ); + entry.append( atom ); + entryList.append(entry); + + if (entryList.count() >= UDS_ENTRY_CHUNK) { + listEntries(entryList); + entryList.clear(); + } + } + } + + // send rest of entryList + if (entryList.count() > 0) listEntries(entryList); +} + +bool NNTPProtocol::fetchGroup( QString &group, unsigned long first ) { + int res_code; + QString resp_line; + + // select group + res_code = sendCommand( "GROUP " + group ); + if (res_code == 411){ + error(ERR_DOES_NOT_EXIST,group); + return false; + } else if (res_code != 211) { + unexpected_response(res_code,"GROUP"); + return false; + } + + // repsonse to "GROUP <requested-group>" command is 211 then find the message count (cnt) + // and the first and last message followed by the group name + int pos, pos2; + unsigned long firstSerNum; + resp_line = readBuffer; + if (((pos = resp_line.find(' ',4)) > 0 || (pos = resp_line.find('\t',4)) > 0) && + ((pos2 = resp_line.find(' ',pos+1)) > 0 || (pos = resp_line.find('\t',pos+1)) > 0)) + { + firstSerNum = resp_line.mid(pos+1,pos2-pos-1).toLong(); + } else { + error(ERR_INTERNAL,i18n("Could not extract first message number from server response:\n%1"). + arg(resp_line)); + return false; + } + + if (firstSerNum == 0L) + return true; + first = kMax( first, firstSerNum ); + DBG << "Starting from serial number: " << first << " of " << firstSerNum << endl; + + bool notSupported = true; + if ( fetchGroupXOVER( first, notSupported ) ) + return true; + else if ( notSupported ) + return fetchGroupRFC977( first ); + return false; +} + + +bool NNTPProtocol::fetchGroupRFC977( unsigned long first ) +{ + UDSEntry entry; + UDSEntryList entryList; + + // set article pointer to first article and get msg-id of it + int res_code = sendCommand( "STAT " + QString::number( first ) ); + QString resp_line = readBuffer; + if (res_code != 223) { + unexpected_response(res_code,"STAT"); + return false; + } + + //STAT res_line: 223 nnn <msg_id> ... + QString msg_id; + int pos, pos2; + if ((pos = resp_line.find('<')) > 0 && (pos2 = resp_line.find('>',pos+1))) { + msg_id = resp_line.mid(pos,pos2-pos+1); + fillUDSEntry(entry, msg_id, 0, false, true); + entryList.append(entry); + } else { + error(ERR_INTERNAL,i18n("Could not extract first message id from server response:\n%1"). + arg(resp_line)); + return false; + } + + // go through all articles + while (true) { + res_code = sendCommand("NEXT"); + if (res_code == 421) { + // last article reached + if ( !entryList.isEmpty() ) + listEntries( entryList ); + return true; + } else if (res_code != 223) { + unexpected_response(res_code,"NEXT"); + return false; + } + + //res_line: 223 nnn <msg_id> ... + resp_line = readBuffer; + if ((pos = resp_line.find('<')) > 0 && (pos2 = resp_line.find('>',pos+1))) { + msg_id = resp_line.mid(pos,pos2-pos+1); + fillUDSEntry(entry, msg_id, 0, false, true); + entryList.append(entry); + if (entryList.count() >= UDS_ENTRY_CHUNK) { + listEntries(entryList); + entryList.clear(); + } + } else { + error(ERR_INTERNAL,i18n("Could not extract message id from server response:\n%1"). + arg(resp_line)); + return false; + } + } + return true; // Not reached +} + + +bool NNTPProtocol::fetchGroupXOVER( unsigned long first, bool ¬Supported ) +{ + notSupported = false; + + QString line; + QStringList headers; + + int res = sendCommand( "LIST OVERVIEW.FMT" ); + if ( res == 215 ) { + while ( true ) { + if ( ! waitForResponse( readTimeout() ) ) { + error( ERR_SERVER_TIMEOUT, mHost ); + return false; + } + memset( readBuffer, 0, MAX_PACKET_LEN ); + readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); + line = readBuffer; + if ( line == ".\r\n" ) + break; + headers << line.stripWhiteSpace(); + DBG << "OVERVIEW.FMT: " << line.stripWhiteSpace() << endl; + } + } else { + // fallback to defaults + headers << "Subject:" << "From:" << "Date:" << "Message-ID:" + << "References:" << "Bytes:" << "Lines:"; + } + + res = sendCommand( "XOVER " + QString::number( first ) + "-" ); + if ( res == 420 ) + return true; // no articles selected + if ( res == 500 ) + notSupported = true; // unknwon command + if ( res != 224 ) + return false; + + long msgSize; + QString msgId; + UDSAtom atom; + UDSEntry entry; + UDSEntryList entryList; + + QStringList fields; + while ( true ) { + if ( ! waitForResponse( readTimeout() ) ) { + error( ERR_SERVER_TIMEOUT, mHost ); + return false; + } + memset( readBuffer, 0, MAX_PACKET_LEN ); + readBufferLen = readLine ( readBuffer, MAX_PACKET_LEN ); + line = readBuffer; + if ( line == ".\r\n" ) { + // last article reached + if ( !entryList.isEmpty() ) + listEntries( entryList ); + return true; + } + + fields = QStringList::split( "\t", line, true ); + msgId = QString::null; + msgSize = 0; + QStringList::ConstIterator it = headers.constBegin(); + QStringList::ConstIterator it2 = fields.constBegin(); + ++it2; // first entry is the serial number + for ( ; it != headers.constEnd() && it2 != fields.constEnd(); ++it, ++it2 ) { + if ( (*it).contains( "Message-ID:", false ) ) { + msgId = (*it2); + continue; + } + if ( (*it) == "Bytes:" ) { + msgSize = (*it2).toLong(); + continue; + } + atom.m_uds = UDS_EXTRA; + if ( (*it).endsWith( "full" ) ) + atom.m_str = (*it2).stripWhiteSpace(); + else + atom.m_str = (*it) + " " + (*it2).stripWhiteSpace(); + entry.append( atom ); + } + if ( msgId.isEmpty() ) + msgId = fields[0]; // fallback to serial number + fillUDSEntry( entry, msgId, msgSize, false, true ); + entryList.append( entry ); + if (entryList.count() >= UDS_ENTRY_CHUNK) { + listEntries(entryList); + entryList.clear(); + } + } + return true; +} + + +void NNTPProtocol::fillUDSEntry(UDSEntry& entry, const QString& name, long size, + bool posting_allowed, bool is_article) { + + long posting=0; + + UDSAtom atom; + entry.clear(); + + // entry name + atom.m_uds = UDS_NAME; + atom.m_str = name; + atom.m_long = 0; + entry.append(atom); + + // entry size + atom.m_uds = UDS_SIZE; + atom.m_str = QString::null; + atom.m_long = size; + entry.append(atom); + + // file type + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = is_article? S_IFREG : S_IFDIR; + atom.m_str = QString::null; + entry.append(atom); + + // access permissions + atom.m_uds = UDS_ACCESS; + posting = posting_allowed? (S_IWUSR | S_IWGRP | S_IWOTH) : 0; + atom.m_long = (is_article)? (S_IRUSR | S_IRGRP | S_IROTH) : + (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH | posting); + atom.m_str = QString::null; + entry.append(atom); + + atom.m_uds = UDS_USER; + atom.m_str = mUser.isEmpty() ? QString("root") : mUser; + atom.m_long= 0; + entry.append(atom); + + /* + atom.m_uds = UDS_GROUP; + atom.m_str = "root"; + atom.m_long=0; + entry->append(atom); + */ + + // MIME type + if (is_article) { + atom.m_uds = UDS_MIME_TYPE; + atom.m_long= 0; + atom.m_str = "message/news"; + entry.append(atom); + } +} + +void NNTPProtocol::nntp_close () { + if ( isConnectionValid() ) { + write( "QUIT\r\n", 6 ); + closeDescriptor(); + opened = false; + } +} + +bool NNTPProtocol::nntp_open() +{ + // if still connected reuse connection + if ( isConnectionValid() ) { + DBG << "reusing old connection" << endl; + return true; + } + + DBG << " nntp_open -- creating a new connection to " << mHost << ":" << m_iPort << endl; + // create a new connection + if ( connectToHost( mHost.latin1(), m_iPort, true ) ) + { + DBG << " nntp_open -- connection is open " << endl; + + // read greeting + int res_code = evalResponse( readBuffer, readBufferLen ); + + /* expect one of + 200 server ready - posting allowed + 201 server ready - no posting allowed + */ + if ( ! ( res_code == 200 || res_code == 201 ) ) + { + unexpected_response(res_code,"CONNECT"); + return false; + } + + DBG << " nntp_open -- greating was read res_code : " << res_code << endl; + // let local class know that we are connected + opened = true; + + res_code = sendCommand("MODE READER"); + + // TODO: not in RFC 977, so we should not abort here + if ( !(res_code == 200 || res_code == 201) ) { + unexpected_response( res_code, "MODE READER" ); + return false; + } + + // let local class know whether posting is allowed or not + postingAllowed = (res_code == 200); + + // activate TLS if requested + if ( metaData("tls") == "on" ) { + if ( sendCommand( "STARTTLS" ) != 382 ) { + error( ERR_COULD_NOT_CONNECT, i18n("This server does not support TLS") ); + return false; + } + int tlsrc = startTLS(); + if ( tlsrc != 1 ) { + error( ERR_COULD_NOT_CONNECT, i18n("TLS negotiation failed") ); + return false; + } + } + + return true; + } + // connection attempt failed + else + { + DBG << " nntp_open -- connection attempt failed" << endl; + error( ERR_COULD_NOT_CONNECT, mHost ); + return false; + } +} + +int NNTPProtocol::sendCommand( const QString &cmd ) +{ + int res_code = 0; + + if ( !opened ) { + ERR << "NOT CONNECTED, cannot send cmd " << cmd << endl; + return 0; + } + + DBG << "sending cmd " << cmd << endl; + + write( cmd.latin1(), cmd.length() ); + // check the command for proper termination + if ( !cmd.endsWith( "\r\n" ) ) + write( "\r\n", 2 ); + res_code = evalResponse( readBuffer, readBufferLen ); + + // if authorization needed send user info + if (res_code == 480) { + DBG << "auth needed, sending user info" << endl; + + if ( mUser.isEmpty() || mPass.isEmpty() ) { + KIO::AuthInfo authInfo; + authInfo.username = mUser; + authInfo.password = mPass; + if ( openPassDlg( authInfo ) ) { + mUser = authInfo.username; + mPass = authInfo.password; + } + } + if ( mUser.isEmpty() || mPass.isEmpty() ) + return res_code; + + // send username to server and confirm response + write( "AUTHINFO USER ", 14 ); + write( mUser.latin1(), mUser.length() ); + write( "\r\n", 2 ); + res_code = evalResponse( readBuffer, readBufferLen ); + + if (res_code != 381) { + // error should be handled by invoking function + return res_code; + } + + // send password + write( "AUTHINFO PASS ", 14 ); + write( mPass.latin1(), mPass.length() ); + write( "\r\n", 2 ); + res_code = evalResponse( readBuffer, readBufferLen ); + + if (res_code != 281) { + // error should be handled by invoking function + return res_code; + } + + // ok now, resend command + write( cmd.latin1(), cmd.length() ); + if ( !cmd.endsWith( "\r\n" ) ) + write( "\r\n", 2 ); + res_code = evalResponse( readBuffer, readBufferLen ); + } + + return res_code; +} + +void NNTPProtocol::unexpected_response( int res_code, const QString & command) { + ERR << "Unexpected response to " << command << " command: (" << res_code << ") " + << readBuffer << endl; + error(ERR_INTERNAL,i18n("Unexpected server response to %1 command:\n%2"). + arg(command).arg(readBuffer)); + + // close connection + nntp_close(); +} + +int NNTPProtocol::evalResponse ( char *data, ssize_t &len ) +{ + if ( !waitForResponse( responseTimeout() ) ) { + error( ERR_SERVER_TIMEOUT , mHost ); + return -1; + } + memset( data, 0, MAX_PACKET_LEN ); + len = readLine( data, MAX_PACKET_LEN ); + + if ( len < 3 ) + return -1; + + // get the first three characters. should be the response code + int respCode = ( ( data[0] - 48 ) * 100 ) + ( ( data[1] - 48 ) * 10 ) + ( ( data[2] - 48 ) ); + + DBG << "evalResponse - got: " << respCode << endl; + + return respCode; +} + +/* not really necessary, because the slave has to + use the KIO::Error's instead, but let this here for + documentation of the NNTP response codes and may + by later use. +QString& NNTPProtocol::errorStr(int resp_code) { + QString ret; + + switch (resp_code) { + case 100: ret = "help text follows"; break; + case 199: ret = "debug output"; break; + + case 200: ret = "server ready - posting allowed"; break; + case 201: ret = "server ready - no posting allowed"; break; + case 202: ret = "slave status noted"; break; + case 205: ret = "closing connection - goodbye!"; break; + case 211: ret = "group selected"; break; + case 215: ret = "list of newsgroups follows"; break; + case 220: ret = "article retrieved - head and body follow"; break; + case 221: ret = "article retrieved - head follows"; break; + case 222: ret = "article retrieved - body follows"; break; + case 223: ret = "article retrieved - request text separately"; break; + case 230: ret = "list of new articles by message-id follows"; break; + case 231: ret = "list of new newsgroups follows"; break; + case 235: ret = "article transferred ok"; break; + case 240: ret = "article posted ok"; break; + + case 335: ret = "send article to be transferred"; break; + case 340: ret = "send article to be posted"; break; + + case 400: ret = "service discontinued"; break; + case 411: ret = "no such news group"; break; + case 412: ret = "no newsgroup has been selected"; break; + case 420: ret = "no current article has been selected"; break; + case 421: ret = "no next article in this group"; break; + case 422: ret = "no previous article in this group"; break; + case 423: ret = "no such article number in this group"; break; + case 430: ret = "no such article found"; break; + case 435: ret = "article not wanted - do not send it"; break; + case 436: ret = "transfer failed - try again later"; break; + case 437: ret = "article rejected - do not try again"; break; + case 440: ret = "posting not allowed"; break; + case 441: ret = "posting failed"; break; + + case 500: ret = "command not recognized"; break; + case 501: ret = "command syntax error"; break; + case 502: ret = "access restriction or permission denied"; break; + case 503: ret = "program fault - command not performed"; break; + default: ret = QString("unknown NNTP response code %1").arg(resp_code); + } + + return ret; +} +*/ diff --git a/kioslave/nntp/nntp.h b/kioslave/nntp/nntp.h new file mode 100644 index 000000000..7efe597a8 --- /dev/null +++ b/kioslave/nntp/nntp.h @@ -0,0 +1,130 @@ +/* This file is part of KDE + Copyright (C) 2000 by Wolfram Diestel <wolfram@steloj.de> + Copyright (C) 2005 by Tim Way <tim@way.hrcoxmail.com> + Copyright (C) 2005 by Volker Krause <volker.krause@rwth-aachen.de> + + This 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. +*/ + +#ifndef _NNTP_H +#define _NNTP_H + +#include <qstring.h> +#include <kio/global.h> +#include <kio/tcpslavebase.h> + +#define MAX_PACKET_LEN 4096 + +/* TODO: + - test special post command + - progress information in get, and maybe post + - remove unnecessary debug stuff +*/ + +class NNTPProtocol:public KIO::TCPSlaveBase +{ + + public: + /** Default Constructor + * @param isSSL is a true or false to indicate whether ssl is to be used + */ + NNTPProtocol ( const QCString & pool, const QCString & app, bool isSSL ); + virtual ~NNTPProtocol(); + + virtual void get(const KURL& url ); + virtual void put( const KURL& url, int permissions, bool overwrite, bool resume ); + virtual void stat(const KURL& url ); + virtual void listDir(const KURL& url ); + virtual void setHost(const QString& host, int port, + const QString& user, const QString& pass); + + /** + * Special command: 1 = post article + * it takes no other args, the article data are + * requested by dataReq() and should be valid + * as in RFC850. It's not checked for correctness here. + * @deprecated use put() for posting + */ + virtual void special(const QByteArray& data); + + protected: + + /** + * Send a command to the server. Returns the response code and + * the response line + */ + int sendCommand( const QString &cmd ); + + /** + * Attempt to properly shut down the NNTP connection by sending + * "QUIT\r\n" before closing the socket. + */ + void nntp_close (); + + /** + * Attempt to initiate a NNTP connection via a TCP socket, if no existing + * connection could be reused. + */ + bool nntp_open(); + + /** + * Post article. Invoked by special() and put() + */ + bool post_article(); + + + private: + QString mHost, mUser, mPass; + bool postingAllowed, opened; + char readBuffer[MAX_PACKET_LEN]; + ssize_t readBufferLen; + + /** + * Fetch all new groups since the given date or (if the date is empty) + * all available groups. + * @param since Date as specified in RFC 977 for the NEWGROUPS command + */ + void fetchGroups( const QString &since ); + /** + * Fetch message listing from the given newsgroup. + * This will use RFC2980 XOVER if available, plain RFC977 STAT/NEXT + * otherwise. + * @param group The newsgroup name + * @param first Serial number of the first message, 0 lists all messages. + * @return true on sucess, false otherwise. + */ + bool fetchGroup ( QString &group, unsigned long first = 0 ); + /** + * Fetch message listing from the current group using RFC977 STAT/NEXT + * commands. + * @param first message number of the first article + * @return true on sucess, false otherwise. + */ + bool fetchGroupRFC977( unsigned long first ); + /** + * Fetch message listing from the current group using the RFC2980 XOVER + * command. + * Additional headers provided by XOVER are added as UDS_EXTRA entries + * to the listing. + * @param first message number of the first article + * @param notSupported boolean reference to indicate if command failed + * due to missing XOVER support on the server. + * @return true on sucess, false otherwise + */ + bool fetchGroupXOVER( unsigned long first, bool ¬Supported ); + /// creates an UDSEntry with file information used in stat and listDir + void fillUDSEntry ( KIO::UDSEntry & entry, const QString & name, long size, + bool postingAllowed, bool is_article ); + /// error handling for unexpected responses + void unexpected_response ( int res_code, const QString & command ); + /** + * grabs the response line from the server. used after most send_cmd calls. max + * length for the returned string ( char *data ) is 4096 characters including + * the "\r\n" terminator. + */ + int evalResponse ( char *data, ssize_t &len ); +}; + +#endif diff --git a/kioslave/nntp/nntp.protocol b/kioslave/nntp/nntp.protocol new file mode 100644 index 000000000..1ae8a25f7 --- /dev/null +++ b/kioslave/nntp/nntp.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_nntp +protocol=nntp +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=true +deleting=false +DocPath=kioslave/nntp.html +Icon=news diff --git a/kioslave/nntp/nntps.protocol b/kioslave/nntp/nntps.protocol new file mode 100644 index 000000000..4d2e61422 --- /dev/null +++ b/kioslave/nntp/nntps.protocol @@ -0,0 +1,11 @@ +[Protocol] +exec=kio_nntp +protocol=nntps +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=true +deleting=false +DocPath=kioslave/nntp.html +Icon=news diff --git a/kioslave/pop3/Makefile.am b/kioslave/pop3/Makefile.am new file mode 100644 index 000000000..03ce04104 --- /dev/null +++ b/kioslave/pop3/Makefile.am @@ -0,0 +1,17 @@ +INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(SSL_INCLUDES) $(all_includes) + +####### Files + +kde_module_LTLIBRARIES = kio_pop3.la + +kio_pop3_la_SOURCES = pop3.cc +kio_pop3_la_LIBADD = $(LIB_KIO) $(SASL2_LIBS) +kio_pop3_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = pop3.h + +kdelnk_DATA = pop3.protocol pop3s.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cc -o $(podir)/kio_pop3.pot diff --git a/kioslave/pop3/pop3.cc b/kioslave/pop3/pop3.cc new file mode 100644 index 000000000..06266d7a4 --- /dev/null +++ b/kioslave/pop3/pop3.cc @@ -0,0 +1,1263 @@ +/* + * Copyright (c) 1999-2001 Alex Zepeda + * Copyright (c) 2001-2002 Michael Haeckel <haeckel@kde.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include <errno.h> +#include <stdio.h> + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include <sasl/sasl.h> +} +#endif + +#include <qcstring.h> +#include <qglobal.h> +#include <qregexp.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> +#include <kmdcodec.h> +#include <kprotocolmanager.h> +#include <ksock.h> + +#include <kio/connection.h> +#include <kio/slaveinterface.h> +#include <kio/passdlg.h> +#include "pop3.h" + +#define GREETING_BUF_LEN 1024 +#define MAX_RESPONSE_LEN 512 +#define MAX_COMMANDS 10 + +#define POP3_DEBUG kdDebug(7105) + +extern "C" { + int KDE_EXPORT kdemain(int argc, char **argv); +} + +using namespace KIO; + +#ifdef HAVE_LIBSASL2 +static sasl_callback_t callbacks[] = { + { SASL_CB_ECHOPROMPT, NULL, NULL }, + { SASL_CB_NOECHOPROMPT, NULL, NULL }, + { SASL_CB_GETREALM, NULL, NULL }, + { SASL_CB_USER, NULL, NULL }, + { SASL_CB_AUTHNAME, NULL, NULL }, + { SASL_CB_PASS, NULL, NULL }, + { SASL_CB_CANON_USER, NULL, NULL }, + { SASL_CB_LIST_END, NULL, NULL } +}; +#endif + +int kdemain(int argc, char **argv) +{ + + if (argc != 4) { + POP3_DEBUG << "Usage: kio_pop3 protocol domain-socket1 domain-socket2" + << endl; + return -1; + } + +#ifdef HAVE_LIBSASL2 + if ( sasl_client_init( NULL ) != SASL_OK ) { + fprintf(stderr, "SASL library initialization failed!\n"); + return -1; + } +#endif + + KInstance instance("kio_pop3"); + POP3Protocol *slave; + + // Are we looking to use SSL? + if (strcasecmp(argv[1], "pop3s") == 0) { + slave = new POP3Protocol(argv[2], argv[3], true); + } else { + slave = new POP3Protocol(argv[2], argv[3], false); + } + + slave->dispatchLoop(); + delete slave; + +#ifdef HAVE_LIBSASL2 + sasl_done(); +#endif + + return 0; +} + +POP3Protocol::POP3Protocol(const QCString & pool, const QCString & app, + bool isSSL) +: TCPSlaveBase((isSSL ? 995 : 110), (isSSL ? "pop3s" : "pop3"), pool, app, + isSSL) +{ + POP3_DEBUG << "POP3Protocol::POP3Protocol()" << endl; + m_bIsSSL = isSSL; + m_cmd = CMD_NONE; + m_iOldPort = 0; + m_tTimeout.tv_sec = 10; + m_tTimeout.tv_usec = 0; + supports_apop = false; + m_try_apop = true; + m_try_sasl = true; + opened = false; + readBufferLen = 0; +} + +POP3Protocol::~POP3Protocol() +{ + POP3_DEBUG << "POP3Protocol::~POP3Protocol()" << endl; + closeConnection(); +} + +void POP3Protocol::setHost(const QString & _host, int _port, + const QString & _user, const QString & _pass) +{ + m_sServer = _host; + m_iPort = _port; + m_sUser = _user; + m_sPass = _pass; +} + +ssize_t POP3Protocol::myRead(void *data, ssize_t len) +{ + if (readBufferLen) { + ssize_t copyLen = (len < readBufferLen) ? len : readBufferLen; + memcpy(data, readBuffer, copyLen); + readBufferLen -= copyLen; + if (readBufferLen) + memcpy(readBuffer, &readBuffer[copyLen], readBufferLen); + return copyLen; + } + waitForResponse(600); + return read(data, len); +} + +ssize_t POP3Protocol::myReadLine(char *data, ssize_t len) +{ + ssize_t copyLen = 0, readLen = 0; + while (true) { + while (copyLen < readBufferLen && readBuffer[copyLen] != '\n') + copyLen++; + if (copyLen < readBufferLen || copyLen == len) { + copyLen++; + memcpy(data, readBuffer, copyLen); + data[copyLen] = '\0'; + readBufferLen -= copyLen; + if (readBufferLen) + memcpy(readBuffer, &readBuffer[copyLen], readBufferLen); + return copyLen; + } + waitForResponse(600); + readLen = read(&readBuffer[readBufferLen], len - readBufferLen); + readBufferLen += readLen; + if (readLen <= 0) { + data[0] = '\0'; + return 0; + } + } +} + +POP3Protocol::Resp POP3Protocol::getResponse(char *r_buf, unsigned int r_len, + const char *cmd) +{ + char *buf = 0; + unsigned int recv_len = 0; + // fd_set FDs; + + // Give the buffer the appropriate size + r_len = r_len ? r_len : MAX_RESPONSE_LEN; + + buf = new char[r_len]; + + // Clear out the buffer + memset(buf, 0, r_len); + myReadLine(buf, r_len - 1); + + // This is really a funky crash waiting to happen if something isn't + // null terminated. + recv_len = strlen(buf); + + /* + * From rfc1939: + * + * Responses in the POP3 consist of a status indicator and a keyword + * possibly followed by additional information. All responses are + * terminated by a CRLF pair. Responses may be up to 512 characters + * long, including the terminating CRLF. There are currently two status + * indicators: positive ("+OK") and negative ("-ERR"). Servers MUST + * send the "+OK" and "-ERR" in upper case. + */ + + if (strncmp(buf, "+OK", 3) == 0) { + if (r_buf && r_len) { + memcpy(r_buf, (buf[3] == ' ' ? buf + 4 : buf + 3), + QMIN(r_len, (buf[3] == ' ' ? recv_len - 4 : recv_len - 3))); + } + + delete[]buf; + + return Ok; + } else if (strncmp(buf, "-ERR", 4) == 0) { + if (r_buf && r_len) { + memcpy(r_buf, (buf[4] == ' ' ? buf + 5 : buf + 4), + QMIN(r_len, (buf[4] == ' ' ? recv_len - 5 : recv_len - 4))); + } + + QString command = QString::fromLatin1(cmd); + QString serverMsg = QString::fromLatin1(buf).mid(5).stripWhiteSpace(); + + if (command.left(4) == "PASS") { + command = i18n("PASS <your password>"); + } + + m_sError = i18n("The server said: \"%1\"").arg(serverMsg); + + delete[]buf; + + return Err; + } else if (strncmp(buf, "+ ", 2) == 0) { + if (r_buf && r_len) { + memcpy(r_buf, buf + 2, QMIN(r_len, recv_len - 4)); + r_buf[QMIN(r_len - 1, recv_len - 4)] = '\0'; + } + + delete[]buf; + + return Cont; + } else { + POP3_DEBUG << "Invalid POP3 response received!" << endl; + + if (r_buf && r_len) { + memcpy(r_buf, buf, QMIN(r_len, recv_len)); + } + + if (!buf || !*buf) { + m_sError = i18n("The server terminated the connection."); + } else { + m_sError = i18n("Invalid response from server:\n\"%1\"").arg(buf); + } + + delete[]buf; + + return Invalid; + } +} + +bool POP3Protocol::sendCommand(const char *cmd) +{ + /* + * From rfc1939: + * + * Commands in the POP3 consist of a case-insensitive keyword, possibly + * followed by one or more arguments. All commands are terminated by a + * CRLF pair. Keywords and arguments consist of printable ASCII + * characters. Keywords and arguments are each separated by a single + * SPACE character. Keywords are three or four characters long. Each + * argument may be up to 40 characters long. + */ + + if (!isConnectionValid()) return false; + + char *cmdrn = new char[strlen(cmd) + 3]; + sprintf(cmdrn, "%s\r\n", (cmd) ? cmd : ""); + + if (write(cmdrn, strlen(cmdrn)) != static_cast < ssize_t > + (strlen(cmdrn))) { + m_sError = i18n("Could not send to server.\n"); + delete[]cmdrn; + return false; + } + + delete[]cmdrn; + return true; +} + +POP3Protocol::Resp POP3Protocol::command(const char *cmd, char *recv_buf, + unsigned int len) +{ + sendCommand(cmd); + return getResponse(recv_buf, len, cmd); +} + +void POP3Protocol::openConnection() +{ + m_try_apop = !hasMetaData("auth") || metaData("auth") == "APOP"; + m_try_sasl = !hasMetaData("auth") || metaData("auth") == "SASL"; + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + } else { + connected(); + } +} + +void POP3Protocol::closeConnection() +{ + // If the file pointer exists, we can assume the socket is valid, + // and to make sure that the server doesn't magically undo any of + // our deletions and so-on, we should send a QUIT and wait for a + // response. We don't care if it's positive or negative. Also + // flush out any semblance of a persistant connection, i.e.: the + // old username and password are now invalid. + if (!opened) { + return; + } + + command("QUIT"); + closeDescriptor(); + readBufferLen = 0; + m_sOldUser = m_sOldPass = m_sOldServer = ""; + opened = false; +} + +int POP3Protocol::loginAPOP( char *challenge, KIO::AuthInfo &ai ) +{ + char buf[512]; + + QString apop_string = QString::fromLatin1("APOP "); + if (m_sUser.isEmpty() || m_sPass.isEmpty()) { + // Prompt for usernames + if (!openPassDlg(ai)) { + error(ERR_ABORTED, i18n("No authentication details supplied.")); + closeConnection(); + return -1; + } else { + m_sUser = ai.username; + m_sPass = ai.password; + } + } + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + + apop_string.append(m_sUser); + + memset(buf, 0, sizeof(buf)); + + KMD5 ctx; + + POP3_DEBUG << "APOP challenge: " << challenge << endl; + + // Generate digest + ctx.update(challenge, strlen(challenge)); + ctx.update(m_sPass.latin1() ); + + // Genenerate APOP command + apop_string.append(" "); + apop_string.append(ctx.hexDigest()); + + if (command(apop_string.local8Bit(), buf, sizeof(buf)) == Ok) { + return 0; + } + + POP3_DEBUG << "Couldn't login via APOP. Falling back to USER/PASS" << + endl; + closeConnection(); + if (metaData("auth") == "APOP") { + error(ERR_COULD_NOT_LOGIN, + i18n + ("Login via APOP failed. The server %1 may not support APOP, although it claims to support it, or the password may be wrong.\n\n%2"). + arg(m_sServer). + arg(m_sError)); + return -1; + } + return 1; +} + +bool POP3Protocol::saslInteract( void *in, AuthInfo &ai ) +{ +#ifdef HAVE_LIBSASL2 + POP3_DEBUG << "sasl_interact" << endl; + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so don't need a popup + //window for getting this info + for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { + if ( interact->id == SASL_CB_AUTHNAME || + interact->id == SASL_CB_PASS ) { + + if (m_sUser.isEmpty() || m_sPass.isEmpty()) { + if (!openPassDlg(ai)) { + error(ERR_ABORTED, i18n("No authentication details supplied.")); + return false; + } + m_sUser = ai.username; + m_sPass = ai.password; + } + break; + } + } + + interact = ( sasl_interact_t * ) in; + while( interact->id != SASL_CB_LIST_END ) { + POP3_DEBUG << "SASL_INTERACT id: " << interact->id << endl; + switch( interact->id ) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + POP3_DEBUG << "SASL_CB_[USER|AUTHNAME]: " << m_sUser << endl; + interact->result = strdup( m_sUser.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + case SASL_CB_PASS: + POP3_DEBUG << "SASL_CB_PASS: [hidden] " << endl; + interact->result = strdup( m_sPass.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + default: + interact->result = NULL; interact->len = 0; + break; + } + interact++; + } + return true; +#else + return false; +#endif +} + +#define SASLERROR closeConnection(); \ +error(ERR_COULD_NOT_AUTHENTICATE, i18n("An error occured during authentication: %1").arg \ +( QString::fromUtf8( sasl_errdetail( conn ) ))); \ + +int POP3Protocol::loginSASL( KIO::AuthInfo &ai ) +{ +#ifdef HAVE_LIBSASL2 + char buf[512]; + QString sasl_buffer = QString::fromLatin1("AUTH"); + + int result; + sasl_conn_t *conn = NULL; + sasl_interact_t *client_interact = NULL; + const char *out = NULL; + uint outlen; + const char *mechusing = NULL; + Resp resp; + + result = sasl_client_new( "pop", + m_sServer.latin1(), + 0, 0, callbacks, 0, &conn ); + + if ( result != SASL_OK ) { + POP3_DEBUG << "sasl_client_new failed with: " << result << endl; + SASLERROR + return false; + } + + // We need to check what methods the server supports... + // This is based on RFC 1734's wisdom + if ( hasMetaData("sasl") || command(sasl_buffer.local8Bit()) == Ok ) { + + QStringList sasl_list; + if (hasMetaData("sasl")) { + sasl_list.append(metaData("sasl").latin1()); + } else + while (true /* !AtEOF() */ ) { + memset(buf, 0, sizeof(buf)); + myReadLine(buf, sizeof(buf) - 1); + + // HACK: This assumes fread stops at the first \n and not \r + if (strcmp(buf, ".\r\n") == 0) { + break; // End of data + } + // sanders, changed -2 to -1 below + buf[strlen(buf) - 2] = '\0'; + + sasl_list.append(buf); + } + + do { + result = sasl_client_start(conn, sasl_list.join(" ").latin1(), + &client_interact, &out, &outlen, &mechusing); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact, ai ) ) { + closeConnection(); + sasl_dispose( &conn ); + return -1; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + POP3_DEBUG << "sasl_client_start failed with: " << result << endl; + SASLERROR + sasl_dispose( &conn ); + return -1; + } + + POP3_DEBUG << "Preferred authentication method is " << mechusing << "." << endl; + + QByteArray challenge, tmp; + + QString firstCommand = "AUTH " + QString::fromLatin1( mechusing ); + challenge.setRawData( out, outlen ); + KCodecs::base64Encode( challenge, tmp ); + challenge.resetRawData( out, outlen ); + if ( !tmp.isEmpty() ) { + firstCommand += " "; + firstCommand += QString::fromLatin1( tmp.data(), tmp.size() ); + } + + challenge.resize( 2049 ); + resp = command( firstCommand.latin1(), challenge.data(), 2049 ); + while( resp == Cont ) { + challenge.resize(challenge.find(0)); +// POP3_DEBUG << "S: " << QCString(challenge.data(),challenge.size()+1) << endl; + KCodecs::base64Decode( challenge, tmp ); + do { + result = sasl_client_step(conn, tmp.isEmpty() ? 0 : tmp.data(), + tmp.size(), + &client_interact, + &out, &outlen); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact, ai ) ) { + closeConnection(); + sasl_dispose( &conn ); + return -1; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + POP3_DEBUG << "sasl_client_step failed with: " << result << endl; + SASLERROR + sasl_dispose( &conn ); + return -1; + } + + challenge.setRawData( out, outlen ); + KCodecs::base64Encode( challenge, tmp ); + challenge.resetRawData( out, outlen ); +// POP3_DEBUG << "C: " << QCString(tmp.data(),tmp.size()+1) << endl; + tmp.resize(tmp.size()+1); + tmp[tmp.size()-1] = '\0'; + challenge.resize(2049); + resp = command( tmp.data(), challenge.data(), 2049 ); + } + + sasl_dispose( &conn ); + if ( resp == Ok ) { + POP3_DEBUG << "SASL authenticated" << endl; + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + return 0; + } + + if (metaData("auth") == "SASL") { + closeConnection(); + error(ERR_COULD_NOT_LOGIN, + i18n + ("Login via SASL (%1) failed. The server may not support %2, or the password may be wrong.\n\n%3"). + arg(mechusing).arg(mechusing).arg(m_sError)); + return -1; + } + } + + if (metaData("auth") == "SASL") { + closeConnection(); + error(ERR_COULD_NOT_LOGIN, + i18n("Your POP3 server does not support SASL.\n" + "Choose a different authentication method.")); + return -1; + } + return 1; +#else + if (metaData("auth") == "SASL") { + closeConnection(); + error(ERR_COULD_NOT_LOGIN, i18n("SASL authentication is not compiled into kio_pop3.")); + return -1; + } + return 1; //if SASL not explicitly required, try another method (USER/PASS) +#endif +} + +bool POP3Protocol::loginPASS( KIO::AuthInfo &ai ) +{ + char buf[512]; + + if (m_sUser.isEmpty() || m_sPass.isEmpty()) { + // Prompt for usernames + if (!openPassDlg(ai)) { + error(ERR_ABORTED, i18n("No authentication details supplied.")); + closeConnection(); + return false; + } else { + m_sUser = ai.username; + m_sPass = ai.password; + } + } + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + + QString one_string = QString::fromLatin1("USER "); + one_string.append( m_sUser ); + + if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { + POP3_DEBUG << "Couldn't login. Bad username Sorry" << endl; + + m_sError = + i18n("Could not login to %1.\n\n").arg(m_sServer) + m_sError; + error(ERR_COULD_NOT_LOGIN, m_sError); + closeConnection(); + + return false; + } + + one_string = QString::fromLatin1("PASS "); + one_string.append(m_sPass); + + if ( command(one_string.local8Bit(), buf, sizeof(buf)) != Ok ) { + POP3_DEBUG << "Couldn't login. Bad password Sorry." << endl; + m_sError = + i18n + ("Could not login to %1. The password may be wrong.\n\n%2"). + arg(m_sServer).arg(m_sError); + error(ERR_COULD_NOT_LOGIN, m_sError); + closeConnection(); + return false; + } + POP3_DEBUG << "USER/PASS login succeeded" << endl; + return true; +} + +bool POP3Protocol::pop3_open() +{ + POP3_DEBUG << "pop3_open()" << endl; + char *greeting_buf; + if ((m_iOldPort == port(m_iPort)) && (m_sOldServer == m_sServer) && + (m_sOldUser == m_sUser) && (m_sOldPass == m_sPass)) { + POP3_DEBUG << "Reusing old connection" << endl; + return true; + } + do { + closeConnection(); + + if (!connectToHost(m_sServer.ascii(), m_iPort)) { + // error(ERR_COULD_NOT_CONNECT, m_sServer); + // ConnectToHost has already send an error message. + return false; + } + opened = true; + + greeting_buf = new char[GREETING_BUF_LEN]; + memset(greeting_buf, 0, GREETING_BUF_LEN); + + // If the server doesn't respond with a greeting + if (getResponse(greeting_buf, GREETING_BUF_LEN, "") != Ok) { + m_sError = + i18n("Could not login to %1.\n\n").arg(m_sServer) + + ((!greeting_buf + || !*greeting_buf) ? + i18n("The server terminated the connection immediately.") : + i18n("Server does not respond properly:\n%1\n"). + arg(greeting_buf)); + error(ERR_COULD_NOT_LOGIN, m_sError); + delete[]greeting_buf; + closeConnection(); + return false; // we've got major problems, and possibly the + // wrong port + } + QCString greeting(greeting_buf); + delete[]greeting_buf; + + if (greeting.length() > 0) { + greeting.truncate(greeting.length() - 2); + } + + // Does the server support APOP? + QString apop_cmd; + QRegExp re("<[A-Za-z0-9\\.\\-_]+@[A-Za-z0-9\\.\\-_]+>$", false); + + POP3_DEBUG << "greeting: " << greeting << endl; + int apop_pos = greeting.find(re); + supports_apop = (bool) (apop_pos != -1); + + if (metaData("nologin") == "on") + return true; + + if (metaData("auth") == "APOP" && !supports_apop) { + error(ERR_COULD_NOT_LOGIN, + i18n("Your POP3 server does not support APOP.\n" + "Choose a different authentication method.")); + closeConnection(); + return false; + } + + m_iOldPort = m_iPort; + m_sOldServer = m_sServer; + + // Try to go into TLS mode + if ((metaData("tls") == "on" || (canUseTLS() && + metaData("tls") != "off")) + && command("STLS") == Ok ) { + int tlsrc = startTLS(); + if (tlsrc == 1) { + POP3_DEBUG << "TLS mode has been enabled." << endl; + } else { + if (tlsrc != -3) { + POP3_DEBUG << "TLS mode setup has failed. Aborting." << endl; + error(ERR_COULD_NOT_CONNECT, + i18n("Your POP3 server claims to " + "support TLS but negotiation " + "was unsuccessful. You can " + "disable TLS in KDE using the " + "crypto settings module.")); + } + closeConnection(); + return false; + } + } else if (metaData("tls") == "on") { + error(ERR_COULD_NOT_CONNECT, + i18n("Your POP3 server does not support TLS. Disable " + "TLS, if you want to connect without encryption.")); + closeConnection(); + return false; + } + + KIO::AuthInfo authInfo; + authInfo.username = m_sUser; + authInfo.password = m_sPass; + authInfo.prompt = i18n("Username and password for your POP3 account:"); + + if ( supports_apop && m_try_apop ) { + POP3_DEBUG << "Trying APOP" << endl; + int retval = loginAPOP( greeting.data() + apop_pos, authInfo ); + switch ( retval ) { + case 0: return true; + case -1: return false; + default: + m_try_apop = false; + } + } else if ( m_try_sasl ) { + POP3_DEBUG << "Trying SASL" << endl; + int retval = loginSASL( authInfo ); + switch ( retval ) { + case 0: return true; + case -1: return false; + default: + m_try_sasl = false; + } + } else { + // Fall back to conventional USER/PASS scheme + POP3_DEBUG << "Trying USER/PASS" << endl; + return loginPASS( authInfo ); + } + } while ( true ); +} + +size_t POP3Protocol::realGetSize(unsigned int msg_num) +{ + char *buf; + QCString cmd; + size_t ret = 0; + + buf = new char[MAX_RESPONSE_LEN]; + memset(buf, 0, MAX_RESPONSE_LEN); + cmd.sprintf("LIST %u", msg_num); + if ( command(cmd.data(), buf, MAX_RESPONSE_LEN) != Ok ) { + delete[]buf; + return 0; + } else { + cmd = buf; + cmd.remove(0, cmd.find(" ")); + ret = cmd.toLong(); + } + delete[]buf; + return ret; +} + +void POP3Protocol::special(const QByteArray & aData) +{ + QString result; + char buf[MAX_PACKET_LEN]; + QDataStream stream(aData, IO_ReadOnly); + int tmp; + stream >> tmp; + + if (tmp != 'c') + return; + + for (int i = 0; i < 2; i++) { + QCString cmd = (i) ? "AUTH" : "CAPA"; + if ( command(cmd) != Ok ) + continue; + while (true) { + myReadLine(buf, MAX_PACKET_LEN - 1); + if (qstrcmp(buf, ".\r\n") == 0) + break; + result += " " + QString(buf).left(strlen(buf) - 2) + .replace(" ", "-"); + } + } + if (supports_apop) + result += " APOP"; + result = result.mid(1); + infoMessage(result); + finished(); +} + +void POP3Protocol::get(const KURL & url) +{ +// List of supported commands +// +// URI Command Result +// pop3://user:pass@domain/index LIST List message sizes +// pop3://user:pass@domain/uidl UIDL List message UIDs +// pop3://user:pass@domain/remove/#1 DELE #1 Mark a message for deletion +// pop3://user:pass@domain/download/#1 RETR #1 Get message header and body +// pop3://user:pass@domain/list/#1 LIST #1 Get size of a message +// pop3://user:pass@domain/uid/#1 UIDL #1 Get UID of a message +// pop3://user:pass@domain/commit QUIT Delete marked messages +// pop3://user:pass@domain/headers/#1 TOP #1 Get header of message +// +// Notes: +// Sizes are in bytes. +// No support for the STAT command has been implemented. +// commit closes the connection to the server after issuing the QUIT command. + + bool ok = true; + char buf[MAX_PACKET_LEN]; + char destbuf[MAX_PACKET_LEN]; + QByteArray array; + QString cmd, path = url.path(); + int maxCommands = (metaData("pipelining") == "on") ? MAX_COMMANDS : 1; + + if (path.at(0) == '/') + path.remove(0, 1); + if (path.isEmpty()) { + POP3_DEBUG << "We should be a dir!!" << endl; + error(ERR_IS_DIRECTORY, url.url()); + m_cmd = CMD_NONE; + return; + } + + if (((path.find('/') == -1) && (path != "index") && (path != "uidl") + && (path != "commit"))) { + error(ERR_MALFORMED_URL, url.url()); + m_cmd = CMD_NONE; + return; + } + + cmd = path.left(path.find('/')); + path.remove(0, path.find('/') + 1); + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + error(ERR_COULD_NOT_CONNECT, m_sServer); + return; + } + + if ((cmd == "index") || (cmd == "uidl")) { + unsigned long size = 0; + bool result; + + if (cmd == "index") { + result = ( command("LIST") == Ok ); + } else { + result = ( command("UIDL") == Ok ); + } + + /* + LIST + +OK Mailbox scan listing follows + 1 2979 + 2 1348 + . + */ + if (result) { + while (true /* !AtEOF() */ ) { + memset(buf, 0, sizeof(buf)); + myReadLine(buf, sizeof(buf) - 1); + + // HACK: This assumes fread stops at the first \n and not \r + if (strcmp(buf, ".\r\n") == 0) { + break; // End of data + } + // sanders, changed -2 to -1 below + int bufStrLen = strlen(buf); + buf[bufStrLen - 2] = '\0'; + size += bufStrLen; + array.setRawData(buf, bufStrLen); + data(array); + array.resetRawData(buf, bufStrLen); + totalSize(size); + } + } + POP3_DEBUG << "Finishing up list" << endl; + data(QByteArray()); + finished(); + } else if (cmd == "remove") { + QStringList waitingCommands = QStringList::split(',', path); + int activeCommands = 0; + QStringList::Iterator it = waitingCommands.begin(); + while (it != waitingCommands.end() || activeCommands > 0) { + while (activeCommands < maxCommands && it != waitingCommands.end()) { + sendCommand(("DELE " + *it).latin1()); + activeCommands++; + it++; + } + getResponse(buf, sizeof(buf) - 1, ""); + activeCommands--; + } + finished(); + m_cmd = CMD_NONE; + } else if (cmd == "download" || cmd == "headers") { + QStringList waitingCommands = QStringList::split(',', path); + bool noProgress = (metaData("progress") == "off" + || waitingCommands.count() > 1); + int p_size = 0; + unsigned int msg_len = 0; + QString list_cmd("LIST "); + list_cmd += path; + memset(buf, 0, sizeof(buf)); + if ( !noProgress ) { + if ( command(list_cmd.ascii(), buf, sizeof(buf) - 1) == Ok ) { + list_cmd = buf; + // We need a space, otherwise we got an invalid reply + if (!list_cmd.find(" ")) { + POP3_DEBUG << "List command needs a space? " << list_cmd << endl; + closeConnection(); + error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); + return; + } + list_cmd.remove(0, list_cmd.find(" ") + 1); + msg_len = list_cmd.toUInt(&ok); + if (!ok) { + POP3_DEBUG << "LIST command needs to return a number? :" << + list_cmd << ":" << endl; + closeConnection(); + error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); + return; + } + } else { + closeConnection(); + error(ERR_COULD_NOT_READ, m_sError); + return; + } + } + + int activeCommands = 0; + QStringList::Iterator it = waitingCommands.begin(); + while (it != waitingCommands.end() || activeCommands > 0) { + while (activeCommands < maxCommands && it != waitingCommands.end()) { + sendCommand(((cmd == + "headers") ? "TOP " + *it + " 0" : "RETR " + + *it).latin1()); + activeCommands++; + it++; + } + if ( getResponse(buf, sizeof(buf) - 1, "") == Ok ) { + activeCommands--; + mimeType("message/rfc822"); + totalSize(msg_len); + memset(buf, 0, sizeof(buf)); + char ending = '\n'; + bool endOfMail = false; + bool eat = false; + while (true /* !AtEOF() */ ) { + ssize_t readlen = myRead(buf, sizeof(buf) - 1); + if (readlen <= 0) { + if (isConnectionValid()) + error(ERR_SERVER_TIMEOUT, m_sServer); + else + error(ERR_CONNECTION_BROKEN, m_sServer); + closeConnection(); + return; + } + if (ending == '.' && readlen > 1 && buf[0] == '\r' + && buf[1] == '\n') { + readBufferLen = readlen - 2; + memcpy(readBuffer, &buf[2], readBufferLen); + break; + } + bool newline = (ending == '\n'); + + if (buf[readlen - 1] == '\n') + ending = '\n'; + else if (buf[readlen - 1] == '.' + && ((readlen > 1) ? buf[readlen - 2] == '\n' : ending == + '\n')) + ending = '.'; + else + ending = ' '; + + char *buf1 = buf, *buf2 = destbuf; + // ".." at start of a line means only "." + // "." means end of data + for (ssize_t i = 0; i < readlen; i++) { + if (*buf1 == '\r' && eat) { + endOfMail = true; + if (i == readlen - 1 /* && !AtEOF() */ ) + myRead(buf, 1); + else if (i < readlen - 2) { + readBufferLen = readlen - i - 2; + memcpy(readBuffer, &buf[i + 2], readBufferLen); + } + break; + } else if (*buf1 == '\n') { + newline = true; + eat = false; + } else if (*buf1 == '.' && newline) { + newline = false; + eat = true; + } else { + newline = false; + eat = false; + } + if (!eat) { + *buf2 = *buf1; + buf2++; + } + buf1++; + } + + if (buf2 > destbuf) { + array.setRawData(destbuf, buf2 - destbuf); + data(array); + array.resetRawData(destbuf, buf2 - destbuf); + } + + if (endOfMail) + break; + + if (!noProgress) { + p_size += readlen; + processedSize(p_size); + } + } + infoMessage("message complete"); + } else { + POP3_DEBUG << "Couldn't login. Bad RETR Sorry" << endl; + closeConnection(); + error(ERR_COULD_NOT_READ, m_sError); + return; + } + } + POP3_DEBUG << "Finishing up" << endl; + data(QByteArray()); + finished(); + } else if ((cmd == "uid") || (cmd == "list")) { + QString qbuf; + (void) path.toInt(&ok); + + if (!ok) { + return; // We fscking need a number! + } + + if (cmd == "uid") { + path.prepend("UIDL "); + } else { + path.prepend("LIST "); + } + + memset(buf, 0, sizeof(buf)); + if ( command(path.ascii(), buf, sizeof(buf) - 1) == Ok ) { + const int len = strlen(buf); + mimeType("text/plain"); + totalSize(len); + array.setRawData(buf, len); + data(array); + array.resetRawData(buf, len); + processedSize(len); + POP3_DEBUG << buf << endl; + POP3_DEBUG << "Finishing up uid" << endl; + data(QByteArray()); + finished(); + } else { + closeConnection(); + error(ERR_INTERNAL, i18n("Unexpected response from POP3 server.")); + return; + } + } else if (cmd == "commit") { + POP3_DEBUG << "Issued QUIT" << endl; + closeConnection(); + finished(); + m_cmd = CMD_NONE; + return; + } +} + +void POP3Protocol::listDir(const KURL &) +{ + bool isINT; + int num_messages = 0; + char buf[MAX_RESPONSE_LEN]; + QCString q_buf; + + // Try and open a connection + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + error(ERR_COULD_NOT_CONNECT, m_sServer); + return; + } + // Check how many messages we have. STAT is by law required to + // at least return +OK num_messages total_size + memset(buf, 0, MAX_RESPONSE_LEN); + if ( command("STAT", buf, MAX_RESPONSE_LEN) != Ok ) { + error(ERR_INTERNAL, "??"); + return; + } + POP3_DEBUG << "The stat buf is :" << buf << ":" << endl; + q_buf = buf; + if (q_buf.find(" ") == -1) { + error(ERR_INTERNAL, + "Invalid POP3 response, we should have at least one space!"); + closeConnection(); + return; + } + q_buf.remove(q_buf.find(" "), q_buf.length()); + + num_messages = q_buf.toUInt(&isINT); + if (!isINT) { + error(ERR_INTERNAL, "Invalid POP3 STAT response!"); + closeConnection(); + return; + } + UDSEntry entry; + UDSAtom atom; + QString fname; + for (int i = 0; i < num_messages; i++) { + fname = "Message %1"; + + atom.m_uds = UDS_NAME; + atom.m_long = 0; + atom.m_str = fname.arg(i + 1); + entry.append(atom); + + atom.m_uds = UDS_MIME_TYPE; + atom.m_long = 0; + atom.m_str = "text/plain"; + entry.append(atom); + POP3_DEBUG << "Mimetype is " << atom.m_str.ascii() << endl; + + atom.m_uds = UDS_URL; + KURL uds_url; + if (m_bIsSSL) { + uds_url.setProtocol("pop3s"); + } else { + uds_url.setProtocol("pop3"); + } + + uds_url.setUser(m_sUser); + uds_url.setPass(m_sPass); + uds_url.setHost(m_sServer); + uds_url.setPath(QString::fromLatin1("/download/%1").arg(i + 1)); + atom.m_str = uds_url.url(); + atom.m_long = 0; + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = UDS_SIZE; + atom.m_str = ""; + atom.m_long = realGetSize(i + 1); + entry.append(atom); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IWUSR; + entry.append (atom); + + listEntry(entry, false); + entry.clear(); + } + listEntry(entry, true); // ready + + finished(); +} + +void POP3Protocol::stat(const KURL & url) +{ + QString _path = url.path(); + + if (_path.at(0) == '/') + _path.remove(0, 1); + + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = UDS_NAME; + atom.m_str = _path; + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_str = ""; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = UDS_MIME_TYPE; + atom.m_str = "message/rfc822"; + entry.append(atom); + + // TODO: maybe get the size of the message? + statEntry(entry); + + finished(); +} + +void POP3Protocol::del(const KURL & url, bool /*isfile */ ) +{ + QString invalidURI = QString::null; + bool isInt; + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + error(ERR_COULD_NOT_CONNECT, m_sServer); + return; + } + + QString _path = url.path(); + if (_path.at(0) == '/') { + _path.remove(0, 1); + } + + _path.toUInt(&isInt); + if (!isInt) { + invalidURI = _path; + } else { + _path.prepend("DELE "); + if ( command(_path.ascii()) != Ok ) { + invalidURI = _path; + } + } + + POP3_DEBUG << "POP3Protocol::del " << _path << endl; + finished(); +} diff --git a/kioslave/pop3/pop3.h b/kioslave/pop3/pop3.h new file mode 100644 index 000000000..94114f675 --- /dev/null +++ b/kioslave/pop3/pop3.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 1999,2000 Alex Zepeda + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef _POP3_H +#define _POP3_H + +#include <sys/types.h> +#include <sys/time.h> + +#include <stdio.h> + +#include <qstring.h> + +#include <kio/tcpslavebase.h> + +#define MAX_PACKET_LEN 4096 + +class POP3Protocol:public KIO::TCPSlaveBase { +public: + POP3Protocol(const QCString & pool, const QCString & app, bool SSL); + virtual ~ POP3Protocol(); + + virtual void setHost(const QString & host, int port, + const QString & user, const QString & pass); + + virtual void special(const QByteArray & aData); + virtual void get(const KURL & url); + virtual void stat(const KURL & url); + virtual void del(const KURL & url, bool isfile); + virtual void listDir(const KURL & url); + +protected: + + ssize_t myRead(void *data, ssize_t len); + ssize_t myReadLine(char *data, ssize_t len); + + /** + * This returns the size of a message as a long integer. + * This is useful as an internal member, because the "other" + * getSize command will emit a signal, which would be harder + * to trap when doing something like listing a directory. + */ + size_t realGetSize(unsigned int msg_num); + + /** + * Send a command to the server. Using this function, getResponse + * has to be called separately. + */ + bool sendCommand(const char *cmd); + + enum Resp{Err, Ok, Cont, Invalid}; + /** + * Send a command to the server, and wait for the one-line-status + * reply via getResponse. Similar rules apply. If no buffer is + * specified, no data is passed back. + */ + Resp command(const char *buf, char *r_buf = 0, unsigned int r_len = 0); + + /** + * All POP3 commands will generate a response. Each response will + * either be prefixed with a "+OK " or a "-ERR ". The getResponse + * function will wait until there's data to be read, and then read in + * the first line (the response), and copy the response sans +OK/-ERR + * into a buffer (up to len bytes) if one was passed to it. + */ + Resp getResponse(char *buf, unsigned int len, const char *command); + + /** Call int pop3_open() and report an error, if if fails */ + void openConnection(); + + /** + * Attempt to properly shut down the POP3 connection by sending + * "QUIT\r\n" before closing the socket. + */ + void closeConnection(); + + /** + * Attempt to initiate a POP3 connection via a TCP socket. If no port + * is passed, port 110 is assumed, if no user || password is + * specified, the user is prompted for them. + */ + bool pop3_open(); + /** + * Authenticate via APOP + */ + int loginAPOP( char *challenge, KIO::AuthInfo &ai ); + + bool saslInteract( void *in, KIO::AuthInfo &ai ); + /** + * Authenticate via SASL + */ + int loginSASL( KIO::AuthInfo &ai ); + /** + * Authenticate via traditional USER/PASS + */ + bool loginPASS( KIO::AuthInfo &ai ); + + int m_cmd; + unsigned short int m_iOldPort; + struct timeval m_tTimeout; + QString m_sOldServer, m_sOldPass, m_sOldUser; + QString m_sServer, m_sPass, m_sUser; + bool m_try_apop, m_try_sasl, opened, supports_apop; + QString m_sError; + char readBuffer[MAX_PACKET_LEN]; + ssize_t readBufferLen; +}; + +#endif diff --git a/kioslave/pop3/pop3.protocol b/kioslave/pop3/pop3.protocol new file mode 100644 index 000000000..ba20ae70d --- /dev/null +++ b/kioslave/pop3/pop3.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_pop3 +protocol=pop3 +Capabilities=SASL +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=false +deleting=true +source=true +makedir=false +linking=false +moving=false +DocPath=kioslave/pop3.html +Icon=folder_inbox diff --git a/kioslave/pop3/pop3s.protocol b/kioslave/pop3/pop3s.protocol new file mode 100644 index 000000000..2f9f848c2 --- /dev/null +++ b/kioslave/pop3/pop3s.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_pop3 +protocol=pop3s +Capabilities=SASL +input=none +output=filesystem +listing=Name,Type,Size +reading=true +writing=false +deleting=true +source=true +makedir=false +linking=false +moving=false +DocPath=kioslave/pop3s.html +Icon=folder_inbox diff --git a/kioslave/remote/Makefile.am b/kioslave/remote/Makefile.am new file mode 100644 index 000000000..073bfd12e --- /dev/null +++ b/kioslave/remote/Makefile.am @@ -0,0 +1,32 @@ +SUBDIRS= . kdedmodule +# wizard + +INCLUDES = $(all_includes) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kio_remote.la + +kio_remote_la_SOURCES = dummy.cpp +kio_remote_la_LIBADD = libkioremote.la $(LIB_KIO) +kio_remote_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined + +dummy.cpp: + echo > dummy.cpp + +kde_services_DATA = remote.protocol + +noinst_LTLIBRARIES = libkioremote.la +libkioremote_la_SOURCES = kio_remote.cpp remoteimpl.cpp + +check_PROGRAMS = testremote +testremote_SOURCES = testremote.cpp +testremote_LDADD = libkioremote.la $(LIB_KIO) +testremote_LDFLAGS = $(all_libraries) + +## TODO in unsermake: TESTS = testremote +check: testremote + ./testremote + +messages: + $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/kio_remote.pot + diff --git a/kioslave/remote/dummy.cpp b/kioslave/remote/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/kioslave/remote/dummy.cpp @@ -0,0 +1 @@ + diff --git a/kioslave/remote/kdedmodule/Makefile.am b/kioslave/remote/kdedmodule/Makefile.am new file mode 100644 index 000000000..61f4d2261 --- /dev/null +++ b/kioslave/remote/kdedmodule/Makefile.am @@ -0,0 +1,13 @@ +kde_module_LTLIBRARIES = kded_remotedirnotify.la + +METASOURCES = AUTO +INCLUDES = $(all_includes) + +kded_remotedirnotify_la_SOURCES = remotedirnotify.cpp remotedirnotify.skel remotedirnotifymodule.cpp remotedirnotifymodule.skel +kded_remotedirnotify_la_LDFLAGS = $(all_libraries) -module -avoid-version +kded_remotedirnotify_la_LIBADD = $(LIB_KSYCOCA) + + +servicesdir = $(kde_servicesdir)/kded +services_DATA = remotedirnotify.desktop + diff --git a/kioslave/remote/kdedmodule/remotedirnotify.cpp b/kioslave/remote/kdedmodule/remotedirnotify.cpp new file mode 100644 index 000000000..88933e29a --- /dev/null +++ b/kioslave/remote/kdedmodule/remotedirnotify.cpp @@ -0,0 +1,143 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "remotedirnotify.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kdesktopfile.h> + +#include <kdirnotify_stub.h> + +#include <qdir.h> + +RemoteDirNotify::RemoteDirNotify() +{ + KGlobal::dirs()->addResourceType("remote_entries", + KStandardDirs::kde_default("data") + "remoteview"); + + QString path = KGlobal::dirs()->saveLocation("remote_entries"); + m_baseURL.setPath(path); +} + +KURL RemoteDirNotify::toRemoteURL(const KURL &url) +{ + kdDebug(1220) << "RemoteDirNotify::toRemoteURL(" << url << ")" << endl; + if ( m_baseURL.isParentOf(url) ) + { + QString path = KURL::relativePath(m_baseURL.path(), + url.path()); + KURL result("remote:/"+path); + result.cleanPath(); + kdDebug(1220) << "result => " << result << endl; + return result; + } + + kdDebug(1220) << "result => KURL()" << endl; + return KURL(); +} + +KURL::List RemoteDirNotify::toRemoteURLList(const KURL::List &list) +{ + KURL::List new_list; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL url = toRemoteURL(*it); + + if (url.isValid()) + { + new_list.append(url); + } + } + + return new_list; +} + +ASYNC RemoteDirNotify::FilesAdded(const KURL &directory) +{ + kdDebug(1220) << "RemoteDirNotify::FilesAdded" << endl; + + KURL new_dir = toRemoteURL(directory); + + if (new_dir.isValid()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesAdded( new_dir ); + } +} + +// This hack is required because of the way we manage .desktop files with +// Forwarding Slaves, their URL is out of the ioslave (most remote:/ files +// have a file:/ based UDS_URL so that they are executed correctly. +// Hence, FilesRemoved and FilesChanged does nothing... We're forced to use +// FilesAdded to re-list the modified directory. +inline void evil_hack(const KURL::List &list) +{ + KDirNotify_stub notifier("*", "*"); + + KURL::List notified; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL url = (*it).upURL(); + + if (!notified.contains(url)) + { + notifier.FilesAdded(url); + notified.append(url); + } + } +} + + +ASYNC RemoteDirNotify::FilesRemoved(const KURL::List &fileList) +{ + kdDebug(1220) << "RemoteDirNotify::FilesRemoved" << endl; + + KURL::List new_list = toRemoteURLList(fileList); + + if (!new_list.isEmpty()) + { + //KDirNotify_stub notifier("*", "*"); + //notifier.FilesRemoved( new_list ); + evil_hack(new_list); + } +} + +ASYNC RemoteDirNotify::FilesChanged(const KURL::List &fileList) +{ + kdDebug(1220) << "RemoteDirNotify::FilesChanged" << endl; + + KURL::List new_list = toRemoteURLList(fileList); + + if (!new_list.isEmpty()) + { + //KDirNotify_stub notifier("*", "*"); + //notifier.FilesChanged( new_list ); + evil_hack(new_list); + } +} diff --git a/kioslave/remote/kdedmodule/remotedirnotify.desktop b/kioslave/remote/kdedmodule/remotedirnotify.desktop new file mode 100644 index 000000000..cbae89366 --- /dev/null +++ b/kioslave/remote/kdedmodule/remotedirnotify.desktop @@ -0,0 +1,64 @@ +[Desktop Entry] +Type=Service +Name=KDED Remote Base URL Notifier +Name[af]=KDED afgeleë URL inkennissteller +Name[be]=Праверка зменаў аддаленых файлаў KDED +Name[bs]=KDED udaljeno obavjeÅ¡tenje o baznom URLu +Name[ca]=Notificador d'URL de base remota KDED +Name[cs]=Démon upozorňovánà na vzdálená URL +Name[csb]=Dôwanié wiédzë ò daleczich URL-ach dlô KDED +Name[da]=KDED Ekstern basis-URL pÃ¥mindelser +Name[de]=Ãœberwachung für Dateien auf Fremdrechnern +Name[el]=Ειδοποιητής KDED για απομακÏυσμÎνη URL +Name[eo]=KDED Fora Bazo URL Atentigilo +Name[es]=Notificador de URL remota KDED +Name[et]=KDED mujalasuva URL-i teadustaja +Name[eu]=KDED urruneko URL oinarriaren iragarlea +Name[fa]=اخطاردهندۀ نشانی وب پایه دور KDED +Name[fi]=KDED:in etäverkko-osoitteen ilmaisin +Name[fr]=Notificateur d'URL distante KDED +Name[fy]=KDED ekstern basis-URL-adres melding +Name[gl]=KDED Notificador de Base de URL Remota +Name[hi]=केडीईडी रिमोट आधारित यूआरà¤à¤² नोटिफ़ायर +Name[hr]=KDED URL obavjeÅ¡tavanje udaljene baze +Name[hu]=Távoli alapcÃm-értesÃtÅ‘ +Name[is]=KDED fjarlægur grunnslóðartilkynnari +Name[it]=Notifica KDED Remote Base URL +Name[ja]=KDED リモート ベース URL 通知 +Name[kk]=Желідегі диÑкідегі Ó©Ð·Ð³ÐµÑ€Ñ–Ñ Ñ‚ÑƒÑ€Ð°Ð»Ñ‹ қулақтандыру +Name[ko]=KDED ì›ê²© 기반 URL 알리미 +Name[lt]=KDED nutolusio pagrindinio URL priminiklis +Name[lv]=KDED attÄlinÄtÄ URL bÄzes atgÄdinÄtÄjs +Name[ms]=Pemberitahu URL Pangkalan Jauh KDED +Name[nb]=KDED-pÃ¥minner for eksterne nettadresser +Name[nds]=KDED-Narichten för feern Basis-URLs +Name[ne]=KDED रिमोटमा आधारित यूआरà¤à¤² सूचक +Name[nl]=KDED extern basis-URL-adres notificatie +Name[nn]=KDED-pÃ¥minnar for eksterne nettadressar +Name[pa]=KDED ਰਿਮੋਟ ਅਧਾਰਿਤ URL ਸੂਚਨਾ +Name[pl]=Powiadamianie o zdalnych URL-ach dla KDED +Name[pt]=Notificador de URLs de Base Remotos do KDED +Name[pt_BR]=Serviço de Notificação da URL Remota +Name[ro]=Notificare KDED pentru URL distant de bază +Name[ru]=Проверка Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñетевых файлов +Name[rw]=Mumenyekanisha wa URL KDED Yakure Shingiro +Name[sk]=KDED notifikátor vzdialenej URL +Name[sl]=Obvestilnik KDED oddaljenega osnovnega URL-ja +Name[sr]=Обавештавач о удаљеном базном URL-у, KDED +Name[sr@Latn]=ObaveÅ¡tavaÄ o udaljenom baznom URL-u, KDED +Name[sv]=KDED-meddelande om fjärrbaswebbadresser +Name[ta]=KDED தொலைதூரம௠சாரà¯à®¨à¯à®¤ வலைமனை கà¯à®±à®¿à®ªà¯à®ªà®¾à®©à¯ +Name[th]=ตัวà¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™ Remote Base URL KDED +Name[tr]=KDED Uzak Tabanlı URL Hatırlatıcı +Name[tt]=Çittäge URL Ãœzgärelü Beldergeçe +Name[uk]=Сповіщувач про віддалену оÑновну адреÑу (URL) Ð´Ð»Ñ KDED +Name[vi]=Trình thông báo URL trên mạng KDED +Name[wa]=Notifiaedje KDED d' URL di bÃ¥ze Ã¥ lon +Name[zh_CN]=KDED 远程基 URL 通知器 +Name[zh_TW]=KDED é 端基礎 URL é€šçŸ¥ç¨‹å¼ +ServiceTypes=KDEDModule +X-KDE-ModuleType=Library +X-KDE-Library=remotedirnotify +X-KDE-FactoryName=remotedirnotify +X-KDE-Kded-load-on-demand=true +X-KDE-Kded-autoload=true diff --git a/kioslave/remote/kdedmodule/remotedirnotify.h b/kioslave/remote/kdedmodule/remotedirnotify.h new file mode 100644 index 000000000..110d53a9d --- /dev/null +++ b/kioslave/remote/kdedmodule/remotedirnotify.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef REMOTEDIRNOTIFY_H +#define REMOTEDIRNOTIFY_H + +#include <kurl.h> +#include <kdirnotify.h> + +class RemoteDirNotify : public KDirNotify +{ +K_DCOP + +public: + RemoteDirNotify(); + +k_dcop: + virtual ASYNC FilesAdded (const KURL &directory); + virtual ASYNC FilesRemoved (const KURL::List &fileList); + virtual ASYNC FilesChanged (const KURL::List &fileList); + +private: + KURL toRemoteURL(const KURL &url); + KURL::List toRemoteURLList(const KURL::List &list); + KURL m_baseURL; +}; + +#endif diff --git a/kioslave/remote/kdedmodule/remotedirnotifymodule.cpp b/kioslave/remote/kdedmodule/remotedirnotifymodule.cpp new file mode 100644 index 000000000..5877ac299 --- /dev/null +++ b/kioslave/remote/kdedmodule/remotedirnotifymodule.cpp @@ -0,0 +1,37 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "remotedirnotifymodule.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> + +RemoteDirNotifyModule::RemoteDirNotifyModule(const QCString &obj) + : KDEDModule(obj) +{ +} + +extern "C" { + KDE_EXPORT KDEDModule *create_remotedirnotify(const QCString &obj) + { + KGlobal::locale()->insertCatalogue("kio_remote"); + return new RemoteDirNotifyModule(obj); + } +} + diff --git a/kioslave/remote/kdedmodule/remotedirnotifymodule.h b/kioslave/remote/kdedmodule/remotedirnotifymodule.h new file mode 100644 index 000000000..4ce8c2c7d --- /dev/null +++ b/kioslave/remote/kdedmodule/remotedirnotifymodule.h @@ -0,0 +1,36 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef REMOTEDIRNOTIFYMODULE_H +#define REMOTEDIRNOTIFYMODULE_H + +#include <kdedmodule.h> + +#include "remotedirnotify.h" + +class RemoteDirNotifyModule : public KDEDModule +{ +K_DCOP + +public: + RemoteDirNotifyModule(const QCString &obj); +private: + RemoteDirNotify notifier; +}; + +#endif diff --git a/kioslave/remote/kio_remote.cpp b/kioslave/remote/kio_remote.cpp new file mode 100644 index 000000000..1200f943c --- /dev/null +++ b/kioslave/remote/kio_remote.cpp @@ -0,0 +1,234 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <stdlib.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <kcmdlineargs.h> +#include <kglobal.h> + + +#include "kio_remote.h" + +static const KCmdLineOptions options[] = +{ + { "+protocol", I18N_NOOP( "Protocol name" ), 0 }, + { "+pool", I18N_NOOP( "Socket name" ), 0 }, + { "+app", I18N_NOOP( "Socket name" ), 0 }, + KCmdLineLastOption +}; + +extern "C" { + int KDE_EXPORT kdemain( int argc, char **argv ) + { + // KApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + KCmdLineArgs::init(argc, argv, "kio_remote", 0, 0, 0, 0); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + RemoteProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +RemoteProtocol::RemoteProtocol(const QCString &protocol, + const QCString &pool, const QCString &app) + : SlaveBase(protocol, pool, app) +{ +} + +RemoteProtocol::~RemoteProtocol() +{ +} + +void RemoteProtocol::listDir(const KURL &url) +{ + kdDebug(1220) << "RemoteProtocol::listDir: " << url << endl; + + if ( url.path().length() <= 1 ) + { + listRoot(); + return; + } + + int second_slash_idx = url.path().find( '/', 1 ); + QString root_dirname = url.path().mid( 1, second_slash_idx-1 ); + + KURL target = m_impl.findBaseURL( root_dirname ); + kdDebug(1220) << "possible redirection target : " << target << endl; + if( target.isValid() ) + { + target.addPath( url.path().remove(0, second_slash_idx) ); + redirection(target); + finished(); + return; + } + + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); +} + +void RemoteProtocol::listRoot() +{ + KIO::UDSEntry entry; + + KIO::UDSEntryList remote_entries; + m_impl.listRoot(remote_entries); + + totalSize(remote_entries.count()+2); + + m_impl.createTopLevelEntry(entry); + listEntry(entry, false); + + m_impl.createWizardEntry(entry); + listEntry(entry, false); + + KIO::UDSEntryListIterator it = remote_entries.begin(); + KIO::UDSEntryListIterator end = remote_entries.end(); + + for(; it!=end; ++it) + { + listEntry(*it, false); + } + + entry.clear(); + listEntry(entry, true); + + finished(); +} + +void RemoteProtocol::stat(const KURL &url) +{ + kdDebug(1220) << "RemoteProtocol::stat: " << url << endl; + + QString path = url.path(); + if ( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + KIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + if (m_impl.isWizardURL(url)) + { + KIO::UDSEntry entry; + if (m_impl.createWizardEntry(entry)) + { + statEntry(entry); + finished(); + } + else + { + error(KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + return; + } + + int second_slash_idx = url.path().find( '/', 1 ); + QString root_dirname = url.path().mid( 1, second_slash_idx-1 ); + + if ( second_slash_idx==-1 || ( (int)url.path().length() )==second_slash_idx+1 ) + { + KIO::UDSEntry entry; + if (m_impl.statNetworkFolder(entry, root_dirname)) + { + statEntry(entry); + finished(); + return; + } + } + else + { + KURL target = m_impl.findBaseURL( root_dirname ); + kdDebug( 1220 ) << "possible redirection target : " << target << endl; + if ( target.isValid() ) + { + target.addPath( url.path().remove( 0, second_slash_idx ) ); + redirection( target ); + finished(); + return; + } + } + + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); +} + +void RemoteProtocol::del(const KURL &url, bool /*isFile*/) +{ + kdDebug(1220) << "RemoteProtocol::del: " << url << endl; + + if (!m_impl.isWizardURL(url) + && m_impl.deleteNetworkFolder(url.fileName())) + { + finished(); + return; + } + + error(KIO::ERR_CANNOT_DELETE, url.prettyURL()); +} + +void RemoteProtocol::get(const KURL &url) +{ + kdDebug(1220) << "RemoteProtocol::get: " << url << endl; + + QString file = m_impl.findDesktopFile( url.fileName() ); + kdDebug(1220) << "desktop file : " << file << endl; + + if (!file.isEmpty()) + { + KURL desktop; + desktop.setPath(file); + + redirection(desktop); + finished(); + return; + } + + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); +} + +void RemoteProtocol::rename(const KURL &src, const KURL &dest, + bool overwrite) +{ + if (src.protocol()!="remote" || dest.protocol()!="remote" + || m_impl.isWizardURL(src) || m_impl.isWizardURL(dest)) + { + error(KIO::ERR_UNSUPPORTED_ACTION, src.prettyURL()); + return; + } + + if (m_impl.renameFolders(src.fileName(), dest.fileName(), overwrite)) + { + finished(); + return; + } + + error(KIO::ERR_CANNOT_RENAME, src.prettyURL()); +} diff --git a/kioslave/remote/kio_remote.h b/kioslave/remote/kio_remote.h new file mode 100644 index 000000000..7d3b0366e --- /dev/null +++ b/kioslave/remote/kio_remote.h @@ -0,0 +1,45 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef KIO_REMOTE_H +#define KIO_REMOTE_H + +#include <kio/slavebase.h> +#include "remoteimpl.h" + +class RemoteProtocol : public KIO::SlaveBase +{ +public: + RemoteProtocol(const QCString &protocol, const QCString &pool, + const QCString &app); + virtual ~RemoteProtocol(); + + virtual void listDir(const KURL &url); + virtual void stat(const KURL &url); + virtual void del(const KURL &url, bool isFile); + virtual void get(const KURL &url); + virtual void rename(const KURL &src, const KURL &dest, bool overwrite); + +private: + void listRoot(); + + RemoteImpl m_impl; +}; + +#endif diff --git a/kioslave/remote/remote.protocol b/kioslave/remote/remote.protocol new file mode 100644 index 000000000..6554be442 --- /dev/null +++ b/kioslave/remote/remote.protocol @@ -0,0 +1,17 @@ +[Protocol] +exec=kio_remote +protocol=remote +input=none +output=filesystem +listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link +reading=true +writing=false +makedir=false +deleting=true +linking=true +moving=true +Icon=network +maxInstances=4 +#TODO DocPath=kioslave/file.html +Class=:local +deleteRecursive=true diff --git a/kioslave/remote/remoteimpl.cpp b/kioslave/remote/remoteimpl.cpp new file mode 100644 index 000000000..139cbfd2a --- /dev/null +++ b/kioslave/remote/remoteimpl.cpp @@ -0,0 +1,298 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "remoteimpl.h" + +#include <kdebug.h> +#include <kglobalsettings.h> +#include <kstandarddirs.h> +#include <kdesktopfile.h> +#include <kservice.h> +#include <klocale.h> + +#include <qdir.h> +#include <qfile.h> + +#include <sys/stat.h> + +#define WIZARD_URL "remote:/x-wizard_service.desktop" +#define WIZARD_SERVICE "knetattach" + +RemoteImpl::RemoteImpl() +{ + KGlobal::dirs()->addResourceType("remote_entries", + KStandardDirs::kde_default("data") + "remoteview"); + + QString path = KGlobal::dirs()->saveLocation("remote_entries"); + + QDir dir = path; + if (!dir.exists()) + { + dir.cdUp(); + dir.mkdir("remoteview"); + } +} + +void RemoteImpl::listRoot(QValueList<KIO::UDSEntry> &list) const +{ + kdDebug(1220) << "RemoteImpl::listRoot" << endl; + + QStringList names_found; + QStringList dirList = KGlobal::dirs()->resourceDirs("remote_entries"); + + QStringList::ConstIterator dirpath = dirList.begin(); + QStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + QDir dir = *dirpath; + if (!dir.exists()) continue; + + QStringList filenames + = dir.entryList( QDir::Files | QDir::Readable ); + + + KIO::UDSEntry entry; + + QStringList::ConstIterator name = filenames.begin(); + QStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (!names_found.contains(*name)) + { + entry.clear(); + createEntry(entry, *dirpath, *name); + list.append(entry); + names_found.append(*name); + } + } + } +} + +bool RemoteImpl::findDirectory(const QString &filename, QString &directory) const +{ + kdDebug(1220) << "RemoteImpl::findDirectory" << endl; + + QStringList dirList = KGlobal::dirs()->resourceDirs("remote_entries"); + + QStringList::ConstIterator dirpath = dirList.begin(); + QStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + QDir dir = *dirpath; + if (!dir.exists()) continue; + + QStringList filenames + = dir.entryList( QDir::Files | QDir::Readable ); + + + KIO::UDSEntry entry; + + QStringList::ConstIterator name = filenames.begin(); + QStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (*name==filename) + { + directory = *dirpath; + return true; + } + } + } + + return false; +} + +QString RemoteImpl::findDesktopFile(const QString &filename) const +{ + kdDebug(1220) << "RemoteImpl::findDesktopFile" << endl; + + QString directory; + if (findDirectory(filename+".desktop", directory)) + { + return directory+filename+".desktop"; + } + + return QString::null; +} + +KURL RemoteImpl::findBaseURL(const QString &filename) const +{ + kdDebug(1220) << "RemoteImpl::findBaseURL" << endl; + + QString file = findDesktopFile(filename); + if (!file.isEmpty()) + { + KDesktopFile desktop(file, true); + return desktop.readURL(); + } + + return KURL(); +} + + +static void addAtom(KIO::UDSEntry &entry, unsigned int ID, long l, + const QString &s = QString::null) +{ + KIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void RemoteImpl::createTopLevelEntry(KIO::UDSEntry &entry) const +{ + entry.clear(); + addAtom(entry, KIO::UDS_NAME, 0, "."); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_ACCESS, 0555); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, KIO::UDS_ICON_NAME, 0, "network"); +} + +static KURL findWizardRealURL() +{ + KURL url; + KService::Ptr service = KService::serviceByDesktopName(WIZARD_SERVICE); + + if (service && service->isValid()) + { + url.setPath( locate("apps", + service->desktopEntryPath()) + ); + } + + return url; +} + +bool RemoteImpl::createWizardEntry(KIO::UDSEntry &entry) const +{ + entry.clear(); + + KURL url = findWizardRealURL(); + + if (!url.isValid()) + { + return false; + } + + addAtom(entry, KIO::UDS_NAME, 0, i18n("Add a Network Folder")); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFREG); + addAtom(entry, KIO::UDS_URL, 0, WIZARD_URL); + addAtom(entry, KIO::UDS_LOCAL_PATH, 0, url.path()); + addAtom(entry, KIO::UDS_ACCESS, 0500); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "application/x-desktop"); + addAtom(entry, KIO::UDS_ICON_NAME, 0, "wizard"); + + return true; +} + +bool RemoteImpl::isWizardURL(const KURL &url) const +{ + return url==KURL(WIZARD_URL); +} + + +void RemoteImpl::createEntry(KIO::UDSEntry &entry, + const QString &directory, + const QString &file) const +{ + kdDebug(1220) << "RemoteImpl::createEntry" << endl; + + KDesktopFile desktop(directory+file, true); + + kdDebug(1220) << "path = " << directory << file << endl; + + entry.clear(); + + QString new_filename = file; + new_filename.truncate( file.length()-8); + + addAtom(entry, KIO::UDS_NAME, 0, desktop.readName()); + addAtom(entry, KIO::UDS_URL, 0, "remote:/"+new_filename); + + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + + QString icon = desktop.readIcon(); + + addAtom(entry, KIO::UDS_ICON_NAME, 0, icon); + addAtom(entry, KIO::UDS_LINK_DEST, 0, desktop.readURL()); +} + +bool RemoteImpl::statNetworkFolder(KIO::UDSEntry &entry, const QString &filename) const +{ + kdDebug(1220) << "RemoteImpl::statNetworkFolder: " << filename << endl; + + QString directory; + if (findDirectory(filename+".desktop", directory)) + { + createEntry(entry, directory, filename+".desktop"); + return true; + } + + return false; +} + +bool RemoteImpl::deleteNetworkFolder(const QString &filename) const +{ + kdDebug(1220) << "RemoteImpl::deleteNetworkFolder: " << filename << endl; + + QString directory; + if (findDirectory(filename+".desktop", directory)) + { + kdDebug(1220) << "Removing " << directory << filename << ".desktop" << endl; + return QFile::remove(directory+filename+".desktop"); + } + + return false; +} + +bool RemoteImpl::renameFolders(const QString &src, const QString &dest, + bool overwrite) const +{ + kdDebug(1220) << "RemoteImpl::renameFolders: " + << src << ", " << dest << endl; + + QString directory; + if (findDirectory(src+".desktop", directory)) + { + if (!overwrite && QFile::exists(directory+dest+".desktop")) + { + return false; + } + + kdDebug(1220) << "Renaming " << directory << src << ".desktop"<< endl; + QDir dir(directory); + bool res = dir.rename(src+".desktop", dest+".desktop"); + if (res) + { + KDesktopFile desktop(directory+dest+".desktop"); + desktop.writeEntry("Name", dest); + } + return res; + } + + return false; +} + + diff --git a/kioslave/remote/remoteimpl.h b/kioslave/remote/remoteimpl.h new file mode 100644 index 000000000..ba7390bf4 --- /dev/null +++ b/kioslave/remote/remoteimpl.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef REMOTEIMPL_H +#define REMOTEIMPL_H + +#include <kio/global.h> +#include <kio/job.h> +#include <kurl.h> + +#include <qstring.h> + +class RemoteImpl +{ +public: + RemoteImpl(); + + void createTopLevelEntry(KIO::UDSEntry &entry) const; + bool createWizardEntry(KIO::UDSEntry &entry) const; + bool isWizardURL(const KURL &url) const; + bool statNetworkFolder(KIO::UDSEntry &entry, const QString &filename) const; + + void listRoot(QValueList<KIO::UDSEntry> &list) const; + + KURL findBaseURL(const QString &filename) const; + QString findDesktopFile(const QString &filename) const; + + bool deleteNetworkFolder(const QString &filename) const; + bool renameFolders(const QString &src, const QString &dest, + bool overwrite) const; + +private: + bool findDirectory(const QString &filename, QString &directory) const; + void createEntry(KIO::UDSEntry& entry, const QString &directory, + const QString &file) const; +}; + +#endif diff --git a/kioslave/remote/testremote.cpp b/kioslave/remote/testremote.cpp new file mode 100644 index 000000000..a3e979023 --- /dev/null +++ b/kioslave/remote/testremote.cpp @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kio_remote.h" +#include "testremote.h" + +#include <config.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#include <stdlib.h> + +static bool check(const QString& txt, QString a, QString b) +{ + if (a.isEmpty()) + a = QString::null; + if (b.isEmpty()) + b = QString::null; + if (a == b) { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +int main(int argc, char *argv[]) +{ + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc,argv,"testremote", 0, 0, 0, 0); + KApplication app; + + TestRemote test; + test.setup(); + test.runAll(); + kdDebug() << "All tests OK." << endl; + return 0; // success. The exit(1) in check() is what happens in case of failure. +} + +void TestRemote::setup() +{ + +} + +void TestRemote::runAll() +{ + +} + diff --git a/kioslave/remote/testremote.h b/kioslave/remote/testremote.h new file mode 100644 index 000000000..ba51a8602 --- /dev/null +++ b/kioslave/remote/testremote.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef TESTREMOTE_H +#define TESTREMOTE_H + +class TestRemote +{ +public: + TestRemote() {} + void setup(); + void runAll(); + + // tests + +}; + +#endif diff --git a/kioslave/settings/Makefile.am b/kioslave/settings/Makefile.am new file mode 100644 index 000000000..e302f7d90 --- /dev/null +++ b/kioslave/settings/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES= $(all_includes) + +####### Files + +kde_module_LTLIBRARIES = kio_settings.la + +kio_settings_la_SOURCES = kio_settings.cc +kio_settings_la_LIBADD = $(LIB_KSYCOCA) +kio_settings_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_settings.h + +kdelnk_DATA = settings.protocol programs.protocol applications.protocol +kdelnkdir = $(kde_servicesdir) + + +SUBDIRS=. + +messages: + $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/kio_settings.pot + diff --git a/kioslave/settings/applications.protocol b/kioslave/settings/applications.protocol new file mode 100644 index 000000000..0193dfb8b --- /dev/null +++ b/kioslave/settings/applications.protocol @@ -0,0 +1,15 @@ +[Protocol] +exec=kio_settings +protocol=applications +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link, +reading=true +writing=false +makedir=false +deleting=false +linking=false +moving=false +Icon=kmenu +maxInstances=2 +Class=:local diff --git a/kioslave/settings/kio_settings.cc b/kioslave/settings/kio_settings.cc new file mode 100644 index 000000000..4d5bbe873 --- /dev/null +++ b/kioslave/settings/kio_settings.cc @@ -0,0 +1,298 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger <jowenn@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <kio/slavebase.h> +#include <kinstance.h> +#include <kdebug.h> +#include <qtextstream.h> +#include <klocale.h> +#include <sys/stat.h> +#include <dcopclient.h> +#include <qdatastream.h> +#include <time.h> +#include <kprocess.h> +#include <kservice.h> +#include <kservicegroup.h> +#include <kstandarddirs.h> + +class SettingsProtocol : public KIO::SlaveBase +{ +public: + enum RunMode { SettingsMode, ProgramsMode, ApplicationsMode }; + SettingsProtocol(const QCString &protocol, const QCString &pool, const QCString &app); + virtual ~SettingsProtocol(); + virtual void get( const KURL& url ); + virtual void stat(const KURL& url); + virtual void listDir(const KURL& url); + void listRoot(); + KServiceGroup::Ptr findGroup(const QString &relPath); + +private: + DCOPClient *m_dcopClient; + RunMode m_runMode; +}; + +extern "C" { + KDE_EXPORT int kdemain( int, char **argv ) + { + kdDebug() << "kdemain for settings kioslave" << endl; + KInstance instance( "kio_settings" ); + SettingsProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + return 0; + } +} + + +static void addAtom(KIO::UDSEntry& entry, unsigned int ID, long l, const QString& s = QString::null) +{ + KIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + +static void createFileEntry(KIO::UDSEntry& entry, const QString& name, const QString& url, const QString& mime, const QString& iconName, const QString& localPath) +{ + entry.clear(); + addAtom(entry, KIO::UDS_NAME, 0, KIO::encodeFileName(name)); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFREG); + addAtom(entry, KIO::UDS_URL, 0, url); + addAtom(entry, KIO::UDS_ACCESS, 0500); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, mime); + addAtom(entry, KIO::UDS_SIZE, 0); + addAtom(entry, KIO::UDS_LOCAL_PATH, 0, localPath); + addAtom(entry, KIO::UDS_CREATION_TIME, 1); + addAtom(entry, KIO::UDS_MODIFICATION_TIME, time(0)); + addAtom(entry, KIO::UDS_ICON_NAME, 0, iconName); +} + +static void createDirEntry(KIO::UDSEntry& entry, const QString& name, const QString& url, const QString& mime,const QString& iconName) +{ + entry.clear(); + addAtom(entry, KIO::UDS_NAME, 0, name); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_ACCESS, 0500); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, mime); + addAtom(entry, KIO::UDS_URL, 0, url); + addAtom(entry, KIO::UDS_SIZE, 0); + addAtom(entry, KIO::UDS_ICON_NAME, 0, iconName); +} + +SettingsProtocol::SettingsProtocol( const QCString &protocol, const QCString &pool, const QCString &app): SlaveBase( protocol, pool, app ) +{ + // Adjusts which part of the K Menu to virtualize. + if ( protocol == "programs" ) + m_runMode = ProgramsMode; + else + if (protocol == "applications") + m_runMode = ApplicationsMode; + else + m_runMode = SettingsMode; + + m_dcopClient = new DCOPClient(); + if (!m_dcopClient->attach()) + { + kdDebug() << "ERROR WHILE CONNECTING TO DCOPSERVER" << endl; + } +} + +SettingsProtocol::~SettingsProtocol() +{ + delete m_dcopClient; +} + +KServiceGroup::Ptr SettingsProtocol::findGroup(const QString &relPath) +{ + QString nextPart; + QString alreadyFound("Settings/"); + QStringList rest = QStringList::split('/', relPath); + + kdDebug() << "Trying harder to find group " << relPath << endl; + for (unsigned int i=0; i<rest.count(); i++) + kdDebug() << "Item (" << *rest.at(i) << ")" << endl; + + while (!rest.isEmpty()) { + KServiceGroup::Ptr tmp = KServiceGroup::group(alreadyFound); + if (!tmp || !tmp->isValid()) + return 0; + + KServiceGroup::List list = tmp->entries(true, true); + KServiceGroup::List::ConstIterator it = list.begin(); + + bool found = false; + for (; it != list.end(); ++it) { + KSycocaEntry *e = *it; + if (e->isType(KST_KServiceGroup)) { + KServiceGroup::Ptr g(static_cast<KServiceGroup *>(e)); + if ((g->caption()==rest.front()) || (g->name()==alreadyFound+rest.front())) { + kdDebug() << "Found group with caption " << g->caption() + << " with real name: " << g->name() << endl; + found = true; + rest.remove(rest.begin()); + alreadyFound = g->name(); + kdDebug() << "ALREADY FOUND: " << alreadyFound << endl; + break; + } + } + } + + if (!found) { + kdDebug() << "Group with caption " << rest.front() << " not found within " + << alreadyFound << endl; + return 0; + } + + } + return KServiceGroup::group(alreadyFound); +} + +void SettingsProtocol::get( const KURL & url ) +{ + KService::Ptr service = KService::serviceByDesktopName(url.fileName()); + if (service && service->isValid()) { + KURL redirUrl; + redirUrl.setPath(locate("apps", service->desktopEntryPath())); + redirection(redirUrl); + finished(); + } else { + error( KIO::ERR_IS_DIRECTORY, url.prettyURL() ); + } +} + + +void SettingsProtocol::stat(const KURL& url) +{ + KIO::UDSEntry entry; + + QString servicePath( url.path(1) ); + servicePath.remove(0, 1); // remove starting '/' + + if ( m_runMode == SettingsMode) + servicePath = "Settings/" + servicePath; + + KServiceGroup::Ptr grp = KServiceGroup::group(servicePath); + + if (grp && grp->isValid()) { + createDirEntry(entry, (m_runMode == SettingsMode) ? i18n("Settings") : ( (m_runMode==ApplicationsMode) ? i18n("Applications") : i18n("Programs")), + url.url(), "inode/directory",grp->icon() ); + } else { + KService::Ptr service = KService::serviceByDesktopName( url.fileName() ); + if (service && service->isValid()) { +// KURL newUrl; +// newUrl.setPath(locate("apps", service->desktopEntryPath())); +// createFileEntry(entry, service->name(), newUrl, "application/x-desktop", service->icon()); + + createFileEntry(entry, service->name(), url.url(1)+service->desktopEntryName(), + "application/x-desktop", service->icon(), locate("apps", service->desktopEntryPath()) ); + } else { + error(KIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); + return; + } + } + + statEntry(entry); + finished(); + return; +} + + +void SettingsProtocol::listDir(const KURL& url) +{ + QString groupPath = url.path(1); + groupPath.remove(0, 1); // remove starting '/' + + if ( m_runMode == SettingsMode) + groupPath.prepend("Settings/"); + + KServiceGroup::Ptr grp = KServiceGroup::group(groupPath); + + if (!grp || !grp->isValid()) { + grp = findGroup(groupPath); + if (!grp || !grp->isValid()) { + error(KIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); + return; + } + } + + unsigned int count = 0; + KIO::UDSEntry entry; + + KServiceGroup::List list = grp->entries(true, true); + KServiceGroup::List::ConstIterator it; + + for (it = list.begin(); it != list.end(); ++it) { + KSycocaEntry * e = *it; + + if (e->isType(KST_KServiceGroup)) { + KServiceGroup::Ptr g(static_cast<KServiceGroup *>(e)); + QString groupCaption = g->caption(); + + // Avoid adding empty groups. + KServiceGroup::Ptr subMenuRoot = KServiceGroup::group(g->relPath()); + if (subMenuRoot->childCount() == 0) + continue; + + // Ignore dotfiles. + if ((g->name().at(0) == '.')) + continue; + + QString relPath = g->relPath(); + + // Do not display the "Settings" menu group in Programs Mode. + if( (m_runMode == ProgramsMode) && relPath.startsWith( "Settings" ) ) + { + kdDebug() << "SettingsProtocol: SKIPPING entry programs:/" << relPath << endl; + continue; + } + + switch( m_runMode ) + { + case( SettingsMode ): + relPath.remove(0, 9); // length("Settings/") ==9 + kdDebug() << "SettingsProtocol: adding entry settings:/" << relPath << endl; + createDirEntry(entry, groupCaption, "settings:/"+relPath, "inode/directory",g->icon()); + break; + case( ProgramsMode ): + kdDebug() << "SettingsProtocol: adding entry programs:/" << relPath << endl; + createDirEntry(entry, groupCaption, "programs:/"+relPath, "inode/directory",g->icon()); + break; + case( ApplicationsMode ): + kdDebug() << "SettingsProtocol: adding entry applications:/" << relPath << endl; + createDirEntry(entry, groupCaption, "applications:/"+relPath, "inode/directory",g->icon()); + break; + } + + } else { + KService::Ptr s(static_cast<KService *>(e)); + kdDebug() << "SettingsProtocol: adding file entry " << url.url(1)+s->name() << endl; + createFileEntry(entry,s->name(),url.url(1)+s->desktopEntryName(), "application/x-desktop",s->icon(),locate("apps", s->desktopEntryPath())); + } + + listEntry(entry, false); + count++; + } + + totalSize(count); + listEntry(entry, true); + finished(); +} + +// vim: ts=4 sw=4 et diff --git a/kioslave/settings/kio_settings.h b/kioslave/settings/kio_settings.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/kioslave/settings/kio_settings.h diff --git a/kioslave/settings/programs.protocol b/kioslave/settings/programs.protocol new file mode 100644 index 000000000..c09ac6372 --- /dev/null +++ b/kioslave/settings/programs.protocol @@ -0,0 +1,15 @@ +[Protocol] +exec=kio_settings +protocol=programs +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link, +reading=true +writing=false +makedir=false +deleting=false +linking=false +moving=false +Icon=kmenu +maxInstances=2 +Class=:local diff --git a/kioslave/settings/settings.protocol b/kioslave/settings/settings.protocol new file mode 100644 index 000000000..268bc0c69 --- /dev/null +++ b/kioslave/settings/settings.protocol @@ -0,0 +1,15 @@ +[Protocol] +exec=kio_settings +protocol=settings +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link, +reading=true +writing=false +makedir=false +deleting=false +linking=false +moving=false +Icon=kcontrol +maxInstances=2 +Class=:local diff --git a/kioslave/sftp/AUTHORS b/kioslave/sftp/AUTHORS new file mode 100644 index 000000000..c763d00bc --- /dev/null +++ b/kioslave/sftp/AUTHORS @@ -0,0 +1,3 @@ +Dawit Alemayehu <adawit@kde.org> +Lucas Fisher <ljfisher@iastate.edu> + diff --git a/kioslave/sftp/CHANGELOG b/kioslave/sftp/CHANGELOG new file mode 100644 index 000000000..b41c17019 --- /dev/null +++ b/kioslave/sftp/CHANGELOG @@ -0,0 +1,59 @@ +- add dialog to ask for username +- rename() causes SSH to die +- How to handle overwrite? +- After the user cancels with the stop button, we get ERR_CANNOT_LAUNCH_PROCESS + errors, until we kill the ioslave. Same thing after trying the wrong passwd + too many times. + This is happening because KProcess thinks that the ssh process is still running + even though it exited. +- How to handle password and caching? + - Write our own askpass program using kde + - set env SSH_ASKPASS_PROGRAM before launching + -how to do this? KProcess doesn't give us access to env variables. + - Our askpass program can probably talk to the kdesu daemon to implement caching. +- chmod() succeeds, but konqueror always puts permissions to 0 afterwards. The properties + dialog is right though. + Nevermind - ftp ioslave does this too! Maybe a bug with konqueror. +- stat does not give us group and owner names, only numbers. We could cache the uid/name and + gid/name so we can give names when doing a stat also. + +7-13-2001 - ReadLink stopped working. sftp server always retuns a file not found error + - Need to implement 64 bit file lengths-->write DataStream << for u_int64 + Still need to offer 32 bit size since this is what kde wants. ljf + - rename() isn't exactly causing ioslave to die. The stat of the file we are + going to rename is killing the slave. The slave dies in the statEntry() call. + I don't know what I am putting in the UDS entry that is causing this. ljf +7-14-2001 - got put, mimetype working ljf + - fixed readlink problem - I was sending the wrong path. doh! ljf +7-17-2001 - If the user changes the host, the slave doesn't change host! setHost() is not + called, nor is another ioslave spawned. I have not investigated the problem + yet. ljf +7-21-2001 - got slave working with kde 2.2 cvs +7-22-2001 - probable solution to getting password prompt -- open with controlling + but don't connect stdin/out to terminal. duh! +8-9-2001 - Doh! I haven't kept very good logs. Look at the cvs logs for better info. + - At this point kio_sftp is using KSshProcess which I wrote in order to make + a standard interface to the various version of ssh out there. So far it is + working fairly well. We also now report host key changes to the user and + allow them to choose whether or not to continue. This is a big improvement. + - Todo: support use of keys and ssh agent + put()'s resume functionality needs some work +1-26-2002 - Rewrote put() following the ftp::put() so it should behave the same way + - increase the size of the data packet we ask for in ::get up to 60k. + Through-put increases nicely. + - Call closeConnection() from construction. Keeps from having unused ssh + processes laying around after failed operations. +2-19-2002 - get() now emits mimetype, fixes problem with konqi not downloading file for + viewing in kpart. + - get port number using getservbyname instead of hard coding it. +2-27-2002 - testing before committing back to cvs, test with openssh 3, ssh 3 +6-?-2002 - rewrote openConnection() to using new KSshProcess connect proceedures +7-20-2002 - Don't put up a message box when auth fails because of now or changed key, + the call to error() will put up the dialog. + - Connect fails and no more password are prompted for when we get + ERR_AUTH_FAILED from KSshProcess. +9-15-2002 - stuff +9-29-2002 - the last i18n string updates, fixed problem with uploading files to + openssh server. +5-8-2003 - check whether operation types are supported by the negotiated sftp + protocol version diff --git a/kioslave/sftp/DEBUGGING b/kioslave/sftp/DEBUGGING new file mode 100644 index 000000000..89ae8fe18 --- /dev/null +++ b/kioslave/sftp/DEBUGGING @@ -0,0 +1,12 @@ +DEBUGGING + +The best way to debug this slave is to send debug info to a +file using 'kdebugDialog --fullmode'. Then you can 'tail -f' the file to +see debug messages in real-time. + +I also suggest getting the openssh source and recompiling sftp-server to +send messages to the auth log. This can be done in sftp-server.c be defining +DEBUG_SFTP_SERVER. + +You can do the same with the ssh client by finding the two calls to log_init() +in ssh.c and changing the last argument from 1 to 0 and recompiling. diff --git a/kioslave/sftp/Makefile.am b/kioslave/sftp/Makefile.am new file mode 100644 index 000000000..24ebc5aef --- /dev/null +++ b/kioslave/sftp/Makefile.am @@ -0,0 +1,25 @@ +## Makefile.am of kdebase/kioslave/sftp + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) +METASOURCES = AUTO + +####### Files + +check_PROGRAMS = ksshprocesstest + +ksshprocesstest_SOURCES = ksshprocesstest.cpp +ksshprocesstest_LDADD = $(LIB_KSYCOCA) ksshprocess.lo process.lo atomicio.lo + +kde_module_LTLIBRARIES = kio_sftp.la + +kio_sftp_la_SOURCES = process.cpp atomicio.cpp kio_sftp.cpp sftpfileattr.cpp ksshprocess.cpp +kio_sftp_la_LIBADD = $(LIB_KIO) +kio_sftp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +noinst_HEADERS = atomicio.h kio_sftp.h ksshprocess.h process.h sftpfileattr.h sftp.h + +kdelnk_DATA = sftp.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_sftp.pot diff --git a/kioslave/sftp/TODO b/kioslave/sftp/TODO new file mode 100644 index 000000000..0f1411317 --- /dev/null +++ b/kioslave/sftp/TODO @@ -0,0 +1,5 @@ +TODO: +===== + +- Support for use of public keys, maybe ssh-agent, a key management app, etc. + diff --git a/kioslave/sftp/atomicio.cpp b/kioslave/sftp/atomicio.cpp new file mode 100644 index 000000000..057f20fe9 --- /dev/null +++ b/kioslave/sftp/atomicio.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//#include "includes.h" +//RCSID("$OpenBSD: atomicio.c,v 1.9 2001/03/02 18:54:30 deraadt Exp $"); + +//#include "xmalloc.h" +#include "atomicio.h" +#include <unistd.h> +#include <errno.h> +#include <kdebug.h> + +/* + * ensure all of data on socket comes through. f==read || f==write + */ + +ssize_t atomicio(int fd, char *_s, size_t n, bool read) +{ + char *s = _s; + ssize_t res; + ssize_t pos = 0; + + while (n > pos) { + if( read) + res = ::read(fd, s + pos, n - pos); + else + res = ::write(fd, s + pos, n - pos); + + switch (res) { + case -1: + kdDebug() << "atomicio(): errno=" << errno << endl; +#ifdef EWOULDBLOCK + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) +#else + if (errno == EINTR || errno == EAGAIN) +#endif + continue; + case 0: + return (res); + default: + pos += res; + } + } + return (pos); +} diff --git a/kioslave/sftp/atomicio.h b/kioslave/sftp/atomicio.h new file mode 100644 index 000000000..4468757d5 --- /dev/null +++ b/kioslave/sftp/atomicio.h @@ -0,0 +1,39 @@ +#ifndef atomicio_h +#define atomicio_h + +/* $OpenBSD: atomicio.h,v 1.3 2001/03/02 18:54:30 deraadt Exp $ */ + +/* + * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <unistd.h> + +/* + * Ensure all of data on socket comes through. f==read || f==write + */ +ssize_t atomicio(int fd, char *_s, size_t n, bool read = true); + +#endif diff --git a/kioslave/sftp/kio_sftp.cpp b/kioslave/sftp/kio_sftp.cpp new file mode 100644 index 000000000..e6aaaf532 --- /dev/null +++ b/kioslave/sftp/kio_sftp.cpp @@ -0,0 +1,2286 @@ +/*************************************************************************** + sftp.cpp - description + ------------------- + begin : Fri Jun 29 23:45:40 CDT 2001 + copyright : (C) 2001 by Lucas Fisher + email : ljfisher@purdue.edu + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +/* +DEBUGGING +We are pretty much left with kdDebug messages for debugging. We can't use a gdb +as described in the ioslave DEBUG.howto because kdeinit has to run in a terminal. +Ssh will detect this terminal and ask for a password there, but will just get garbage. +So we can't connect. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> + +#include <qcstring.h> +#include <qstring.h> +#include <qobject.h> +#include <qstrlist.h> +#include <qfile.h> +#include <qbuffer.h> + +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <ctype.h> +#include <time.h> +#include <netdb.h> +#include <string.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <kapplication.h> +#include <kuser.h> +#include <kdebug.h> +#include <kmessagebox.h> +#include <kinstance.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kurl.h> +#include <kio/ioslave_defaults.h> +#include <kmimetype.h> +#include <kmimemagic.h> +#include <klargefile.h> +#include <kremoteencoding.h> + +#include "sftp.h" +#include "kio_sftp.h" +#include "atomicio.h" +#include "sftpfileattr.h" +#include "ksshprocess.h" + + +using namespace KIO; +extern "C" +{ + int KDE_EXPORT kdemain( int argc, char **argv ) + { + KInstance instance( "kio_sftp" ); + + kdDebug(KIO_SFTP_DB) << "*** Starting kio_sftp " << endl; + + if (argc != 4) { + kdDebug(KIO_SFTP_DB) << "Usage: kio_sftp protocol domain-socket1 domain-socket2" << endl; + exit(-1); + } + + sftpProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(KIO_SFTP_DB) << "*** kio_sftp Done" << endl; + return 0; + } +} + + +/* + * This helper handles some special issues (blocking and interrupted + * system call) when writing to a file handle. + * + * @return 0 on success or an error code on failure (ERR_COULD_NOT_WRITE, + * ERR_DISK_FULL, ERR_CONNECTION_BROKEN). + */ +static int writeToFile (int fd, const char *buf, size_t len) +{ + while (len > 0) + { + ssize_t written = ::write(fd, buf, len); + if (written >= 0) + { + buf += written; + len -= written; + continue; + } + + switch(errno) + { + case EINTR: + continue; + case EPIPE: + return ERR_CONNECTION_BROKEN; + case ENOSPC: + return ERR_DISK_FULL; + default: + return ERR_COULD_NOT_WRITE; + } + } + return 0; +} + +sftpProtocol::sftpProtocol(const QCString &pool_socket, const QCString &app_socket) + : SlaveBase("kio_sftp", pool_socket, app_socket), + mConnected(false), mPort(-1), mMsgId(0) { + kdDebug(KIO_SFTP_DB) << "sftpProtocol(): pid = " << getpid() << endl; +} + + +sftpProtocol::~sftpProtocol() { + kdDebug(KIO_SFTP_DB) << "~sftpProtocol(): pid = " << getpid() << endl; + closeConnection(); +} + +/** + * Type is a sftp packet type found in .sftp.h'. + * Example: SSH2_FXP_READLINK, SSH2_FXP_RENAME, etc. + * + * Returns true if the type is supported by the sftp protocol + * version negotiated by the client and server (sftpVersion). + */ +bool sftpProtocol::isSupportedOperation(int type) { + switch (type) { + case SSH2_FXP_VERSION: + case SSH2_FXP_STATUS: + case SSH2_FXP_HANDLE: + case SSH2_FXP_DATA: + case SSH2_FXP_NAME: + case SSH2_FXP_ATTRS: + case SSH2_FXP_INIT: + case SSH2_FXP_OPEN: + case SSH2_FXP_CLOSE: + case SSH2_FXP_READ: + case SSH2_FXP_WRITE: + case SSH2_FXP_LSTAT: + case SSH2_FXP_FSTAT: + case SSH2_FXP_SETSTAT: + case SSH2_FXP_FSETSTAT: + case SSH2_FXP_OPENDIR: + case SSH2_FXP_READDIR: + case SSH2_FXP_REMOVE: + case SSH2_FXP_MKDIR: + case SSH2_FXP_RMDIR: + case SSH2_FXP_REALPATH: + case SSH2_FXP_STAT: + return true; + case SSH2_FXP_RENAME: + return sftpVersion >= 2 ? true : false; + case SSH2_FXP_EXTENDED: + case SSH2_FXP_EXTENDED_REPLY: + case SSH2_FXP_READLINK: + case SSH2_FXP_SYMLINK: + return sftpVersion >= 3 ? true : false; + default: + kdDebug(KIO_SFTP_DB) << "isSupportedOperation(type:" + << type << "): unrecognized operation type" << endl; + break; + } + + return false; +} + +void sftpProtocol::copy(const KURL &src, const KURL &dest, int permissions, bool overwrite) +{ + kdDebug(KIO_SFTP_DB) << "copy(): " << src << " -> " << dest << endl; + + bool srcLocal = src.isLocalFile(); + bool destLocal = dest.isLocalFile(); + + if ( srcLocal && !destLocal ) // Copy file -> sftp + sftpCopyPut(src, dest, permissions, overwrite); + else if ( destLocal && !srcLocal ) // Copy sftp -> file + sftpCopyGet(dest, src, permissions, overwrite); + else + error(ERR_UNSUPPORTED_ACTION, QString::null); +} + +void sftpProtocol::sftpCopyGet(const KURL& dest, const KURL& src, int mode, bool overwrite) +{ + kdDebug(KIO_SFTP_DB) << "sftpCopyGet(): " << src << " -> " << dest << endl; + + // Attempt to establish a connection... + openConnection(); + if( !mConnected ) + return; + + KDE_struct_stat buff_orig; + QCString dest_orig ( QFile::encodeName(dest.path()) ); + bool origExists = (KDE_lstat( dest_orig.data(), &buff_orig ) != -1); + + if (origExists) + { + if (S_ISDIR(buff_orig.st_mode)) + { + error(ERR_IS_DIRECTORY, dest.prettyURL()); + return; + } + + if (!overwrite) + { + error(ERR_FILE_ALREADY_EXIST, dest.prettyURL()); + return; + } + } + + KIO::filesize_t offset = 0; + QCString dest_part ( dest_orig + ".part" ); + + int fd = -1; + bool partExists = false; + bool markPartial = config()->readBoolEntry("MarkPartial", true); + + if (markPartial) + { + KDE_struct_stat buff_part; + partExists = (KDE_stat( dest_part.data(), &buff_part ) != -1); + + if (partExists && buff_part.st_size > 0 && S_ISREG(buff_part.st_mode)) + { + if (canResume( buff_part.st_size )) + { + offset = buff_part.st_size; + kdDebug(KIO_SFTP_DB) << "sftpCopyGet: Resuming @ " << offset << endl; + } + } + + if (offset > 0) + { + fd = KDE_open(dest_part.data(), O_RDWR); + offset = KDE_lseek(fd, 0, SEEK_END); + if (offset == 0) + { + error(ERR_CANNOT_RESUME, dest.prettyURL()); + return; + } + } + else + { + // Set up permissions properly, based on what is done in file io-slave + int openFlags = (O_CREAT | O_TRUNC | O_WRONLY); + int initialMode = (mode == -1) ? 0666 : (mode | S_IWUSR); + fd = KDE_open(dest_part.data(), openFlags, initialMode); + } + } + else + { + // Set up permissions properly, based on what is done in file io-slave + int openFlags = (O_CREAT | O_TRUNC | O_WRONLY); + int initialMode = (mode == -1) ? 0666 : (mode | S_IWUSR); + fd = KDE_open(dest_orig.data(), openFlags, initialMode); + } + + if(fd == -1) + { + kdDebug(KIO_SFTP_DB) << "sftpCopyGet: Unable to open (" << fd << ") for writting." << endl; + if (errno == EACCES) + error (ERR_WRITE_ACCESS_DENIED, dest.prettyURL()); + else + error (ERR_CANNOT_OPEN_FOR_WRITING, dest.prettyURL()); + return; + } + + Status info = sftpGet(src, offset, fd); + if ( info.code != 0 ) + { + // Should we keep the partially downloaded file ?? + KIO::filesize_t size = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + if (info.size < size) + ::remove(dest_part.data()); + + error(info.code, info.text); + return; + } + + if (::close(fd) != 0) + { + error(ERR_COULD_NOT_WRITE, dest.prettyURL()); + return; + } + + // + if (markPartial) + { + if (::rename(dest_part.data(), dest_orig.data()) != 0) + { + error (ERR_CANNOT_RENAME_PARTIAL, dest_part); + return; + } + } + + data(QByteArray()); + kdDebug(KIO_SFTP_DB) << "sftpCopyGet(): emit finished()" << endl; + finished(); +} + +sftpProtocol::Status sftpProtocol::sftpGet( const KURL& src, KIO::filesize_t offset, int fd ) +{ + int code; + sftpFileAttr attr(remoteEncoding()); + + Status res; + res.code = 0; + res.size = 0; + + kdDebug(KIO_SFTP_DB) << "sftpGet(): " << src << endl; + + // stat the file first to get its size + if( (code = sftpStat(src, attr)) != SSH2_FX_OK ) { + return doProcessStatus(code, src.prettyURL()); + } + + // We cannot get file if it is a directory + if( attr.fileType() == S_IFDIR ) { + res.text = src.prettyURL(); + res.code = ERR_IS_DIRECTORY; + return res; + } + + KIO::filesize_t fileSize = attr.fileSize(); + Q_UINT32 pflags = SSH2_FXF_READ; + attr.clear(); + + QByteArray handle; + if( (code = sftpOpen(src, pflags, attr, handle)) != SSH2_FX_OK ) { + res.text = src.prettyURL(); + res.code = ERR_CANNOT_OPEN_FOR_READING; + return res; + } + + // needed for determining mimetype + // note: have to emit mimetype before emitting totalsize. + QByteArray buff; + QByteArray mimeBuffer; + + unsigned int oldSize; + bool foundMimetype = false; + + // How big should each data packet be? Definitely not bigger than 64kb or + // you will overflow the 2 byte size variable in a sftp packet. + Q_UINT32 len = 60*1024; + code = SSH2_FX_OK; + + kdDebug(KIO_SFTP_DB) << "sftpGet(): offset = " << offset << endl; + while( code == SSH2_FX_OK ) { + if( (code = sftpRead(handle, offset, len, buff)) == SSH2_FX_OK ) { + offset += buff.size(); + + // save data for mimetype. Pretty much follows what is in the ftp ioslave + if( !foundMimetype ) { + oldSize = mimeBuffer.size(); + mimeBuffer.resize(oldSize + buff.size()); + memcpy(mimeBuffer.data()+oldSize, buff.data(), buff.size()); + + if( mimeBuffer.size() > 1024 || offset == fileSize ) { + // determine mimetype + KMimeMagicResult* result = + KMimeMagic::self()->findBufferFileType(mimeBuffer, src.fileName()); + kdDebug(KIO_SFTP_DB) << "sftpGet(): mimetype is " << + result->mimeType() << endl; + mimeType(result->mimeType()); + + // Always send the total size after emitting mime-type... + totalSize(fileSize); + + if (fd == -1) + data(mimeBuffer); + else + { + if ( (res.code=writeToFile(fd, mimeBuffer.data(), mimeBuffer.size())) != 0 ) + return res; + } + + processedSize(mimeBuffer.size()); + mimeBuffer.resize(0); + foundMimetype = true; + } + } + else { + if (fd == -1) + data(buff); + else + { + if ( (res.code= writeToFile(fd, buff.data(), buff.size())) != 0 ) + return res; + } + processedSize(offset); + } + } + + /* + Check if slave was killed. According to slavebase.h we need to leave + the slave methods as soon as possible if the slave is killed. This + allows the slave to be cleaned up properly. + */ + if( wasKilled() ) { + res.text = i18n("An internal error occurred. Please retry the request again."); + res.code = ERR_UNKNOWN; + return res; + } + } + + if( code != SSH2_FX_EOF ) { + res.text = src.prettyURL(); + res.code = ERR_COULD_NOT_READ; // return here or still send empty array to indicate end of read? + } + + res.size = offset; + sftpClose(handle); + processedSize (offset); + return res; +} + +void sftpProtocol::get(const KURL& url) { + kdDebug(KIO_SFTP_DB) << "get(): " << url << endl ; + + openConnection(); + if( !mConnected ) + return; + + // Get resume offset + Q_UINT64 offset = config()->readUnsignedLongNumEntry("resume"); + if( offset > 0 ) { + canResume(); + kdDebug(KIO_SFTP_DB) << "get(): canResume(), offset = " << offset << endl; + } + + Status info = sftpGet(url, offset); + + if (info.code != 0) + { + error(info.code, info.text); + return; + } + + data(QByteArray()); + kdDebug(KIO_SFTP_DB) << "get(): emit finished()" << endl; + finished(); +} + + +void sftpProtocol::setHost (const QString& h, int port, const QString& user, const QString& pass) +{ + kdDebug(KIO_SFTP_DB) << "setHost(): " << user << "@" << h << ":" << port << endl; + + if( mHost != h || mPort != port || user != mUsername || mPassword != pass ) + closeConnection(); + + mHost = h; + + if( port > 0 ) + mPort = port; + else { + struct servent *pse; + if( (pse = getservbyname("ssh", "tcp") ) == NULL ) + mPort = 22; + else + mPort = ntohs(pse->s_port); + } + + mUsername = user; + mPassword = pass; + + if (user.isEmpty()) + { + KUser u; + mUsername = u.loginName(); + } +} + + +void sftpProtocol::openConnection() { + + if(mConnected) + return; + + kdDebug(KIO_SFTP_DB) << "openConnection(): " << mUsername << "@" + << mHost << ":" << mPort << endl; + + infoMessage( i18n("Opening SFTP connection to host <b>%1:%2</b>").arg(mHost).arg(mPort)); + + if( mHost.isEmpty() ) { + kdDebug(KIO_SFTP_DB) << "openConnection(): Need hostname..." << endl; + error(ERR_UNKNOWN_HOST, i18n("No hostname specified")); + return; + } + + //////////////////////////////////////////////////////////////////////////// + // Setup AuthInfo for use with password caching and the + // password dialog box. + AuthInfo info; + info.url.setProtocol("sftp"); + info.url.setHost(mHost); + info.url.setPort(mPort); + info.url.setUser(mUsername); + info.caption = i18n("SFTP Login"); + info.comment = "sftp://" + mHost + ":" + QString::number(mPort); + info.commentLabel = i18n("site:"); + info.username = mUsername; + info.keepPassword = true; + + /////////////////////////////////////////////////////////////////////////// + // Check for cached authentication info if a username AND password were + // not specified in setHost(). + if( mUsername.isEmpty() && mPassword.isEmpty() ) { + kdDebug(KIO_SFTP_DB) << "openConnection(): checking cache " + << "info.username = " << info.username + << ", info.url = " << info.url.prettyURL() << endl; + + if( checkCachedAuthentication(info) ) { + mUsername = info.username; + mPassword = info.password; + } + } + + /////////////////////////////////////////////////////////////////////////// + // Now setup our ssh options. If we found a cached username + // and password we set the SSH_PASSWORD and SSH_USERNAME + // options right away. Otherwise we wait. The other options are + // necessary for running sftp over ssh. + KSshProcess::SshOpt opt; // a ssh option, this can be reused + KSshProcess::SshOptList opts; // list of SshOpts + KSshProcess::SshOptListIterator passwdIt; // points to the opt in opts that specifies the password + KSshProcess::SshOptListIterator usernameIt; + +// opt.opt = KSshProcess::SSH_VERBOSE; +// opts.append(opt); +// opts.append(opt); + + if( mPort != -1 ) { + opt.opt = KSshProcess::SSH_PORT; + opt.num = mPort; + opts.append(opt); + } + + opt.opt = KSshProcess::SSH_SUBSYSTEM; + opt.str = "sftp"; + opts.append(opt); + + opt.opt = KSshProcess::SSH_FORWARDX11; + opt.boolean = false; + opts.append(opt); + + opt.opt = KSshProcess::SSH_FORWARDAGENT; + opt.boolean = false; + opts.append(opt); + + opt.opt = KSshProcess::SSH_PROTOCOL; + opt.num = 2; + opts.append(opt); + + opt.opt = KSshProcess::SSH_HOST; + opt.str = mHost; + opts.append(opt); + + opt.opt = KSshProcess::SSH_ESCAPE_CHAR; + opt.num = -1; // don't use any escape character + opts.append(opt); + + // set the username and password if we have them + if( !mUsername.isEmpty() ) { + opt.opt = KSshProcess::SSH_USERNAME; + opt.str = mUsername; + usernameIt = opts.append(opt); + } + + if( !mPassword.isEmpty() ) { + opt.opt = KSshProcess::SSH_PASSWD; + opt.str = mPassword; + passwdIt = opts.append(opt); + } + + ssh.setOptions(opts); + ssh.printArgs(); + + /////////////////////////////////////////////////////////////////////////// + // Start the ssh connection process. + // + + int err; // error code from KSshProcess + QString msg; // msg for dialog box + QString caption; // dialog box caption + bool firstTime = true; + bool dlgResult; + + while( !(mConnected = ssh.connect()) ) { + err = ssh.error(); + kdDebug(KIO_SFTP_DB) << "openConnection(): " + "Got " << err << " from KSshProcess::connect()" << endl; + + switch(err) { + case KSshProcess::ERR_NEED_PASSWD: + case KSshProcess::ERR_NEED_PASSPHRASE: + // At this point we know that either we didn't set + // an username or password in the ssh options list, + // or what we did pass did not work. Therefore we + // must prompt the user. + if( err == KSshProcess::ERR_NEED_PASSPHRASE ) + info.prompt = i18n("Please enter your username and key passphrase."); + else + info.prompt = i18n("Please enter your username and password."); + + kdDebug(KIO_SFTP_DB) << "openConnection(): info.username = " << info.username + << ", info.url = " << info.url.prettyURL() << endl; + + if( firstTime ) + dlgResult = openPassDlg(info); + else + dlgResult = openPassDlg(info, i18n("Incorrect username or password")); + + if( dlgResult ) { + if( info.username.isEmpty() || info.password.isEmpty() ) { + error(ERR_COULD_NOT_AUTHENTICATE, + i18n("Please enter a username and password")); + continue; + } + } + else { + // user canceled or dialog failed to open + error(ERR_USER_CANCELED, QString::null); + kdDebug(KIO_SFTP_DB) << "openConnection(): user canceled, dlgResult = " << dlgResult << endl; + closeConnection(); + return; + } + + firstTime = false; + + // Check if the username has changed. SSH only accepts + // the username at startup. If the username has changed + // we must disconnect ssh, change the SSH_USERNAME + // option, and reset the option list. We will also set + // the password option so the user is not prompted for + // it again. + if( mUsername != info.username ) { + kdDebug(KIO_SFTP_DB) << "openConnection(): Username changed from " + << mUsername << " to " << info.username << endl; + + ssh.disconnect(); + + // if we haven't yet added the username + // or password option to the ssh options list then + // the iterators will be equal to the empty iterator. + // Create the opts now and add them to the opt list. + if( usernameIt == KSshProcess::SshOptListIterator() ) { + kdDebug(KIO_SFTP_DB) << "openConnection(): " + "Adding username to options list" << endl; + opt.opt = KSshProcess::SSH_USERNAME; + usernameIt = opts.append(opt); + } + + if( passwdIt == KSshProcess::SshOptListIterator() ) { + kdDebug(KIO_SFTP_DB) << "openConnection(): " + "Adding password to options list" << endl; + opt.opt = KSshProcess::SSH_PASSWD; + passwdIt = opts.append(opt); + } + + (*usernameIt).str = info.username; + (*passwdIt).str = info.password; + ssh.setOptions(opts); + ssh.printArgs(); + } + else { // just set the password + ssh.setPassword(info.password); + } + + mUsername = info.username; + mPassword = info.password; + + break; + + case KSshProcess::ERR_NEW_HOST_KEY: + caption = i18n("Warning: Cannot verify host's identity."); + msg = ssh.errorMsg(); + if( KMessageBox::Yes != messageBox(WarningYesNo, msg, caption) ) { + closeConnection(); + error(ERR_USER_CANCELED, QString::null); + return; + } + ssh.acceptHostKey(true); + break; + + case KSshProcess::ERR_DIFF_HOST_KEY: + caption = i18n("Warning: Host's identity changed."); + msg = ssh.errorMsg(); + if( KMessageBox::Yes != messageBox(WarningYesNo, msg, caption) ) { + closeConnection(); + error(ERR_USER_CANCELED, QString::null); + return; + } + ssh.acceptHostKey(true); + break; + + case KSshProcess::ERR_AUTH_FAILED: + infoMessage(i18n("Authentication failed.")); + error(ERR_COULD_NOT_LOGIN, i18n("Authentication failed.")); + return; + + case KSshProcess::ERR_AUTH_FAILED_NEW_KEY: + msg = ssh.errorMsg(); + error(ERR_COULD_NOT_LOGIN, msg); + return; + + case KSshProcess::ERR_AUTH_FAILED_DIFF_KEY: + msg = ssh.errorMsg(); + error(ERR_COULD_NOT_LOGIN, msg); + return; + + case KSshProcess::ERR_CLOSED_BY_REMOTE_HOST: + infoMessage(i18n("Connection failed.")); + caption = i18n("Connection closed by remote host."); + msg = ssh.errorMsg(); + messageBox(Information, msg, caption); + closeConnection(); + error(ERR_COULD_NOT_LOGIN, msg); + return; + + case KSshProcess::ERR_INTERACT: + case KSshProcess::ERR_INTERNAL: + case KSshProcess::ERR_UNKNOWN: + case KSshProcess::ERR_INVALID_STATE: + case KSshProcess::ERR_CANNOT_LAUNCH: + case KSshProcess::ERR_HOST_KEY_REJECTED: + default: + infoMessage(i18n("Connection failed.")); + caption = i18n("Unexpected SFTP error: %1").arg(err); + msg = ssh.errorMsg(); + messageBox(Information, msg, caption); + closeConnection(); + error(ERR_UNKNOWN, msg); + return; + } + } + + // catch all in case we did something wrong above + if( !mConnected ) { + error(ERR_INTERNAL, QString::null); + return; + } + + // Now send init packet. + kdDebug(KIO_SFTP_DB) << "openConnection(): Sending SSH2_FXP_INIT packet." << endl; + QByteArray p; + QDataStream packet(p, IO_WriteOnly); + packet << (Q_UINT32)5; // packet length + packet << (Q_UINT8) SSH2_FXP_INIT; // packet type + packet << (Q_UINT32)SSH2_FILEXFER_VERSION; // client version + + putPacket(p); + getPacket(p); + + QDataStream s(p, IO_ReadOnly); + Q_UINT32 version; + Q_UINT8 type; + s >> type; + kdDebug(KIO_SFTP_DB) << "openConnection(): Got type " << type << endl; + + if( type == SSH2_FXP_VERSION ) { + s >> version; + kdDebug(KIO_SFTP_DB) << "openConnection(): Got server version " << version << endl; + + // XXX Get extensions here + sftpVersion = version; + + /* Server should return lowest common version supported by + * client and server, but double check just in case. + */ + if( sftpVersion > SSH2_FILEXFER_VERSION ) { + error(ERR_UNSUPPORTED_PROTOCOL, + i18n("SFTP version %1").arg(version)); + closeConnection(); + return; + } + } + else { + error(ERR_UNKNOWN, i18n("Protocol error.")); + closeConnection(); + return; + } + + // Login succeeded! + infoMessage(i18n("Successfully connected to %1").arg(mHost)); + info.url.setProtocol("sftp"); + info.url.setHost(mHost); + info.url.setPort(mPort); + info.url.setUser(mUsername); + info.username = mUsername; + info.password = mPassword; + kdDebug(KIO_SFTP_DB) << "sftpProtocol(): caching info.username = " << info.username << + ", info.url = " << info.url.prettyURL() << endl; + cacheAuthentication(info); + mConnected = true; + connected(); + + mPassword.fill('x'); + info.password.fill('x'); + + return; +} + +void sftpProtocol::closeConnection() { + kdDebug(KIO_SFTP_DB) << "closeConnection()" << endl; + ssh.disconnect(); + mConnected = false; +} + +void sftpProtocol::sftpCopyPut(const KURL& src, const KURL& dest, int permissions, bool overwrite) { + + KDE_struct_stat buff; + QCString file (QFile::encodeName(src.path())); + + if (KDE_lstat(file.data(), &buff) == -1) { + error (ERR_DOES_NOT_EXIST, src.prettyURL()); + return; + } + + if (S_ISDIR (buff.st_mode)) { + error (ERR_IS_DIRECTORY, src.prettyURL()); + return; + } + + int fd = KDE_open (file.data(), O_RDONLY); + if (fd == -1) { + error (ERR_CANNOT_OPEN_FOR_READING, src.prettyURL()); + return; + } + + totalSize (buff.st_size); + + sftpPut (dest, permissions, false, overwrite, fd); + + // Close the file descriptor... + ::close( fd ); +} + +void sftpProtocol::sftpPut( const KURL& dest, int permissions, bool resume, bool overwrite, int fd ) { + + openConnection(); + if( !mConnected ) + return; + + kdDebug(KIO_SFTP_DB) << "sftpPut(): " << dest + << ", resume=" << resume + << ", overwrite=" << overwrite << endl; + + KURL origUrl( dest ); + sftpFileAttr origAttr(remoteEncoding()); + bool origExists = false; + + // Stat original (without part ext) to see if it already exists + int code = sftpStat(origUrl, origAttr); + + if( code == SSH2_FX_OK ) { + kdDebug(KIO_SFTP_DB) << "sftpPut(): <file> already exists" << endl; + + // Delete remote file if its size is zero + if( origAttr.fileSize() == 0 ) { + if( sftpRemove(origUrl, true) != SSH2_FX_OK ) { + error(ERR_CANNOT_DELETE_ORIGINAL, origUrl.prettyURL()); + return; + } + } + else { + origExists = true; + } + } + else if( code != SSH2_FX_NO_SUCH_FILE ) { + processStatus(code, origUrl.prettyURL()); + return; + } + + // Do not waste time/resources with more remote stat calls if the file exists + // and we weren't instructed to overwrite it... + if( origExists && !overwrite ) { + error(ERR_FILE_ALREADY_EXIST, origUrl.prettyURL()); + return; + } + + // Stat file with part ext to see if it already exists... + KURL partUrl( origUrl ); + partUrl.setFileName( partUrl.fileName() + ".part" ); + + Q_UINT64 offset = 0; + bool partExists = false; + bool markPartial = config()->readBoolEntry("MarkPartial", true); + + if( markPartial ) { + + sftpFileAttr partAttr(remoteEncoding()); + code = sftpStat(partUrl, partAttr); + + if( code == SSH2_FX_OK ) { + kdDebug(KIO_SFTP_DB) << "sftpPut(): .part file already exists" << endl; + partExists = true; + offset = partAttr.fileSize(); + + // If for some reason, both the original and partial files exist, + // skip resumption just like we would if the size of the partial + // file is zero... + if( origExists || offset == 0 ) + { + if( sftpRemove(partUrl, true) != SSH2_FX_OK ) { + error(ERR_CANNOT_DELETE_PARTIAL, partUrl.prettyURL()); + return; + } + + if( sftpRename(origUrl, partUrl) != SSH2_FX_OK ) { + error(ERR_CANNOT_RENAME_ORIGINAL, origUrl.prettyURL()); + return; + } + + offset = 0; + } + else if( !overwrite && !resume ) { + if (fd != -1) + resume = (KDE_lseek(fd, offset, SEEK_SET) != -1); + else + resume = canResume( offset ); + + kdDebug(KIO_SFTP_DB) << "sftpPut(): can resume = " << resume + << ", offset = " << offset; + + if( !resume ) { + error(ERR_FILE_ALREADY_EXIST, partUrl.prettyURL()); + return; + } + } + else { + offset = 0; + } + } + else if( code == SSH2_FX_NO_SUCH_FILE ) { + if( origExists && sftpRename(origUrl, partUrl) != SSH2_FX_OK ) { + error(ERR_CANNOT_RENAME_ORIGINAL, origUrl.prettyURL()); + return; + } + } + else { + processStatus(code, partUrl.prettyURL()); + return; + } + } + + // Determine the url we will actually write to... + KURL writeUrl (markPartial ? partUrl:origUrl); + + Q_UINT32 pflags = 0; + if( overwrite && !resume ) + pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_TRUNC; + else if( !overwrite && !resume ) + pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_EXCL; + else if( overwrite && resume ) + pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT; + else if( !overwrite && resume ) + pflags = SSH2_FXF_WRITE | SSH2_FXF_CREAT | SSH2_FXF_APPEND; + + sftpFileAttr attr(remoteEncoding()); + QByteArray handle; + + // Set the permissions of the file we write to if it didn't already exist + // and the permission info is supplied, i.e it is not -1 + if( !partExists && !origExists && permissions != -1) + attr.setPermissions(permissions); + + code = sftpOpen( writeUrl, pflags, attr, handle ); + if( code != SSH2_FX_OK ) { + + // Rename the file back to its original name if a + // put fails due to permissions problems... + if( markPartial && overwrite ) { + (void) sftpRename(partUrl, origUrl); + writeUrl = origUrl; + } + + if( code == SSH2_FX_FAILURE ) { // assume failure means file exists + error(ERR_FILE_ALREADY_EXIST, writeUrl.prettyURL()); + return; + } + else { + processStatus(code, writeUrl.prettyURL()); + return; + } + } + + long nbytes; + QByteArray buff; + + do { + + if( fd != -1 ) { + buff.resize( 16*1024 ); + if ( (nbytes = ::read(fd, buff.data(), buff.size())) > -1 ) + buff.resize( nbytes ); + } + else { + dataReq(); + nbytes = readData( buff ); + } + + if( nbytes >= 0 ) { + if( (code = sftpWrite(handle, offset, buff)) != SSH2_FX_OK ) { + error(ERR_COULD_NOT_WRITE, dest.prettyURL()); + return; + } + + offset += nbytes; + processedSize(offset); + + /* Check if slave was killed. According to slavebase.h we + * need to leave the slave methods as soon as possible if + * the slave is killed. This allows the slave to be cleaned + * up properly. + */ + if( wasKilled() ) { + sftpClose(handle); + closeConnection(); + error(ERR_UNKNOWN, i18n("An internal error occurred. Please try again.")); + return; + } + } + + } while( nbytes > 0 ); + + if( nbytes < 0 ) { + sftpClose(handle); + + if( markPartial ) { + // Remove remote file if it smaller than our keep size + uint minKeepSize = config()->readNumEntry("MinimumKeepSize", DEFAULT_MINIMUM_KEEP_SIZE); + + if( sftpStat(writeUrl, attr) == SSH2_FX_OK ) { + if( attr.fileSize() < minKeepSize ) { + sftpRemove(writeUrl, true); + } + } + } + + error( ERR_UNKNOWN, i18n("Unknown error was encountered while copying the file " + "to '%1'. Please try again.").arg(dest.host()) ); + return; + } + + if( (code = sftpClose(handle)) != SSH2_FX_OK ) { + error(ERR_COULD_NOT_WRITE, writeUrl.prettyURL()); + return; + } + + // If wrote to a partial file, then remove the part ext + if( markPartial ) { + if( sftpRename(partUrl, origUrl) != SSH2_FX_OK ) { + error(ERR_CANNOT_RENAME_PARTIAL, origUrl.prettyURL()); + return; + } + } + + finished(); +} + +void sftpProtocol::put ( const KURL& url, int permissions, bool overwrite, bool resume ){ + kdDebug(KIO_SFTP_DB) << "put(): " << url << ", overwrite = " << overwrite + << ", resume = " << resume << endl; + + sftpPut( url, permissions, resume, overwrite ); +} + +void sftpProtocol::stat ( const KURL& url ){ + kdDebug(KIO_SFTP_DB) << "stat(): " << url << endl; + + openConnection(); + if( !mConnected ) + return; + + // If the stat URL has no path, do not attempt to determine the real + // path and do a redirect. KRun will simply ignore such requests. + // Instead, simply return the mime-type as a directory... + if( !url.hasPath() ) { + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = KIO::UDS_NAME; + atom.m_str = QString::null; + entry.append( atom ); + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append( atom ); + + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + entry.append( atom ); + + atom.m_uds = KIO::UDS_USER; + atom.m_str = mUsername; + entry.append( atom ); + atom.m_uds = KIO::UDS_GROUP; + entry.append( atom ); + + // no size + statEntry( entry ); + finished(); + return; + } + + int code; + sftpFileAttr attr(remoteEncoding()); + if( (code = sftpStat(url, attr)) != SSH2_FX_OK ) { + processStatus(code, url.prettyURL()); + return; + } + else { + //kdDebug() << "We sent and received stat packet ok" << endl; + attr.setFilename(url.fileName()); + statEntry(attr.entry()); + } + + finished(); + + kdDebug(KIO_SFTP_DB) << "stat: END" << endl; + return; +} + + +void sftpProtocol::mimetype ( const KURL& url ){ + kdDebug(KIO_SFTP_DB) << "mimetype(): " << url << endl; + + openConnection(); + if( !mConnected ) + return; + + Q_UINT32 pflags = SSH2_FXF_READ; + QByteArray handle, mydata; + sftpFileAttr attr(remoteEncoding()); + int code; + if( (code = sftpOpen(url, pflags, attr, handle)) != SSH2_FX_OK ) { + error(ERR_CANNOT_OPEN_FOR_READING, url.prettyURL()); + return; + } + + Q_UINT32 len = 1024; // Get first 1k for determining mimetype + Q_UINT64 offset = 0; + code = SSH2_FX_OK; + while( offset < len && code == SSH2_FX_OK ) { + if( (code = sftpRead(handle, offset, len, mydata)) == SSH2_FX_OK ) { + data(mydata); + offset += mydata.size(); + processedSize(offset); + + kdDebug(KIO_SFTP_DB) << "mimetype(): offset = " << offset << endl; + } + } + + + data(QByteArray()); + processedSize(offset); + sftpClose(handle); + finished(); + kdDebug(KIO_SFTP_DB) << "mimetype(): END" << endl; +} + + +void sftpProtocol::listDir(const KURL& url) { + kdDebug(KIO_SFTP_DB) << "listDir(): " << url << endl; + + openConnection(); + if( !mConnected ) + return; + + if( !url.hasPath() ) { + KURL newUrl ( url ); + if( sftpRealPath(url, newUrl) == SSH2_FX_OK ) { + kdDebug(KIO_SFTP_DB) << "listDir: Redirecting to " << newUrl << endl; + redirection(newUrl); + finished(); + return; + } + } + + int code; + QByteArray handle; + + if( (code = sftpOpenDirectory(url, handle)) != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "listDir(): open directory failed" << endl; + processStatus(code, url.prettyURL()); + return; + } + + + code = SSH2_FX_OK; + while( code == SSH2_FX_OK ) { + code = sftpReadDir(handle, url); + if( code != SSH2_FX_OK && code != SSH2_FX_EOF ) + processStatus(code, url.prettyURL()); + kdDebug(KIO_SFTP_DB) << "listDir(): return code = " << code << endl; + } + + if( (code = sftpClose(handle)) != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "listdir(): closing of directory failed" << endl; + processStatus(code, url.prettyURL()); + return; + } + + finished(); + kdDebug(KIO_SFTP_DB) << "listDir(): END" << endl; +} + +/** Make a directory. + OpenSSH does not follow the internet draft for sftp in this case. + The format of the mkdir request expected by OpenSSH sftp server is: + uint32 id + string path + ATTR attr + */ +void sftpProtocol::mkdir(const KURL&url, int permissions){ + + kdDebug(KIO_SFTP_DB) << "mkdir() creating dir: " << url.path() << endl; + + openConnection(); + if( !mConnected ) + return; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + sftpFileAttr attr(remoteEncoding()); + + if (permissions != -1) + attr.setPermissions(permissions); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << Q_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); + s << (Q_UINT8)SSH2_FXP_MKDIR; + s << id; + s.writeBytes(path.data(), len); + s << attr; + + kdDebug(KIO_SFTP_DB) << "mkdir(): packet size is " << p.size() << endl; + + putPacket(p); + getPacket(p); + + Q_UINT8 type; + QDataStream r(p, IO_ReadOnly); + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "mkdir: sftp packet id mismatch" << endl; + error(ERR_COULD_NOT_MKDIR, path); + finished(); + return; + } + + if( type != SSH2_FXP_STATUS ) { + kdError(KIO_SFTP_DB) << "mkdir(): unexpected packet type of " << type << endl; + error(ERR_COULD_NOT_MKDIR, path); + finished(); + return; + } + + int code; + r >> code; + if( code != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "mkdir(): failed with code " << code << endl; + + // Check if mkdir failed because the directory already exists so that + // we can return the appropriate message... + sftpFileAttr dirAttr(remoteEncoding()); + if ( sftpStat(url, dirAttr) == SSH2_FX_OK ) + { + error( ERR_DIR_ALREADY_EXIST, url.prettyURL() ); + return; + } + + error(ERR_COULD_NOT_MKDIR, path); + } + + finished(); +} + +void sftpProtocol::rename(const KURL& src, const KURL& dest, bool overwrite){ + kdDebug(KIO_SFTP_DB) << "rename(" << src << " -> " << dest << ")" << endl; + + if (!isSupportedOperation(SSH2_FXP_RENAME)) { + error(ERR_UNSUPPORTED_ACTION, + i18n("The remote host does not support renaming files.")); + return; + } + + openConnection(); + if( !mConnected ) + return; + + // Always stat the destination before attempting to rename + // a file or a directory... + sftpFileAttr attr(remoteEncoding()); + int code = sftpStat(dest, attr); + + // If the destination directory, exists tell it to the job + // so it the proper action can be presented to the user... + if( code == SSH2_FX_OK ) + { + if (!overwrite) + { + if ( S_ISDIR(attr.permissions()) ) + error( KIO::ERR_DIR_ALREADY_EXIST, dest.url() ); + else + error( KIO::ERR_FILE_ALREADY_EXIST, dest.url() ); + return; + } + + // If overwrite is specified, then simply remove the existing file/dir first... + if( (code = sftpRemove( dest, !S_ISDIR(attr.permissions()) )) != SSH2_FX_OK ) + { + processStatus(code); + return; + } + } + + // Do the renaming... + if( (code = sftpRename(src, dest)) != SSH2_FX_OK ) { + processStatus(code); + return; + } + + finished(); + kdDebug(KIO_SFTP_DB) << "rename(): END" << endl; +} + +void sftpProtocol::symlink(const QString& target, const KURL& dest, bool overwrite){ + kdDebug(KIO_SFTP_DB) << "symlink()" << endl; + + if (!isSupportedOperation(SSH2_FXP_SYMLINK)) { + error(ERR_UNSUPPORTED_ACTION, + i18n("The remote host does not support creating symbolic links.")); + return; + } + + openConnection(); + if( !mConnected ) + return; + + int code; + bool failed = false; + if( (code = sftpSymLink(target, dest)) != SSH2_FX_OK ) { + if( overwrite ) { // try to delete the destination + sftpFileAttr attr(remoteEncoding()); + if( (code = sftpStat(dest, attr)) != SSH2_FX_OK ) { + failed = true; + } + else { + if( (code = sftpRemove(dest, !S_ISDIR(attr.permissions())) ) != SSH2_FX_OK ) { + failed = true; + } + else { + // XXX what if rename fails again? We have lost the file. + // Maybe rename dest to a temporary name first? If rename is + // successful, then delete? + if( (code = sftpSymLink(target, dest)) != SSH2_FX_OK ) + failed = true; + } + } + } + else if( code == SSH2_FX_FAILURE ) { + error(ERR_FILE_ALREADY_EXIST, dest.prettyURL()); + return; + } + else + failed = true; + } + + // What error code do we return? Code for the original symlink command + // or for the last command or for both? The second one is implemented here. + if( failed ) + processStatus(code); + + finished(); +} + +void sftpProtocol::chmod(const KURL& url, int permissions){ + QString perms; + perms.setNum(permissions, 8); + kdDebug(KIO_SFTP_DB) << "chmod(" << url << ", " << perms << ")" << endl; + + openConnection(); + if( !mConnected ) + return; + + sftpFileAttr attr(remoteEncoding()); + + if (permissions != -1) + attr.setPermissions(permissions); + + int code; + if( (code = sftpSetStat(url, attr)) != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "chmod(): sftpSetStat failed with error " << code << endl; + if( code == SSH2_FX_FAILURE ) + error(ERR_CANNOT_CHMOD, QString::null); + else + processStatus(code, url.prettyURL()); + } + finished(); +} + + +void sftpProtocol::del(const KURL &url, bool isfile){ + kdDebug(KIO_SFTP_DB) << "del(" << url << ", " << (isfile?"file":"dir") << ")" << endl; + + openConnection(); + if( !mConnected ) + return; + + int code; + if( (code = sftpRemove(url, isfile)) != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "del(): sftpRemove failed with error code " << code << endl; + processStatus(code, url.prettyURL()); + } + finished(); +} + +void sftpProtocol::slave_status() { + kdDebug(KIO_SFTP_DB) << "slave_status(): connected to " + << mHost << "? " << mConnected << endl; + + slaveStatus ((mConnected ? mHost : QString::null), mConnected); +} + +bool sftpProtocol::getPacket(QByteArray& msg) { + QByteArray buf(4096); + + // Get the message length... + ssize_t len = atomicio(ssh.stdioFd(), buf.data(), 4, true /*read*/); + + if( len == 0 || len == -1 ) { + kdDebug(KIO_SFTP_DB) << "getPacket(): read of packet length failed, ret = " + << len << ", error =" << strerror(errno) << endl; + closeConnection(); + error( ERR_CONNECTION_BROKEN, mHost); + msg.resize(0); + return false; + } + + uint msgLen; + QDataStream s(buf, IO_ReadOnly); + s >> msgLen; + + //kdDebug(KIO_SFTP_DB) << "getPacket(): Message size = " << msgLen << endl; + + msg.resize(0); + + QBuffer b( msg ); + b.open( IO_WriteOnly ); + + while( msgLen ) { + len = atomicio(ssh.stdioFd(), buf.data(), kMin(buf.size(), msgLen), true /*read*/); + + if( len == 0 || len == -1) { + QString errmsg; + if (len == 0) + errmsg = i18n("Connection closed"); + else + errmsg = i18n("Could not read SFTP packet"); + kdDebug(KIO_SFTP_DB) << "getPacket(): nothing to read, ret = " << + len << ", error =" << strerror(errno) << endl; + closeConnection(); + error(ERR_CONNECTION_BROKEN, errmsg); + b.close(); + return false; + } + + b.writeBlock(buf.data(), len); + + //kdDebug(KIO_SFTP_DB) << "getPacket(): Read Message size = " << len << endl; + //kdDebug(KIO_SFTP_DB) << "getPacket(): Copy Message size = " << msg.size() << endl; + + msgLen -= len; + } + + b.close(); + + return true; +} + +/** Send an sftp packet to stdin of the ssh process. */ +bool sftpProtocol::putPacket(QByteArray& p){ +// kdDebug(KIO_SFTP_DB) << "putPacket(): size == " << p.size() << endl; + int ret; + ret = atomicio(ssh.stdioFd(), p.data(), p.size(), false /*write*/); + if( ret <= 0 ) { + kdDebug(KIO_SFTP_DB) << "putPacket(): write failed, ret =" << ret << + ", error = " << strerror(errno) << endl; + return false; + } + + return true; +} + +/** Used to have the server canonicalize any given path name to an absolute path. +This is useful for converting path names containing ".." components or relative +pathnames without a leading slash into absolute paths. +Returns the canonicalized url. */ +int sftpProtocol::sftpRealPath(const KURL& url, KURL& newUrl){ + + kdDebug(KIO_SFTP_DB) << "sftpRealPath(" << url << ", newUrl)" << endl; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << Q_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (Q_UINT8)SSH2_FXP_REALPATH; + s << id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + Q_UINT8 type; + QDataStream r(p, IO_ReadOnly); + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpRealPath: sftp packet id mismatch" << endl; + return -1; + } + + if( type == SSH2_FXP_STATUS ) { + Q_UINT32 code; + r >> code; + return code; + } + + if( type != SSH2_FXP_NAME ) { + kdError(KIO_SFTP_DB) << "sftpRealPath(): unexpected packet type of " << type << endl; + return -1; + } + + Q_UINT32 count; + r >> count; + if( count != 1 ) { + kdError(KIO_SFTP_DB) << "sftpRealPath(): Bad number of file attributes for realpath command" << endl; + return -1; + } + + QCString newPath; + r >> newPath; + + newPath.truncate(newPath.size()); + if (newPath.isEmpty()) + newPath = "/"; + newUrl.setPath(newPath); + + return SSH2_FX_OK; +} + +sftpProtocol::Status sftpProtocol::doProcessStatus(Q_UINT8 code, const QString& message) +{ + Status res; + res.code = 0; + res.size = 0; + res.text = message; + + switch(code) + { + case SSH2_FX_OK: + case SSH2_FX_EOF: + break; + case SSH2_FX_NO_SUCH_FILE: + res.code = ERR_DOES_NOT_EXIST; + break; + case SSH2_FX_PERMISSION_DENIED: + res.code = ERR_ACCESS_DENIED; + break; + case SSH2_FX_FAILURE: + res.text = i18n("SFTP command failed for an unknown reason."); + res.code = ERR_UNKNOWN; + break; + case SSH2_FX_BAD_MESSAGE: + res.text = i18n("The SFTP server received a bad message."); + res.code = ERR_UNKNOWN; + break; + case SSH2_FX_OP_UNSUPPORTED: + res.text = i18n("You attempted an operation unsupported by the SFTP server."); + res.code = ERR_UNKNOWN; + break; + default: + res.text = i18n("Error code: %1").arg(code); + res.code = ERR_UNKNOWN; + } + + return res; +} + +/** Process SSH_FXP_STATUS packets. */ +void sftpProtocol::processStatus(Q_UINT8 code, const QString& message){ + Status st = doProcessStatus( code, message ); + if( st.code != 0 ) + error( st.code, st.text ); +} + +/** Opens a directory handle for url.path. Returns true if succeeds. */ +int sftpProtocol::sftpOpenDirectory(const KURL& url, QByteArray& handle){ + + kdDebug(KIO_SFTP_DB) << "sftpOpenDirectory(" << url << ", handle)" << endl; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (Q_UINT8)SSH2_FXP_OPENDIR; + s << (Q_UINT32)id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpOpenDirectory: sftp packet id mismatch: " << + "expected " << expectedId << ", got " << id << endl; + return -1; + } + + if( type == SSH2_FXP_STATUS ) { + Q_UINT32 errCode; + r >> errCode; + return errCode; + } + + if( type != SSH2_FXP_HANDLE ) { + kdError(KIO_SFTP_DB) << "sftpOpenDirectory: unexpected message type of " << type << endl; + return -1; + } + + r >> handle; + if( handle.size() > 256 ) { + kdError(KIO_SFTP_DB) << "sftpOpenDirectory: handle exceeds max length" << endl; + return -1; + } + + kdDebug(KIO_SFTP_DB) << "sftpOpenDirectory: handle (" << handle.size() << "): [" << handle << "]" << endl; + return SSH2_FX_OK; +} + +/** Closes a directory or file handle. */ +int sftpProtocol::sftpClose(const QByteArray& handle){ + + kdDebug(KIO_SFTP_DB) << "sftpClose()" << endl; + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); + s << (Q_UINT8)SSH2_FXP_CLOSE; + s << (Q_UINT32)id; + s << handle; + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpClose: sftp packet id mismatch" << endl; + return -1; + } + + if( type != SSH2_FXP_STATUS ) { + kdError(KIO_SFTP_DB) << "sftpClose: unexpected message type of " << type << endl; + return -1; + } + + Q_UINT32 code; + r >> code; + if( code != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "sftpClose: close failed with err code " << code << endl; + } + + return code; +} + +/** Set a files attributes. */ +int sftpProtocol::sftpSetStat(const KURL& url, const sftpFileAttr& attr){ + + kdDebug(KIO_SFTP_DB) << "sftpSetStat(" << url << ", attr)" << endl; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); + s << (Q_UINT8)SSH2_FXP_SETSTAT; + s << (Q_UINT32)id; + s.writeBytes(path.data(), len); + s << attr; + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpSetStat(): sftp packet id mismatch" << endl; + return -1; + // XXX How do we do a fatal error? + } + + if( type != SSH2_FXP_STATUS ) { + kdError(KIO_SFTP_DB) << "sftpSetStat(): unexpected message type of " << type << endl; + return -1; + } + + Q_UINT32 code; + r >> code; + if( code != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "sftpSetStat(): set stat failed with err code " << code << endl; + } + + return code; +} + +/** Sends a sftp command to remove a file or directory. */ +int sftpProtocol::sftpRemove(const KURL& url, bool isfile){ + + kdDebug(KIO_SFTP_DB) << "sftpRemove(): " << url << ", isFile ? " << isfile << endl; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (Q_UINT8)(isfile ? SSH2_FXP_REMOVE : SSH2_FXP_RMDIR); + s << (Q_UINT32)id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "del(): sftp packet id mismatch" << endl; + return -1; + } + + if( type != SSH2_FXP_STATUS ) { + kdError(KIO_SFTP_DB) << "del(): unexpected message type of " << type << endl; + return -1; + } + + Q_UINT32 code; + r >> code; + if( code != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "del(): del failed with err code " << code << endl; + } + + return code; +} + +/** Send a sftp command to rename a file or directoy. */ +int sftpProtocol::sftpRename(const KURL& src, const KURL& dest){ + + kdDebug(KIO_SFTP_DB) << "sftpRename(" << src << " -> " << dest << ")" << endl; + + QCString srcPath = remoteEncoding()->encode(src.path()); + QCString destPath = remoteEncoding()->encode(dest.path()); + + uint slen = srcPath.length(); + uint dlen = destPath.length(); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + slen + + 4 /*str length*/ + dlen); + s << (Q_UINT8)SSH2_FXP_RENAME; + s << (Q_UINT32)id; + s.writeBytes(srcPath.data(), slen); + s.writeBytes(destPath.data(), dlen); + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpRename(): sftp packet id mismatch" << endl; + return -1; + } + + if( type != SSH2_FXP_STATUS ) { + kdError(KIO_SFTP_DB) << "sftpRename(): unexpected message type of " << type << endl; + return -1; + } + + int code; + r >> code; + if( code != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "sftpRename(): rename failed with err code " << code << endl; + } + + return code; +} +/** Get directory listings. */ +int sftpProtocol::sftpReadDir(const QByteArray& handle, const KURL& url){ + // url is needed so we can lookup the link destination + kdDebug(KIO_SFTP_DB) << "sftpReadDir(): " << url << endl; + + Q_UINT32 id, expectedId, count; + Q_UINT8 type; + + sftpFileAttr attr (remoteEncoding()); + attr.setDirAttrsFlag(true); + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + id = expectedId = mMsgId++; + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); + s << (Q_UINT8)SSH2_FXP_READDIR; + s << (Q_UINT32)id; + s << handle; + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + r >> type >> id; + + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpReadDir(): sftp packet id mismatch" << endl; + return -1; + } + + int code; + if( type == SSH2_FXP_STATUS ) { + r >> code; + return code; + } + + if( type != SSH2_FXP_NAME ) { + kdError(KIO_SFTP_DB) << "kio_sftpProtocl::sftpReadDir(): Unexpected message" << endl; + return -1; + } + + r >> count; + kdDebug(KIO_SFTP_DB) << "sftpReadDir(): got " << count << " entries" << endl; + + while(count--) { + r >> attr; + + if( S_ISLNK(attr.permissions()) ) { + KURL myurl ( url ); + myurl.addPath(attr.filename()); + + // Stat the symlink to find out its type... + sftpFileAttr attr2 (remoteEncoding()); + (void) sftpStat(myurl, attr2); + + attr.setLinkType(attr2.linkType()); + attr.setLinkDestination(attr2.linkDestination()); + } + + listEntry(attr.entry(), false); + } + + listEntry(attr.entry(), true); + + return SSH2_FX_OK; +} + +int sftpProtocol::sftpReadLink(const KURL& url, QString& target){ + + kdDebug(KIO_SFTP_DB) << "sftpReadLink(): " << url << endl; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + //kdDebug(KIO_SFTP_DB) << "sftpReadLink(): Encoded Path: " << path << endl; + //kdDebug(KIO_SFTP_DB) << "sftpReadLink(): Encoded Size: " << len << endl; + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (Q_UINT8)SSH2_FXP_READLINK; + s << id; + s.writeBytes(path.data(), len); + + + putPacket(p); + getPacket(p); + + Q_UINT8 type; + QDataStream r(p, IO_ReadOnly); + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpReadLink(): sftp packet id mismatch" << endl; + return -1; + } + + if( type == SSH2_FXP_STATUS ) { + Q_UINT32 code; + r >> code; + kdDebug(KIO_SFTP_DB) << "sftpReadLink(): read link failed with code " << code << endl; + return code; + } + + if( type != SSH2_FXP_NAME ) { + kdError(KIO_SFTP_DB) << "sftpReadLink(): unexpected packet type of " << type << endl; + return -1; + } + + Q_UINT32 count; + r >> count; + if( count != 1 ) { + kdError(KIO_SFTP_DB) << "sftpReadLink(): Bad number of file attributes for realpath command" << endl; + return -1; + } + + QCString linkAddress; + r >> linkAddress; + + linkAddress.truncate(linkAddress.size()); + kdDebug(KIO_SFTP_DB) << "sftpReadLink(): Link address: " << linkAddress << endl; + + target = remoteEncoding()->decode(linkAddress); + + return SSH2_FX_OK; +} + +int sftpProtocol::sftpSymLink(const QString& _target, const KURL& dest){ + + QCString destPath = remoteEncoding()->encode(dest.path()); + QCString target = remoteEncoding()->encode(_target); + uint dlen = destPath.length(); + uint tlen = target.length(); + + kdDebug(KIO_SFTP_DB) << "sftpSymLink(" << target << " -> " << destPath << ")" << endl; + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + tlen + + 4 /*str length*/ + dlen); + s << (Q_UINT8)SSH2_FXP_SYMLINK; + s << (Q_UINT32)id; + s.writeBytes(target.data(), tlen); + s.writeBytes(destPath.data(), dlen); + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpSymLink(): sftp packet id mismatch" << endl; + return -1; + } + + if( type != SSH2_FXP_STATUS ) { + kdError(KIO_SFTP_DB) << "sftpSymLink(): unexpected message type of " << type << endl; + return -1; + } + + Q_UINT32 code; + r >> code; + if( code != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "sftpSymLink(): rename failed with err code " << code << endl; + } + + return code; +} + +/** Stats a file. */ +int sftpProtocol::sftpStat(const KURL& url, sftpFileAttr& attr) { + + kdDebug(KIO_SFTP_DB) << "sftpStat(): " << url << endl; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (Q_UINT8)SSH2_FXP_LSTAT; + s << (Q_UINT32)id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpStat(): sftp packet id mismatch" << endl; + return -1; + } + + if( type == SSH2_FXP_STATUS ) { + Q_UINT32 errCode; + r >> errCode; + kdError(KIO_SFTP_DB) << "sftpStat(): stat failed with code " << errCode << endl; + return errCode; + } + + if( type != SSH2_FXP_ATTRS ) { + kdError(KIO_SFTP_DB) << "sftpStat(): unexpected message type of " << type << endl; + return -1; + } + + r >> attr; + attr.setFilename(url.fileName()); + kdDebug(KIO_SFTP_DB) << "sftpStat(): " << attr << endl; + + // If the stat'ed resource is a symlink, perform a recursive stat + // to determine the actual destination's type (file/dir). + if( S_ISLNK(attr.permissions()) && isSupportedOperation(SSH2_FXP_READLINK) ) { + + QString target; + int code = sftpReadLink( url, target ); + + if ( code != SSH2_FX_OK ) { + kdError(KIO_SFTP_DB) << "sftpStat(): Unable to stat symlink destination" << endl; + return -1; + } + + kdDebug(KIO_SFTP_DB) << "sftpStat(): Resource is a symlink to -> " << target << endl; + + KURL dest( url ); + if( target[0] == '/' ) + dest.setPath(target); + else + dest.setFileName(target); + + dest.cleanPath(); + + // Ignore symlinks that point to themselves... + if ( dest != url ) { + + sftpFileAttr attr2 (remoteEncoding()); + (void) sftpStat(dest, attr2); + + if (attr2.linkType() == 0) + attr.setLinkType(attr2.fileType()); + else + attr.setLinkType(attr2.linkType()); + + attr.setLinkDestination(target); + + kdDebug(KIO_SFTP_DB) << "sftpStat(): File type: " << attr.fileType() << endl; + } + } + + return SSH2_FX_OK; +} + + +int sftpProtocol::sftpOpen(const KURL& url, const Q_UINT32 pflags, + const sftpFileAttr& attr, QByteArray& handle) { + kdDebug(KIO_SFTP_DB) << "sftpOpen(" << url << ", handle" << endl; + + QCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + QByteArray p; + QDataStream s(p, IO_WriteOnly); + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + len + + 4 /*pflags*/ + attr.size()); + s << (Q_UINT8)SSH2_FXP_OPEN; + s << (Q_UINT32)id; + s.writeBytes(path.data(), len); + s << pflags; + s << attr; + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpOpen(): sftp packet id mismatch" << endl; + return -1; + } + + if( type == SSH2_FXP_STATUS ) { + Q_UINT32 errCode; + r >> errCode; + return errCode; + } + + if( type != SSH2_FXP_HANDLE ) { + kdError(KIO_SFTP_DB) << "sftpOpen(): unexpected message type of " << type << endl; + return -1; + } + + r >> handle; + if( handle.size() > 256 ) { + kdError(KIO_SFTP_DB) << "sftpOpen(): handle exceeds max length" << endl; + return -1; + } + + kdDebug(KIO_SFTP_DB) << "sftpOpen(): handle (" << handle.size() << "): [" << handle << "]" << endl; + return SSH2_FX_OK; +} + + +int sftpProtocol::sftpRead(const QByteArray& handle, KIO::filesize_t offset, Q_UINT32 len, QByteArray& data) +{ + // kdDebug(KIO_SFTP_DB) << "sftpRead( offset = " << offset << ", len = " << len << ")" << endl; + QByteArray p; + QDataStream s(p, IO_WriteOnly); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + handle.size() + + 8 /*offset*/ + 4 /*length*/); + s << (Q_UINT8)SSH2_FXP_READ; + s << (Q_UINT32)id; + s << handle; + s << offset; // we don't have a convienient 64 bit int so set upper int to zero + s << len; + + putPacket(p); + getPacket(p); + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpRead: sftp packet id mismatch" << endl; + return -1; + } + + if( type == SSH2_FXP_STATUS ) { + Q_UINT32 errCode; + r >> errCode; + kdError(KIO_SFTP_DB) << "sftpRead: read failed with code " << errCode << endl; + return errCode; + } + + if( type != SSH2_FXP_DATA ) { + kdError(KIO_SFTP_DB) << "sftpRead: unexpected message type of " << type << endl; + return -1; + } + + r >> data; + + return SSH2_FX_OK; +} + + +int sftpProtocol::sftpWrite(const QByteArray& handle, KIO::filesize_t offset, const QByteArray& data){ +// kdDebug(KIO_SFTP_DB) << "sftpWrite( offset = " << offset << +// ", data sz = " << data.size() << ")" << endl; + QByteArray p; + QDataStream s(p, IO_WriteOnly); + + Q_UINT32 id, expectedId; + id = expectedId = mMsgId++; + s << (Q_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + handle.size() + + 8 /*offset*/ + + 4 /* data size */ + data.size()); + s << (Q_UINT8)SSH2_FXP_WRITE; + s << (Q_UINT32)id; + s << handle; + s << offset; // we don't have a convienient 64 bit int so set upper int to zero + s << data; + +// kdDebug(KIO_SFTP_DB) << "sftpWrite(): SSH2_FXP_WRITE, id:" +// << id << ", handle:" << handle << ", offset:" << offset << ", some data" << endl; + +// kdDebug(KIO_SFTP_DB) << "sftpWrite(): send packet [" << p << "]" << endl; + + putPacket(p); + getPacket(p); + +// kdDebug(KIO_SFTP_DB) << "sftpWrite(): received packet [" << p << "]" << endl; + + QDataStream r(p, IO_ReadOnly); + Q_UINT8 type; + + r >> type >> id; + if( id != expectedId ) { + kdError(KIO_SFTP_DB) << "sftpWrite(): sftp packet id mismatch, got " + << id << ", expected " << expectedId << endl; + return -1; + } + + if( type != SSH2_FXP_STATUS ) { + kdError(KIO_SFTP_DB) << "sftpWrite(): unexpected message type of " << type << endl; + return -1; + } + + Q_UINT32 code; + r >> code; + return code; +} + + diff --git a/kioslave/sftp/kio_sftp.h b/kioslave/sftp/kio_sftp.h new file mode 100644 index 000000000..ff99b4760 --- /dev/null +++ b/kioslave/sftp/kio_sftp.h @@ -0,0 +1,149 @@ +/*************************************************************************** + sftpProtocol.h - description + ------------------- + begin : Sat Jun 30 20:08:47 CDT 2001 + copyright : (C) 2001 by Lucas Fisher + email : ljfisher@purdue.edu +***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#ifndef __kio_sftp_h__ +#define __kio_sftp_h__ + +#include <qstring.h> +#include <qcstring.h> +#include <qobject.h> + +#include <kurl.h> +#include <kio/global.h> +#include <kio/slavebase.h> +#include <kdebug.h> + +#include "process.h" +#include "sftpfileattr.h" +#include "ksshprocess.h" + +#define KIO_SFTP_DB 7120 + + +class sftpProtocol : public KIO::SlaveBase +{ + +public: + sftpProtocol(const QCString &pool_socket, const QCString &app_socket); + virtual ~sftpProtocol(); + virtual void setHost(const QString& h, int port, const QString& user, const QString& pass); + virtual void get(const KURL& url); + virtual void listDir(const KURL& url) ; + virtual void mimetype(const KURL& url); + virtual void stat(const KURL& url); + virtual void copy(const KURL &src, const KURL &dest, int permissions, bool overwrite); + virtual void put(const KURL& url, int permissions, bool overwrite, bool resume); + virtual void closeConnection(); + virtual void slave_status(); + virtual void del(const KURL &url, bool isfile); + virtual void chmod(const KURL& url, int permissions); + virtual void symlink(const QString& target, const KURL& dest, bool overwrite); + virtual void rename(const KURL& src, const KURL& dest, bool overwrite); + virtual void mkdir(const KURL&url, int permissions); + virtual void openConnection(); + +private: // Private variables + /** True if ioslave is connected to sftp server. */ + bool mConnected; + + /** Host we are connected to. */ + QString mHost; + + /** Port we are connected to. */ + int mPort; + + /** Ssh process to which we send the sftp packets. */ + KSshProcess ssh; + + /** Username to use when connecting */ + QString mUsername; + + /** User's password */ + QString mPassword; + + /** Message id of the last sftp packet we sent. */ + unsigned int mMsgId; + + /** Type of packet we are expecting to receive next. */ + unsigned char mExpected; + + /** Version of the sftp protocol we are using. */ + int sftpVersion; + + struct Status + { + int code; + KIO::filesize_t size; + QString text; + }; + +private: // private methods + bool getPacket(QByteArray& msg); + + /* Type is a sftp packet type found in .sftp.h'. + * Example: SSH2_FXP_READLINK, SSH2_FXP_RENAME, etc. + * + * Returns true if the type is supported by the sftp protocol + * version negotiated by the client and server (sftpVersion). + */ + bool isSupportedOperation(int type); + /** Used to have the server canonicalize any given path name to an absolute path. + This is useful for converting path names containing ".." components or relative + pathnames without a leading slash into absolute paths. + Returns the canonicalized url. */ + int sftpRealPath(const KURL& url, KURL& newUrl); + + /** Send an sftp packet to stdin of the ssh process. */ + bool putPacket(QByteArray& p); + /** Process SSH_FXP_STATUS packets. */ + void processStatus(Q_UINT8, const QString& message = QString::null); + /** Process SSH_FXP_STATUS packes and return the result. */ + Status doProcessStatus(Q_UINT8, const QString& message = QString::null); + /** Opens a directory handle for url.path. Returns true if succeeds. */ + int sftpOpenDirectory(const KURL& url, QByteArray& handle); + /** Closes a directory or file handle. */ + int sftpClose(const QByteArray& handle); + /** Send a sftp command to rename a file or directoy. */ + int sftpRename(const KURL& src, const KURL& dest); + /** Set a files attributes. */ + int sftpSetStat(const KURL& url, const sftpFileAttr& attr); + /** Sends a sftp command to remove a file or directory. */ + int sftpRemove(const KURL& url, bool isfile); + /** Creates a symlink named dest to target. */ + int sftpSymLink(const QString& target, const KURL& dest); + /** Get directory listings. */ + int sftpReadDir(const QByteArray& handle, const KURL& url); + /** Retrieves the destination of a link. */ + int sftpReadLink(const KURL& url, QString& target); + /** Stats a file. */ + int sftpStat(const KURL& url, sftpFileAttr& attr); + /** No descriptions */ + int sftpOpen(const KURL& url, const Q_UINT32 pflags, const sftpFileAttr& attr, QByteArray& handle); + /** No descriptions */ + int sftpRead(const QByteArray& handle, KIO::filesize_t offset, Q_UINT32 len, QByteArray& data); + /** No descriptions */ + int sftpWrite(const QByteArray& handle, KIO::filesize_t offset, const QByteArray& data); + + /** Performs faster upload when the source is a local file... */ + void sftpCopyPut(const KURL& src, const KURL& dest, int mode, bool overwrite); + /** Performs faster download when the destination is a local file... */ + void sftpCopyGet(const KURL& dest, const KURL& src, int mode, bool overwrite); + + /** */ + Status sftpGet( const KURL& src, KIO::filesize_t offset = 0, int fd = -1); + void sftpPut( const KURL& dest, int permissions, bool resume, bool overwrite, int fd = -1); +}; +#endif diff --git a/kioslave/sftp/ksshprocess.cpp b/kioslave/sftp/ksshprocess.cpp new file mode 100644 index 000000000..c0393445d --- /dev/null +++ b/kioslave/sftp/ksshprocess.cpp @@ -0,0 +1,1104 @@ +/*************************************************************************** + ksshprocess.cpp - description + ------------------- + begin : Tue Jul 31 2001 + copyright : (C) 2001 by Lucas Fisher + email : ljfisher@purdue.edu + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +/* + * See the KSshProcess header for examples on use. + * + * This class uses a hacked version of the PTYProcess + * class. This was needed because the kdelibs PTYProcess does not provide + * access to the pty file descriptor which we need, because ssh prints the + * password prompt to the pty and reads the password from the pty. I don't + * feel I know enough about ptys to confidently modify the orignial + * PTYProcess class. + * + * To start ssh we take the arguments the user gave us + * in the SshOptList and build the ssh command arguments based on the version + * of ssh we are using. This command and its arguments are passed to + * PTYProcess for execution. Once ssh is started we scan each line of input + * from stdin, stderr, and the pty for recognizable strings. The recognizable + * strings are taken from several string tables. Each table contains a string + * for each specific version of ssh we support and a string for a generic + * version of OpenSSH and commercial SSH incase we don't recognized the + * specific ssh version strings (as when a new SSH version is released after + * a release of KSshProcess). There are tables for ssh version strings, + * password prompts, new host key errors, different host key errors, + * messages than indicate a successful connect, authentication errors, etc. + * If we find user interaction is necessary, for instance to provide a + * password or passphrase, we return a err code to the user who can send + * a message to KSshProcess, using one of several methods, to correct + * the error. + * + * Determining when the ssh connection has successfully authenticationed has + * proved to be the most difficult challenge. OpenSSH does not print a message + * on successful authentication, thus the only way to know is to send data + * and wait for a return. The problem here is sometimes it can take a bit + * to establish the connection (for example, do to DNS lookups). This means + * the user may be sitting there waiting for a connection that failed. + * Instead, ssh is always started with the verbose flag. Then we look for + * a message that indicates auth succeeded. This is hazardous because + * debug messages are more likely to change between OpenSSH releases. + * Thus, we could become incompatible with new OpenSSH releases. + */ + +#include <config.h> + +#include "ksshprocess.h" + +#include <stdio.h> +#include <errno.h> + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#include <kstandarddirs.h> +#include <klocale.h> +#include <qregexp.h> + +/* + * The following are tables of string and regexps we match + * against the output of ssh. An entry in each array + * corresponds the the version of ssh found in versionStrs[]. + * + * The version strings must be ordered in the array from most + * specific to least specific in cases where the beginning + * of several version strings are the similar. For example, + * consider the openssh version strings. The generic "OpenSSH" + * must be the last of the openssh version strings in the array + * so that is matched last. We use these generic version strings + * so we can do a best effor to support unknown ssh versions. + */ +QRegExp KSshProcess::versionStrs[] = { + QRegExp("OpenSSH_3\\.[6-9]|OpenSSH_[1-9]*[4-9]\\.[0-9]"), + QRegExp("OpenSSH"), + QRegExp("SSH Secure Shell") +}; + +const char * const KSshProcess::passwordPrompt[] = { + "password:", // OpenSSH + "password:", // OpenSSH + "password:" // SSH +}; + +const char * const KSshProcess::passphrasePrompt[] = { + "Enter passphrase for key", + "Enter passphrase for key", + "Passphrase for key" +}; + +const char * const KSshProcess::authSuccessMsg[] = { + "Authentication succeeded", + "ssh-userauth2 successful", + "Received SSH_CROSS_AUTHENTICATED packet" +}; + +const char* const KSshProcess::authFailedMsg[] = { + "Permission denied (", + "Permission denied (", + "Authentication failed." +}; + +const char* const KSshProcess::tryAgainMsg[] = { + "please try again", + "please try again", + "adjfhjsdhfdsjfsjdfhuefeufeuefe" +}; + +QRegExp KSshProcess::hostKeyMissingMsg[] = { + QRegExp("The authenticity of host|No (DSA|RSA) host key is known for"), + QRegExp("The authenticity of host|No (DSA|RSA) host key is known for"), + QRegExp("Host key not found from database") +}; + +const char* const KSshProcess::continuePrompt[] = { + "Are you sure you want to continue connecting (yes/no)?", + "Are you sure you want to continue connecting (yes/no)?", + "Are you sure you want to continue connecting (yes/no)?" +}; + +const char* const KSshProcess::hostKeyChangedMsg[] = { + "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!", + "WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!", + "WARNING: HOST IDENTIFICATION HAS CHANGED!" +}; + +QRegExp KSshProcess::keyFingerprintMsg[] = { + QRegExp("..(:..){15}"), + QRegExp("..(:..){15}"), + QRegExp(".....(-.....){10}") +}; + +QRegExp KSshProcess::knownHostsFileMsg[] = { + QRegExp("Add correct host key in (.*) to get rid of this message."), + QRegExp("Add correct host key in (.*) to get rid of this message."), + QRegExp("Add correct host key to \"(.*)\"") +}; + + +// This prompt only applies to commerical ssh. +const char* const KSshProcess::changeHostKeyOnDiskPrompt[] = { + "as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf", + "as;jf;sajkfdslkfjas;dfjdsa;fj;dsajfdsajf", + "Do you want to change the host key on disk (yes/no)?" +}; + +// We need this in addition the authFailedMsg because when +// OpenSSH gets a changed host key it will fail to connect +// depending on the StrictHostKeyChecking option. Depending +// how this option is set, it will print "Permission denied" +// and quit, or print "Host key verification failed." and +// quit. The later if StrictHostKeyChecking is "no". +// The former if StrictHostKeyChecking is +// "yes" or explicitly set to "ask". +QRegExp KSshProcess::hostKeyVerifyFailedMsg[] = { + QRegExp("Host key verification failed\\."), + QRegExp("Host key verification failed\\."), + QRegExp("Disconnected; key exchange or algorithm? negotiation failed \\(Key exchange failed\\.\\)\\.") +}; + +const char * const KSshProcess::connectionClosedMsg[] = { + "Connection closed by remote host", + "Connection closed by remote host", + "Connection closed by remote host" +}; + + +void KSshProcess::SIGCHLD_handler(int) { + while(waitpid(-1, NULL, WNOHANG) > 0); +} + +void KSshProcess::installSignalHandlers() { + struct sigaction act; + memset(&act,0,sizeof(act)); + act.sa_handler = SIGCHLD_handler; + act.sa_flags = 0 +#ifdef SA_NOCLDSTOP + | SA_NOCLDSTOP +#endif +#ifdef SA_RESTART + | SA_RESTART +#endif + ; + sigaction(SIGCHLD,&act,NULL); +} + +void KSshProcess::removeSignalHandlers() { + struct sigaction act; + memset(&act,0,sizeof(act)); + act.sa_handler = SIG_DFL; + sigaction(SIGCHLD,&act,NULL); +} + +KSshProcess::KSshProcess() + : mVersion(UNKNOWN_VER), mConnected(false), + mRunning(false), mConnectState(0) { + mSshPath = KStandardDirs::findExe(QString::fromLatin1("ssh")); + kdDebug(KSSHPROC) << "KSshProcess::KSshProcess(): ssh path [" << + mSshPath << "]" << endl; + + installSignalHandlers(); +} + +KSshProcess::KSshProcess(QString pathToSsh) + : mSshPath(pathToSsh), mVersion(UNKNOWN_VER), mConnected(false), + mRunning(false), mConnectState(0) { + installSignalHandlers(); +} + +KSshProcess::~KSshProcess(){ + disconnect(); + removeSignalHandlers(); + while(waitpid(-1, NULL, WNOHANG) > 0); +} + +bool KSshProcess::setSshPath(QString pathToSsh) { + mSshPath = pathToSsh; + version(); + if( mVersion == UNKNOWN_VER ) + return false; + + return true; +} + +KSshProcess::SshVersion KSshProcess::version() { + QString cmd; + cmd = mSshPath+" -V 2>&1"; + + // Get version string from ssh client. + FILE *p; + if( (p = popen(cmd.latin1(), "r")) == NULL ) { + kdDebug(KSSHPROC) << "KSshProcess::version(): " + "failed to start ssh: " << strerror(errno) << endl; + return UNKNOWN_VER; + } + + // Determine of the version from the version string. + size_t len; + char buf[128]; + if( (len = fread(buf, sizeof(char), sizeof(buf)-1, p)) == 0 ) { + kdDebug(KSSHPROC) << "KSshProcess::version(): " + "Read of ssh version string failed " << + strerror(ferror(p)) << endl; + return UNKNOWN_VER; + } + if( pclose(p) == -1 ) { + kdError(KSSHPROC) << "KSshProcess::version(): pclose failed." << endl; + } + buf[len] = '\0'; + QString ver; + ver = buf; + kdDebug(KSSHPROC) << "KSshProcess::version(): " + "got version string [" << ver << "]" << endl; + + mVersion = UNKNOWN_VER; + for(int i = 0; i < SSH_VER_MAX; i++) { + if( ver.find(versionStrs[i]) != -1 ) { + mVersion = (SshVersion)i; + break; + } + } + + kdDebug(KSSHPROC) << "KSshPRocess::version(): version number = " + << mVersion << endl; + + if( mVersion == UNKNOWN_VER ) { + kdDebug(KSSHPROC) << "KSshProcess::version(): " + "Sorry, I don't know about this version of ssh" << endl; + mError = ERR_UNKNOWN_VERSION; + return UNKNOWN_VER; + } + + return mVersion; +} +/* +QString KSshProcess::versionStr() { + if( mVersion == UNKNOWN_VER ) { + version(); + if( mVersion == UNKNOWN_VER ) + return QString::null; + } + + return QString::fromLatin1(versionStrs[mVersion]); +} +*/ + +bool KSshProcess::setOptions(const SshOptList& opts) { + kdDebug(KSSHPROC) << "KSshProcess::setOptions()" << endl; + mArgs.clear(); + SshOptListConstIterator it; + QString cmd, subsystem; + mPassword = mUsername = mHost = QString::null; + QCString tmp; + for(it = opts.begin(); it != opts.end(); ++it) { + //kdDebug(KSSHPROC) << "opt.opt = " << (*it).opt << endl; + //kdDebug(KSSHPROC) << "opt.str = " << (*it).str << endl; + //kdDebug(KSSHPROC) << "opt.num = " << (*it).num << endl; + switch( (*it).opt ) { + case SSH_VERBOSE: + mArgs.append("-v"); + break; + + case SSH_SUBSYSTEM: + subsystem = (*it).str; + break; + + case SSH_PORT: + mArgs.append("-p"); + tmp.setNum((*it).num); + mArgs.append(tmp); + mPort = (*it).num; + break; + + case SSH_HOST: + mHost = (*it).str; + break; + + case SSH_USERNAME: + mArgs.append("-l"); + mArgs.append((*it).str.latin1()); + mUsername = (*it).str; + break; + + case SSH_PASSWD: + mPassword = (*it).str; + break; + + case SSH_PROTOCOL: + if( mVersion <= OPENSSH ) { + tmp = "Protocol="; + tmp += QString::number((*it).num).latin1(); + mArgs.append("-o"); + mArgs.append(tmp); + } + else if( mVersion <= SSH ) { + if( (*it).num == 1 ) { + mArgs.append("-1"); + } + // else uses version 2 by default + } + break; + + case SSH_FORWARDX11: + tmp = "ForwardX11="; + tmp += (*it).boolean ? "yes" : "no"; + mArgs.append("-o"); + mArgs.append(tmp); + break; + + case SSH_FORWARDAGENT: + tmp = "ForwardAgent="; + tmp += (*it).boolean ? "yes" : "no"; + mArgs.append("-o"); + mArgs.append(tmp); + break; + + case SSH_ESCAPE_CHAR: + if( (*it).num == -1 ) + tmp = "none"; + else + tmp = (char)((*it).num); + mArgs.append("-e"); + mArgs.append(tmp); + break; + + case SSH_OPTION: + // don't allow NumberOfPasswordPrompts or StrictHostKeyChecking + // since KSshProcess depends on specific setting of these for + // preforming authentication correctly. + tmp = (*it).str.latin1(); + if( tmp.contains("NumberOfPasswordPrompts") || + tmp.contains("StrictHostKeyChecking") ) { + mError = ERR_INVALID_OPT; + return false; + } + else { + mArgs.append("-o"); + mArgs.append(tmp); + } + break; + + case SSH_COMMAND: + cmd = (*it).str; + break; + + default: + kdDebug(KSSHPROC) << "KSshProcess::setOptions(): " + "unrecognized ssh opt " << (*it).opt << endl; + } + } + + if( !subsystem.isEmpty() && !cmd.isEmpty() ) { + kdDebug(KSSHPROC) << "KSshProcess::setOptions(): " + "cannot use a subsystem and command at the same time" << endl; + mError = ERR_CMD_SUBSYS_CONFLICT; + mErrorMsg = i18n("Cannot specify a subsystem and command at the same time."); + return false; + } + + // These options govern the behavior of ssh and + // cannot be defined by the user + //mArgs.append("-o"); + //mArgs.append("StrictHostKeyChecking=ask"); + mArgs.append("-v"); // So we get a message that the + // connection was successful + if( mVersion <= OPENSSH ) { + // nothing + } + else if( mVersion <= SSH ) { + mArgs.append("-o"); // So we can check if the connection was successful + mArgs.append("AuthenticationSuccessMsg=yes"); + } + + if( mHost.isEmpty() ) { + kdDebug(KSSHPROC) << "KSshProcess::setOptions(): " + "a host name must be supplied" << endl; + return false; + } + else { + mArgs.append(mHost.latin1()); + } + + if( !subsystem.isEmpty() ) { + mArgs.append("-s"); + mArgs.append(subsystem.latin1()); + } + + if( !cmd.isEmpty() ) { + mArgs.append(cmd.latin1()); + } + + return true; +} + +void KSshProcess::printArgs() { + QValueListIterator<QCString> it; + for( it = mArgs.begin(); it != mArgs.end(); ++it) { + kdDebug(KSSHPROC) << "arg: " << *it << endl; + } +} + + +int KSshProcess::error(QString& msg) { + kdDebug(KSSHPROC) << "KSshProcess::error()" << endl; + kdDebug() << mErrorMsg << endl; + msg = mErrorMsg; + return mError; +} + +void KSshProcess::kill(int signal) { + int pid = ssh.pid(); + + kdDebug(KSSHPROC) << "KSshProcess::kill(signal:" << signal + << "): ssh pid is " << pid << endl; + kdDebug(KSSHPROC) << "KSshPRocess::kill(): we are " + << (mConnected ? "" : "not ") << "connected" << endl; + kdDebug(KSSHPROC) << "KSshProcess::kill(): we are " + << (mRunning ? "" : "not ") << "running a ssh process" << endl; + + if( mRunning && pid > 1 ) { + // Kill the child process... + if ( ::kill(pid, signal) == 0 ) { + // clean up if we tried to kill the process + if( signal == SIGTERM || signal == SIGKILL ) { + while(waitpid(-1, NULL, WNOHANG) > 0); + mConnected = false; + mRunning = false; + } + } + else + kdDebug(KSSHPROC) << "KSshProcess::kill(): kill failed" << endl; + } + else + kdDebug(KSSHPROC) << "KSshProcess::kill(): " + "Refusing to kill ssh process" << endl; +} + + + +/** + * Try to open an ssh connection. + * SSH prints certain messages to certain file descriptiors: + * passwordPrompt - pty + * passphrasePrompt - pty + * authSuccessMsg - stderr (OpenSSH), + * authFailedMsg - stderr + * hostKeyMissing - stderr + * hostKeyChanged - stderr + * continuePrompt - stderr + * + * We will use a select to wait for a line on each descriptor. Then get + * each line that available and take action based on it. The type + * of messages we are looking for and the action we take on each + * message are: + * passwordPrompt - Return false, set error to ERR_NEED_PASSWD. + * On the next call to connect() we expect a password + * to be available. + * + * passpharsePrompt - Return false, set error to ERR_NEED_PASSPHRASE. + * On the next call to connect() we expect a + * passphrase to be available. + * + * authSuccessMsg - Return true, as we have successfully established a + * ssh connection. + * + * authFailedMsg - Return false, set error to ERR_AUTH_FAILED. We + * were unable to authenticate the connection given + * the available authentication information. + * + * hostKeyMissing - Return false, set error to ERR_NEW_HOST_KEY. Caller + * must call KSshProcess.acceptHostKey(bool) to accept + * or reject the key before calling connect() again. + * + * hostKeyChanged - Return false, set error to ERR_DIFF_HOST_KEY. Caller + * must call KSshProcess.acceptHostKey(bool) to accept + * or reject the key before calling connect() again. + * + * continuePrompt - Send 'yes' or 'no' to accept or reject a key, + * respectively. + * + */ + + +void KSshProcess::acceptHostKey(bool accept) { + kdDebug(KSSHPROC) << "KSshProcess::acceptHostKey(accept:" + << accept << ")" << endl; + mAcceptHostKey = accept; +} + +void KSshProcess::setPassword(QString password) { + kdDebug(KSSHPROC) << "KSshProcess::setPassword(password:xxxxxxxx)" << endl; + mPassword = password; +} + +QString KSshProcess::getLine() { + static QStringList buffer; + QString line = QString::null; + QCString ptyLine, errLine; + + if( buffer.empty() ) { + // PtyProcess buffers lines. First check that there + // isn't something on the PtyProces buffer or that there + // is not data ready to be read from the pty or stderr. + ptyLine = ssh.readLineFromPty(false); + errLine = ssh.readLineFromStderr(false); + + // If PtyProcess did have something for us, get it and + // place it in our line buffer. + if( ! ptyLine.isEmpty() ) { + buffer.prepend(QString(ptyLine)); + } + + if( ! errLine.isEmpty() ) { + buffer.prepend(QString(errLine)); + } + + // If we still don't have anything in our buffer so there must + // not be anything on the pty or stderr. Setup a select() + // to wait for some data from SSH. + if( buffer.empty() ) { + //kdDebug(KSSHPROC) << "KSshProcess::getLine(): " << + // "Line buffer empty, calling select() to wait for data." << endl; + int errfd = ssh.stderrFd(); + int ptyfd = ssh.fd(); + fd_set rfds; + fd_set efds; + struct timeval tv; + + // find max file descriptor + int maxfd = ptyfd > errfd ? ptyfd : errfd; + + FD_ZERO(&rfds); + FD_SET(ptyfd, &rfds); // Add pty file descriptor + FD_SET(errfd, &rfds); // Add std error file descriptor + + FD_ZERO(&efds); + FD_SET(ptyfd, &efds); + FD_SET(errfd, &efds); + + tv.tv_sec = 60; tv.tv_usec = 0; // 60 second timeout + + // Wait for a message from ssh on stderr or the pty. + int ret = -1; + do + ret = ::select(maxfd+1, &rfds, NULL, &efds, &tv); + while( ret == -1 && errno == EINTR ); + + // Handle any errors from select + if( ret == 0 ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " << + "timed out waiting for a response" << endl; + mError = ERR_TIMED_OUT; + return QString::null; + } + else if( ret == -1 ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + << "select error: " << strerror(errno) << endl; + mError = ERR_INTERNAL; + return QString::null; + } + + // We are not respecting any type of order in which the + // lines were received. Who knows whether pty or stderr + // had data on it first. + if( FD_ISSET(ptyfd, &rfds) ) { + ptyLine = ssh.readLineFromPty(false); + buffer.prepend(QString(ptyLine)); + //kdDebug(KSSHPROC) << "KSshProcess::getLine(): " + // "line from pty -" << ptyLine << endl; + } + + if( FD_ISSET(errfd, &rfds) ) { + errLine = ssh.readLineFromStderr(false); + buffer.prepend(QString(errLine)); + //kdDebug(KSSHPROC) << "KSshProcess::getLine(): " + // "line from err -" << errLine << endl; + } + + if( FD_ISSET(ptyfd, &efds) ) { + kdDebug(KSSHPROC) << "KSshProcess::getLine(): " + "Exception on pty file descriptor." << endl; + } + + if( FD_ISSET(errfd, &efds) ) { + kdDebug(KSSHPROC) << "KSshProcess::getLine(): " + "Exception on std err file descriptor." << endl; + } + + } + } + + // We should have something in our buffer now. + // Return the last line. + //it = buffer.end(); + //line = *it; + //buffer.remove(it); + + line = buffer.last(); + buffer.pop_back(); + + if( line.isNull() && buffer.count() > 0 ) { + line = buffer.last(); + buffer.pop_back(); + } + +// kdDebug(KSSHPROC) << "KSshProcess::getLine(): " << +// buffer.count() << " lines in buffer" << endl; + kdDebug(KSSHPROC) << "KSshProcess::getLine(): " + "ssh: " << line << endl; + + + return line; +} + +// All the different states we could go through while trying to connect. +enum sshConnectState { + STATE_START, STATE_TRY_PASSWD, STATE_WAIT_PROMPT, STATE_NEW_KEY_CONTINUE, + STATE_DIFF_KEY_CONTINUE, STATE_FATAL, STATE_WAIT_CONTINUE_PROMPT, + STATE_SEND_CONTINUE, STATE_AUTH_FAILED, STATE_NEW_KEY_WAIT_CONTINUE, + STATE_DIFF_KEY_WAIT_CONTINUE, STATE_TRY_PASSPHRASE +}; + +// Print the state as a string. Good for debugging +const char* stateStr(int state) { + switch(state) { + case STATE_START: + return "STATE_START"; + case STATE_TRY_PASSWD: + return "STATE_TRY_PASSWD"; + case STATE_WAIT_PROMPT: + return "STATE_WAIT_PROMPT"; + case STATE_NEW_KEY_CONTINUE: + return "STATE_NEW_KEY_CONTINUE"; + case STATE_DIFF_KEY_CONTINUE: + return "STATE_DIFF_KEY_CONTINUE"; + case STATE_FATAL: + return "STATE_FATAL"; + case STATE_WAIT_CONTINUE_PROMPT: + return "STATE_WAIT_CONTINUE_PROMPT"; + case STATE_SEND_CONTINUE: + return "STATE_SEND_CONTINE"; + case STATE_AUTH_FAILED: + return "STATE_AUTH_FAILED"; + case STATE_NEW_KEY_WAIT_CONTINUE: + return "STATE_NEW_KEY_WAIT_CONTINUE"; + case STATE_DIFF_KEY_WAIT_CONTINUE: + return "STATE_DIFF_KEY_WAIT_CONTINUE"; + case STATE_TRY_PASSPHRASE: + return "STATE_TRY_PASSPHRASE"; + } + return "UNKNOWN"; +} + +bool KSshProcess::connect() { + if( mVersion == UNKNOWN_VER ) { + // we don't know the ssh version yet, so find out + version(); + if( mVersion == -1 ) { + return false; + } + } + + // We'll put a limit on the number of state transitions + // to ensure we don't go out of control. + int transitionLimit = 500; + + while(--transitionLimit) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + << "Connect state " << stateStr(mConnectState) << endl; + + QString line; // a line from ssh + QString msgBuf; // buffer for important messages from ssh + // which are to be returned to the user + + switch(mConnectState) { + // STATE_START: + // Executes the ssh binary with the options provided. If no options + // have been specified, sets error and returns false. Continue to + // state 1 if execution is successful, otherwise set error and + // return false. + case STATE_START: + // reset some key values to safe values + mAcceptHostKey = false; + mKeyFingerprint = QString::null; + mKnownHostsFile = QString::null; + + if( mArgs.isEmpty() ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): ssh options " + "need to be set first using setArgs()" << endl; + mError = ERR_NO_OPTIONS; + mErrorMsg = i18n("No options provided for ssh execution."); + return false; + } + + if( ssh.exec(mSshPath.latin1(), mArgs) ) { + kdDebug(KSSHPROC) << + "KSshProcess::connect(): ssh exec failed" << endl; + mError = ERR_CANNOT_LAUNCH; + mErrorMsg = i18n("Failed to execute ssh process."); + return false; + } + + kdDebug(KSSHPROC) << "KSshPRocess::connect(): ssh pid = " << ssh.pid() << endl; + + // set flag to indicate what have started a ssh process + mRunning = true; + mConnectState = STATE_WAIT_PROMPT; + break; + + // STATE_WAIT_PROMPT: + // Get a line of input from the ssh process. Check the contents + // of the line to determine the next state. Ignore the line + // if we don't recognize its contents. If the line contains + // the continue prompt, we have an error since we should never + // get that line in this state. Set ERR_INVALID_STATE error + // and return false. + case STATE_WAIT_PROMPT: + line = getLine(); + if( line.isNull() ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + "Got null line in STATE_WAIT_PROMPT." << endl; + mError = ERR_INTERACT; + mErrorMsg = + i18n("Error encountered while talking to ssh."); + mConnectState = STATE_FATAL; + } + else if( line.find(QString::fromLatin1(passwordPrompt[mVersion]), 0, false) != -1 ) { + mConnectState = STATE_TRY_PASSWD; + } + else if( line.find(passphrasePrompt[mVersion]) != -1 ) { + mConnectState = STATE_TRY_PASSPHRASE; + } + else if( line.find(authSuccessMsg[mVersion]) != -1 ) { + return true; + } + else if( line.find(authFailedMsg[mVersion]) != -1 + && line.find(tryAgainMsg[mVersion]) == -1 ) { + mConnectState = STATE_AUTH_FAILED; + } + else if( line.find(hostKeyMissingMsg[mVersion]) != -1 ) { + mConnectState = STATE_NEW_KEY_WAIT_CONTINUE; + } + else if( line.find(hostKeyChangedMsg[mVersion]) != -1 ) { + mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE; + } + else if( line.find(continuePrompt[mVersion]) != -1 ) { + //mConnectState = STATE_SEND_CONTINUE; + kdDebug(KSSHPROC) << "KSshProcess:connect(): " + "Got continue prompt where we shouldn't (STATE_WAIT_PROMPT)" + << endl; + mError = ERR_INTERACT; + mErrorMsg = + i18n("Error encountered while talking to ssh."); + } + else if( line.find(connectionClosedMsg[mVersion]) != -1 ) { + mConnectState = STATE_FATAL; + mError = ERR_CLOSED_BY_REMOTE_HOST; + mErrorMsg = i18n("Connection closed by remote host."); + } + else if( line.find(changeHostKeyOnDiskPrompt[mVersion]) != -1 ) { + // always say yes to this. It always comes after commerical ssh + // prints a "continue to connect prompt". We assume that if the + // user choose to continue, then they also want to save the + // host key to disk. + ssh.writeLine("yes"); + } + else { + // ignore line + } + break; + + // STATE_TRY_PASSWD: + // If we have password send it to the ssh process, else + // set error ERR_NEED_PASSWD and return false to the caller. + // The caller then must then call KSshProcess::setPassword(QString) + // before calling KSshProcess::connect() again. + // + // Almost exactly liek STATE_TRY_PASSPHRASE. Check there if you + // make changes here. + case STATE_TRY_PASSWD: + // We have a password prompt waiting for us to supply + // a password. Send that password to ssh. If the caller + // did not supply a password like we asked, then ask + // again. + if( !mPassword.isEmpty() ) { +// ssh.WaitSlave(); + ssh.writeLine(mPassword.latin1()); + + // Overwrite the password so it isn't in memory. + mPassword.fill(QChar('X')); + + // Set the password to null so we will request another + // password if this one fails. + mPassword = QString::null; + + mConnectState = STATE_WAIT_PROMPT; + } + else { + kdDebug(KSSHPROC) << "KSshProcess::connect() " + "Need password from caller." << endl; + // The caller needs to supply a password before + // connecting can continue. + mError = ERR_NEED_PASSWD; + mErrorMsg = i18n("Please supply a password."); + mConnectState = STATE_TRY_PASSWD; + return false; + } + break; + + // STATE_TRY_KEY_PASSPHRASE: + // If we have passphrase send it to the ssh process, else + // set error ERR_NEED_PASSPHRASE and return false to the caller. + // The caller then must then call KSshProcess::setPassword(QString) + // before calling KSshProcess::connect() again. + // + // Almost exactly like STATE_TRY_PASSWD. The only difference is + // the error we set if we don't have a passphrase. We duplicate + // this code to keep in the spirit of the state machine. + case STATE_TRY_PASSPHRASE: + // We have a passphrase prompt waiting for us to supply + // a passphrase. Send that passphrase to ssh. If the caller + // did not supply a passphrase like we asked, then ask + // again. + if( !mPassword.isEmpty() ) { +// ssh.WaitSlave(); + ssh.writeLine(mPassword.latin1()); + + // Overwrite the password so it isn't in memory. + mPassword.fill(QChar('X')); + + // Set the password to null so we will request another + // password if this one fails. + mPassword = QString::null; + + mConnectState = STATE_WAIT_PROMPT; + } + else { + kdDebug(KSSHPROC) << "KSshProcess::connect() " + "Need passphrase from caller." << endl; + // The caller needs to supply a passphrase before + // connecting can continue. + mError = ERR_NEED_PASSPHRASE; + mErrorMsg = i18n("Please supply the passphrase for " + "your SSH private key."); + mConnectState = STATE_TRY_PASSPHRASE; + return false; + } + break; + + // STATE_AUTH_FAILED: + // Authentication has failed. Tell the caller by setting the + // ERR_AUTH_FAILED error and returning false. If + // auth has failed then ssh should have exited, but + // we will kill it to make sure. + case STATE_AUTH_FAILED: + mError = ERR_AUTH_FAILED; + mErrorMsg = i18n("Authentication to %1 failed").arg(mHost); + mConnectState = STATE_FATAL; + break; + + // STATE_NEW_KEY_WAIT_CONTINUE: + // Grab lines from ssh until we get a continue prompt or a auth + // denied. We will get the later if StrictHostKeyChecking is set + // to yes. Go to STATE_NEW_KEY_CONTINUE if we get a continue prompt. + case STATE_NEW_KEY_WAIT_CONTINUE: + line = getLine(); + if( line.isNull() ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + "Got null line in STATE_NEW_KEY_WAIT_CONTINUE." << endl; + mError = ERR_INTERACT; + mErrorMsg = + i18n("Error encountered while talking to ssh."); + mConnectState = STATE_FATAL; + } + else if( (line.find(authFailedMsg[mVersion]) != -1 + && line.find(tryAgainMsg[mVersion]) == -1) + || line.find(hostKeyVerifyFailedMsg[mVersion]) != -1 ) { + mError = ERR_AUTH_FAILED_NEW_KEY; + mErrorMsg = i18n( + "The identity of the remote host '%1' could not be verified " + "because the host's key is not in the \"known hosts\" file." + ).arg(mHost); + + if( mKnownHostsFile.isEmpty() ) { + mErrorMsg += i18n( + " Manually, add the host's key to the \"known hosts\" " + "file or contact your administrator." + ); + } + else { + mErrorMsg += i18n( + " Manually, add the host's key to %1 " + "or contact your administrator." + ).arg(mKnownHostsFile); + } + + mConnectState = STATE_FATAL; + } + else if( line.find(continuePrompt[mVersion]) != -1 ) { + mConnectState = STATE_NEW_KEY_CONTINUE; + } + else if( line.find(connectionClosedMsg[mVersion]) != -1 ) { + mConnectState = STATE_FATAL; + mError = ERR_CLOSED_BY_REMOTE_HOST; + mErrorMsg = i18n("Connection closed by remote host."); + } + else if( line.find(keyFingerprintMsg[mVersion]) != -1 ) { + mKeyFingerprint = keyFingerprintMsg[mVersion].cap(); + kdDebug(KSSHPROC) << "Found key fingerprint: " << mKeyFingerprint << endl; + mConnectState = STATE_NEW_KEY_WAIT_CONTINUE; + } + else { + // ignore line + } + break; + + + // STATE_NEW_KEY_CONTINUE: + // We got a continue prompt for the new key message. Set the error + // message to reflect this, return false and hope for caller response. + case STATE_NEW_KEY_CONTINUE: + mError = ERR_NEW_HOST_KEY; + mErrorMsg = i18n( + "The identity of the remote host '%1' could not be " + "verified. The host's key fingerprint is:\n%2\nYou should " + "verify the fingerprint with the host's administrator before " + "connecting.\n\n" + "Would you like to accept the host's key and connect anyway? " + ).arg(mHost).arg(mKeyFingerprint); + mConnectState = STATE_SEND_CONTINUE; + return false; + + // STATE_DIFF_KEY_WAIT_CONTINUE: + // Grab lines from ssh until we get a continue prompt or a auth + // denied. We will get the later if StrictHostKeyChecking is set + // to yes. Go to STATE_DIFF_KEY_CONTINUE if we get a continue prompt. + case STATE_DIFF_KEY_WAIT_CONTINUE: + line = getLine(); + if( line.isNull() ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + "Got null line in STATE_DIFF_KEY_WAIT_CONTINUE." << endl; + mError = ERR_INTERACT; + mErrorMsg = + i18n("Error encountered while talking to ssh."); + mConnectState = STATE_FATAL; + } + else if( (line.find(authFailedMsg[mVersion]) != -1 + && line.find(tryAgainMsg[mVersion]) == -1) + || line.find(hostKeyVerifyFailedMsg[mVersion]) != -1 ) { + mError = ERR_AUTH_FAILED_DIFF_KEY; + mErrorMsg = i18n( + "WARNING: The identity of the remote host '%1' has changed!\n\n" + "Someone could be eavesdropping on your connection, or the " + "administrator may have just changed the host's key. " + "Either way, you should verify the host's key fingerprint with the host's " + "administrator. The key fingerprint is:\n%2\n" + "Add the correct host key to \"%3\" to " + "get rid of this message." + ).arg(mHost).arg(mKeyFingerprint).arg(mKnownHostsFile); + mConnectState = STATE_FATAL; + } + else if( line.find(continuePrompt[mVersion]) != -1 ) { + mConnectState = STATE_DIFF_KEY_CONTINUE; + } + else if( line.find(keyFingerprintMsg[mVersion]) != -1 ) { + mKeyFingerprint = keyFingerprintMsg[mVersion].cap(); + kdDebug(KSSHPROC) << "Found key fingerprint: " << mKeyFingerprint << endl; + mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE; + } + else if( line.find(knownHostsFileMsg[mVersion]) != -1 ) { + mKnownHostsFile = (knownHostsFileMsg[mVersion]).cap(1); + kdDebug(KSSHPROC) << "Found known hosts file name: " << mKnownHostsFile << endl; + mConnectState = STATE_DIFF_KEY_WAIT_CONTINUE; + } + else { + // ignore line + } + break; + + // STATE_DIFF_KEY_CONTINUE: + // We got a continue prompt for the different key message. + // Set ERR_DIFF_HOST_KEY error + // and return false to signal need to caller action. + case STATE_DIFF_KEY_CONTINUE: + mError = ERR_DIFF_HOST_KEY; + mErrorMsg = i18n( + "WARNING: The identity of the remote host '%1' has changed!\n\n" + "Someone could be eavesdropping on your connection, or the " + "administrator may have just changed the host's key. " + "Either way, you should verify the host's key fingerprint with the host's " + "administrator before connecting. The key fingerprint is:\n%2\n\n" + "Would you like to accept the host's new key and connect anyway?" + ).arg(mHost).arg(mKeyFingerprint); + mConnectState = STATE_SEND_CONTINUE; + return false; + + // STATE_SEND_CONTINUE: + // We found a continue prompt. Send our answer. + case STATE_SEND_CONTINUE: + if( mAcceptHostKey ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + "host key accepted" << endl; + ssh.writeLine("yes"); + mConnectState = STATE_WAIT_PROMPT; + } + else { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + "host key rejected" << endl; + ssh.writeLine("no"); + mError = ERR_HOST_KEY_REJECTED; + mErrorMsg = i18n("Host key was rejected."); + mConnectState = STATE_FATAL; + } + break; + + // STATE_FATAL: + // Something bad happened that we cannot recover from. + // Kill the ssh process and set flags to show we have + // ended the connection and killed ssh. + // + // mError and mErrorMsg should be set by the immediately + // previous state. + case STATE_FATAL: + kill(); + mConnected = false; + mRunning = false; + mConnectState = STATE_START; + // mError, mErroMsg set by last state + return false; + + default: + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + "Invalid state number - " << mConnectState << endl; + mError = ERR_INVALID_STATE; + mConnectState = STATE_FATAL; + } + } + + // we should never get here + kdDebug(KSSHPROC) << "KSshProcess::connect(): " << + "After switch(). We shouldn't be here." << endl; + mError = ERR_INTERNAL; + return false; +} + +void KSshProcess::disconnect() { + kill(); + mConnected = false; + mRunning = false; + mConnectState = STATE_START; +} + diff --git a/kioslave/sftp/ksshprocess.h b/kioslave/sftp/ksshprocess.h new file mode 100644 index 000000000..2ec1abfd6 --- /dev/null +++ b/kioslave/sftp/ksshprocess.h @@ -0,0 +1,623 @@ +/*************************************************************************** + ksshprocess.h - description + ------------------- + begin : Tue Jul 31 2001 + copyright : (C) 2001 by Lucas Fisher + email : ljfisher@purdue.edu + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KSSHPROCESS_H +#define KSSHPROCESS_H + +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> + +#include <qvaluelist.h> + +#include <kdebug.h> + +#include "process.h" + +#define KSSHPROC 7120 + +/** + * Provides version independent access to ssh. Currently supported + * versions of SSH are: + * OpenSSH 2.9p1 + * OpenSSH 2.9p2 + * OpenSSH 3.0 + * OpenSSH 3.1 + * Commercial SSH 3.0.0 + * Other versions of OpenSSH and commerical SSH will probably work also. + * + * To setup a SSH connection first create a list of options to use and tell + * KSshProcess about your options. Then start the ssh connection. Once the + * connection is setup use the stdin, stdout, stderr, and pty file descriptors + * to communicate with ssh. For a detailed example of how to use, see + * ksshprocesstest.cpp. + * + * @author Lucas Fisher + * + * Example: Connect to ssh server on localhost + * KSshProcess::SshOpt opt; + * KSshProcess::SshOptList options; + * + * opt.opt = KSshProcess::SSH_HOST; + * opt.str = "localhost"; + * options.append(opt); + * + * opt.opt = KSshProcess::SSH_USERNAME; + * opt.str = "me"; + * options.append(opt); + * + * KSshProcess ssh; + * if( !ssh.setOptions(options) ) { + * int err = ssh.error(); + * // process error + * return false; + * } + * + * int err; + * QString errMsg; + * while( !ssh.connect() ) { + * err = ssh.error(errMsg); + * + * switch( err ) { + * case KSshProcess::ERR_NEW_HOST_KEY: + * case KSshProcess::ERR_DIFF_HOST_KEY: + * // ask user to accept key + * if( acceptHostKey ) { + * ssh.acceptKey(true); + * } + * break; + * + * case KSshProcess::ERR_NEED_PASSWORD: + * // ask user for password + * ssh.password(userPassword); + * break; + * + * case KSshProcess::ERR_NEED_KEY_PASSPHRASE: + * // ask user for their key passphrase + * ssh.keyPassphrase(keyPassphrase); + * break; + * + * default: + * // somethings wrong, alert user + * return; + * } + * } + * // We have an open ssh connection to localhost + * + */ + +class KSshProcess { +public: + /** + * SSH Option + * + * Stores SSH options for use with KSshProcess. + * + * SSH options are configured much like UDS entries. + * Each option is assigned a constant and a string, bool, + * or number is assigned based on the option. + * + * @author Lucas Fisher (ljfisher@iastate.edu) + */ + class SshOpt { + public: + Q_UINT32 opt; + QString str; + Q_INT32 num; + bool boolean; + }; + + /** + * List of SshOptions and associated iterators + */ + typedef QValueList<SshOpt> SshOptList; + typedef QValueListIterator<SshOpt> SshOptListIterator; + typedef QValueListConstIterator<SshOpt> SshOptListConstIterator; + + /** + * Ssh versions supported by KSshProcess. Subject to change + * at any time. + */ + enum SshVersion { + OPENSSH_3_6, + OPENSSH, + SSH, + SSH_VER_MAX, + UNKNOWN_VER + }; + + /** + * SSH options supported by KSshProcess. Set SshOpt::opt to one of these + * values. + */ + // we cannot do this like UDSAtomType (ORing the type with the name) because + // we have too many options for ssh and not enough bits. + enum SshOptType { + /** + * Request server to invoke subsystem. (str) + */ + SSH_SUBSYSTEM, + /** + * Connect to port on the server. (num) + */ + SSH_PORT, + /** + * Connect to host. (str) + */ + SSH_HOST, + /** + * connect using this username. (str) + */ + SSH_USERNAME, + /** + * connect using this password. (str) + */ + SSH_PASSWD, + /** + * connect using this version of the SSH protocol. num == 1 or 2 + */ + SSH_PROTOCOL, + /** + * whether to forward X11 connections. (boolean) + */ + SSH_FORWARDX11, + /** + * whether to do agent forwarding. (boolean) + */ + SSH_FORWARDAGENT, + /** + * use as escape character. 0 for none (num) + */ + SSH_ESCAPE_CHAR, + /** + * command for ssh to perform once it is connected (str) + */ + SSH_COMMAND, + /** + * Set ssh verbosity. This may be added multiple times. It may also cause KSSHProcess + * to fail since we don't understand all the debug messages. + */ + SSH_VERBOSE, + /** + * Set a ssh option as one would find in the ssh_config file + * The str member should be set to 'optName value' + */ + SSH_OPTION, + /** + * Set some other option not supported by KSSHProcess. The option should + * be specified in the str member of SshOpt. Careful with this since + * not all versions of SSH support the same options. + */ + SSH_OTHER, + SSH_OPT_MAX // always last + }; // that's all for now + + /** + * Errors that KSshProcess can encounter. When a member function returns + * false, call error() to retrieve one of these error codes. + */ + enum SshError { + /** + * Don't recognize the ssh version + */ + ERR_UNKNOWN_VERSION, + /** + * Cannot lauch ssh client + */ + ERR_CANNOT_LAUNCH, + /** + * Interaction with the ssh client failed. This happens when we can't + * find the password prompt or something similar + */ + ERR_INTERACT, + /** + * Arguments for both a remotely executed subsystem and command were provide. + * Only one or the other may be used + */ + ERR_CMD_SUBSYS_CONFLICT, + /** + * No password was supplied + */ + ERR_NEED_PASSWD, + /** + * No passphrase was supplied. + */ + ERR_NEED_PASSPHRASE, + /** + * No usename was supplied + */ + ERR_NEED_USERNAME, + /** + * Timed out waiting for a response from ssh or the server + */ + ERR_TIMED_OUT, + /** + * Internal error, probably from a system call + */ + ERR_INTERNAL, + /** + * ssh was disconnect from the host + */ + ERR_DISCONNECTED, + /** + * No ssh options have been set. Call setArgs() before calling connect. + */ + ERR_NO_OPTIONS, + /** + * A host key was received from an unknown host. + * Call connect() with the acceptHostKey argument to accept the key. + */ + ERR_NEW_HOST_KEY, + /** + * A host key different from what is stored in the user's known_hosts file + * has be received. This is an indication of an attack + */ + ERR_DIFF_HOST_KEY, + /** + * A new or different host key was rejected by the caller. The ssh + * connection was terminated and the ssh process killed. + */ + ERR_HOST_KEY_REJECTED, + /** + * An invalid option was found in the SSH option list + */ + ERR_INVALID_OPT, + /** + * SSH accepted host key without prompting user. + */ + ERR_ACCEPTED_KEY, + /** + * Authentication failed + */ + ERR_AUTH_FAILED, + /** + * Authentication failed because a new host key was detected and + * SSH is configured with strict host key checking enabled. + */ + ERR_AUTH_FAILED_NEW_KEY, + /** + * Authentication failed because a changed host key was detected and + * SSH is configured with strict host key checking enabled. + */ + ERR_AUTH_FAILED_DIFF_KEY, + /** + * The remote host closed the connection for unknown reasons. + */ + ERR_CLOSED_BY_REMOTE_HOST, + /** + * We have no idea what happened + */ + ERR_UNKNOWN, + /** + * The connect state machine entered an invalid state. + */ + ERR_INVALID_STATE, + ERR_MAX + }; + + /** + * Initialize a SSH process using the first SSH binary found in the PATH + */ + KSshProcess(); + + /** + * Initialize a SSH process using the specified SSH binary. + * @param pathToSsh The fully qualified path name of the ssh binary + * KSshProcess should use to setup a SSH connection. + */ + KSshProcess(QString pathToSsh); + ~KSshProcess(); + + /** + * Set the ssh binary KSshProcess should use. This will only affect the + * next ssh connection attempt using this instance. + * + * @param pathToSsh Full path to the ssh binary. + * + * @return True if the ssh binary is found and KSshProcess + * recognizes the version. + * + */ + bool setSshPath(QString pathToSsh); + + /** + * Get the ssh version. + * + * @return The ssh version or -1 if KSshProcess does not recognize + * the ssh version. The returned value corresponds to the + * member of the SshVersion enum. + */ + SshVersion version(); + + /** + * Get a string describing the ssh version + * + * @return A string describing the ssh version recognized by KSshProcess + */ + //QString versionStr(); + + /** + * Get the last error encountered by KSshProcess. + * + * @param msg Set to the error message, if any, outputted by ssh when it is run. + * + * @return The error number. See SshError for descriptions. + */ + int error(QString& msg); + + /** + * Get the last error encountered by KSshProcess. + * @return The error number. See SshError for descriptions. + */ + int error() { return mError; } + + QString errorMsg() { return mErrorMsg; } + + /** + * Send a signal to the ssh process. Do not use this to end the + * ssh connection as it will not correctly reset the internal + * state of the KSshProcess object. Use KSshProcess::disconnect() + * instead. + * + * @param signal The signal to send to the ssh process. See 'kill -l' + * for a list of possible signals. + * The default signal is SIGKILL which kills ssh. + * + */ + void kill(int signal = SIGKILL); + + /** + * The pid of the ssh process started by this instance of KSshProcess. + * Only valid if KSshProcess::running() returns true; + * + * @return The pid of the running ssh process. + */ + int pid() { return ssh.pid(); } + + /** + * Whether a ssh connection has been established with a + * remote host. A establish connection means ssh has successfully + * authenticated with the remote host and user data can be transfered + * between the local and remote host. This cannot return + * true unless the most recent call to KSshProccess::connect() returned true. + * + * @return True if a ssh connection has been established with a remote + * host. False otherwise. + */ + bool connected() { return mConnected; } + + /** + * Whether a ssh process is currently running. This only indicates + * if a ssh process has been started and is still running. It does not + * tell if authentication has been successful. This may return true + * even if the most recent call to KSshProcess::connect() returned false. + * + * @return True if a ssh process started by this instance of KSshProcess + * is running. False otherwise. + */ + bool running() { return mRunning; } + + /** + * Print the command line arguments ssh is run with using kdDebug. + */ + void printArgs(); + + /** + * Set the SSH options. + * This must be called before connect(). See SshOptType for a list of + * supported ssh options. The required options are SSH_USERNAME + * and SSH_HOST. + * + * To reset the saved options, just recall setOptions() again with + * a different options list. + * + * @param opts A list of SshOpt objects specifying the ssh options. + * + * @return True if all options are valid. False if unrecognized options + * or a required option is missing. Call error() + * for details. + * + */ + bool setOptions(const SshOptList& opts); + + /** + * Create a ssh connection based on the options provided by setOptions(). + * Sets one of the following error codes on failure: + * <ul> + * <li>ERR_NO_OPTIONS</li> + * <li>ERR_CANNOT_LAUNCH</li> + * <li>ERR_INVALID_STATE</li> + * <li>ERR_NEED_PASSWD</li> + * <li>ERR_AUTH_FAILED</li> + * <li>ERR_NEW_HOST_KEY</li> + * <li>ERR_KEY_ACCEPTED</li> + * <li>ERR_DIFF_HOST_KEY</li> + * <li>ERR_INTERNAL</li> + * <li>ERR_INTERACT</li> + * </ul> + * + * @param acceptHostKey When true KSshProcess will automatically accept + * unrecognized or changed host keys. + * + * @return True if the ssh connection is successful. False if the connection + * fails. Call error() to get the reason for the failure. + */ + bool connect(); + + + /** + * Disconnect ssh from the host. This kills the ssh process and + * resets the internal state of this KSshProcess object. After a + * disconnect, the same KSshProcess can be used to connect to a + * host. + */ + void disconnect(); + + /** + * Call to respond to a ERR_NEW_HOST_KEY or ERR_DIFF_HOST_KEY error. + * + * @param accept True to accept the host key, false to not accept the + * host key and kill ssh. + * + */ + void acceptHostKey(bool accept); + + /** + * Call to respond to a ERR_NEED_PASSWD or ERR_NEED_PASSPHRASE error. + * + * @param password The user password to give ssh. + */ + void setPassword(QString password); + + /** + * Access to standard in and out of the ssh process. + * + * @return The file description for stdin and stdout of the ssh process. + */ + int stdioFd() { return ssh.stdioFd(); } + + /** + * Access to standard error of the ssh process. + * + * @return The file descriptior for stderr of the ssh process. + */ + int stderrFd() { return ssh.stderrFd(); } + + /** + * Access the pty to which the ssh process is attached. + * + * @return The file descriptor of pty to which ssh is attached. + */ + int pty() { return ssh.fd(); } +private: + /** + * Path the the ssh binary. + */ + QString mSshPath; + + /** + * SSH version. This is an index into the supported SSH + * versions array, and the various messages arrays. + */ + SshVersion mVersion; + + /** + * User's password. Zero this out when it is no longer needed. + */ + QString mPassword; + + /** + * User's username. + */ + QString mUsername; + + /** + * Name of host we are connecting to. + */ + QString mHost; + + /** + * Accept new or changed host keys if true. + */ + bool mAcceptHostKey; + + /** + * Flag to tell use if we have an open, authenticated ssh + * session going. + */ + bool mConnected; + + /** + * Flag to tell us if we have started a ssh process, we use this + * to make sure we kill ssh before going away. + */ + bool mRunning; + + /** + * Save any key fingerprint msg from ssh so we can present + * it to the caller. + */ + QString mKeyFingerprint; + + /** + * The location of the known host key file. We grab this from + * any error messages ssh prints out. + */ + QString mKnownHostsFile; + + /** + * The state of our connect state machine. + */ + int mConnectState; + + /** + * Port on on which the target ssh server is listening. + */ + int mPort; + + /** + * The last error number encountered. This is only valid for the + * last error. + */ + SshError mError; + + /** + * An error message that corresponds to the error number set in + * mError. Optional. + */ + QString mErrorMsg; + + /** + * Interface to the SSH process we ceate. Handles communication + * to and from the SSH process using stdin, stdout, stderr, and + * pty. + */ + MyPtyProcess ssh; + + /** + * List of arguments we start SSH with. + */ + QCStringList mArgs; + void init(); + + /** + * Handler to clean up when ssh process terminates. + */ + static void SIGCHLD_handler(int signo); + void installSignalHandlers(); + void removeSignalHandlers(); + + QString getLine(); + + static QRegExp versionStrs[]; + static const char * const passwordPrompt[]; + static const char * const passphrasePrompt[]; + static const char * const authSuccessMsg[]; + static const char * const authFailedMsg[]; + static QRegExp hostKeyMissingMsg[]; + static const char * const hostKeyChangedMsg[]; + static const char * const continuePrompt[]; + static const char * const hostKeyAcceptedMsg[]; + static const char * const tryAgainMsg[]; + static QRegExp hostKeyVerifyFailedMsg[]; + static const char * const connectionClosedMsg[]; + static const char * const changeHostKeyOnDiskPrompt[]; + static QRegExp keyFingerprintMsg[]; + static QRegExp knownHostsFileMsg[]; +}; +#endif diff --git a/kioslave/sftp/ksshprocesstest.cpp b/kioslave/sftp/ksshprocesstest.cpp new file mode 100644 index 000000000..3a37be02c --- /dev/null +++ b/kioslave/sftp/ksshprocesstest.cpp @@ -0,0 +1,98 @@ +#include "ksshprocess.h" +#include <iostream> + +using namespace std; + +int main(int argc, char *argv[]) { + + if( argc < 5 ) { + cout << "Usage: " << argv[0] << + " <ssh path> <host> <username> <password>" << endl; + return 1; + } + + KSshProcess ssh(argv[1]); + cout << ssh.version() << endl; + + KSshProcess::SshOptList opts; + KSshProcess::SshOpt opt; + + opt.opt = KSshProcess::SSH_PORT; + opt.num = 22; + opts.append(opt); + + opt.opt = KSshProcess::SSH_HOST; + opt.str = QString(argv[2]); + opts.append(opt); + + opt.opt = KSshProcess::SSH_USERNAME; + opt.str = QString(argv[3]); + opts.append(opt); + +// opt.opt = KSshProcess::SSH_PASSWD; +// opt.str = QString(argv[4]); +// opts.append(opt); + + if( !ssh.setOptions(opts) ) { + cout << "ksshprocesstest: setOptions failed" << endl; + return -1; + } + + ssh.printArgs(); + + bool stop = false; + bool connected; + char buf[256]; + char c; + while( !stop && !(connected = ssh.connect()) ) { + cout << "ksshprocesstest: Error num - " << ssh.error() << endl; + cout << "ksshprocesstest: Error msg - " << ssh.errorMsg().latin1() << endl; + switch( ssh.error() ) { + case KSshProcess::ERR_NEED_PASSWD: + case KSshProcess::ERR_NEED_PASSPHRASE: + cout << "Password: "; + cin >> buf; + cout << "password is " << buf << endl; + ssh.setPassword(QString(buf)); + break; + case KSshProcess::ERR_NEW_HOST_KEY: + case KSshProcess::ERR_DIFF_HOST_KEY: + cout << "Accept host key? (y/n): "; + cin >> c; + cout << "Answered " << c << endl; + ssh.acceptHostKey(c == 'y' ? true : false); + break; + case KSshProcess::ERR_AUTH_FAILED: + cout << "ksshprocesstest: auth failed." << endl; + stop = true; + break; + case KSshProcess::ERR_AUTH_FAILED_NEW_KEY: + cout << "ksshprocesstest: auth failed because of new key." << endl; + stop = true; + break; + case KSshProcess::ERR_AUTH_FAILED_DIFF_KEY: + cout << "ksshprocesstest: auth failed because of changed key." << endl; + stop = true; + break; + + case KSshProcess::ERR_INTERACT: + case KSshProcess::ERR_INTERNAL: + case KSshProcess::ERR_UNKNOWN: + case KSshProcess::ERR_INVALID_STATE: + case KSshProcess::ERR_CANNOT_LAUNCH: + case KSshProcess::ERR_HOST_KEY_REJECTED: + cout << "ksshprocesstest: FATAL ERROR" << endl; + stop = true; + break; + + } + } + + if( connected ) { + cout << "ksshprocesstest: Successfully connected to " << argv[2] << endl; + } + else { + cout << "ksshprocesstest: Connect to " << argv[2] << " failed." << endl; + } + +} diff --git a/kioslave/sftp/process.cpp b/kioslave/sftp/process.cpp new file mode 100644 index 000000000..fcf012514 --- /dev/null +++ b/kioslave/sftp/process.cpp @@ -0,0 +1,493 @@ +/* vi: ts=8 sts=4 sw=4 + * + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This file contains code from TEShell.C of the KDE konsole. + * Copyright (c) 1997,1998 by Lars Doelle <lars.doelle@on-line.de> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + * + * process.cpp: Functionality to build a front end to password asking + * terminal programs. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <termios.h> +#include <signal.h> + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/socket.h> + +#if defined(__SVR4) && defined(sun) +#include <stropts.h> +#include <sys/stream.h> +#endif + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> // Needed on some systems. +#endif + +#include <qglobal.h> +#include <qcstring.h> +#include <qfile.h> + +#include <kdebug.h> +#include <kstandarddirs.h> + +#include "process.h" +#include <kdesu/kdesu_pty.h> +#include <kdesu/kcookie.h> + + +MyPtyProcess::MyPtyProcess() +{ + m_bTerminal = false; + m_bErase = false; + m_pPTY = 0L; + m_Pid = -1; + m_Fd = -1; +} + + +int MyPtyProcess::init() +{ + delete m_pPTY; + m_pPTY = new PTY(); + m_Fd = m_pPTY->getpt(); + if (m_Fd < 0) + return -1; + if ((m_pPTY->grantpt() < 0) || (m_pPTY->unlockpt() < 0)) + { + kdError(PTYPROC) << k_lineinfo << "Master setup failed.\n" << endl; + m_Fd = -1; + return -1; + } + m_TTY = m_pPTY->ptsname(); + m_stdoutBuf.resize(0); + m_stderrBuf.resize(0); + m_ptyBuf.resize(0); + return 0; +} + + +MyPtyProcess::~MyPtyProcess() +{ + delete m_pPTY; +} + + +/* + * Read one line of input. The terminal is in canonical mode, so you always + * read a line at at time, but it's possible to receive multiple lines in + * one time. + */ + + +QCString MyPtyProcess::readLineFrom(int fd, QCString& inbuf, bool block) +{ + int pos; + QCString ret; + + if (!inbuf.isEmpty()) + { + + pos = inbuf.find('\n'); + + if (pos == -1) + { + ret = inbuf; + inbuf.resize(0); + } else + { + ret = inbuf.left(pos); + inbuf = inbuf.mid(pos+1); + } + return ret; + + } + + int flags = fcntl(fd, F_GETFL); + if (flags < 0) + { + kdError(PTYPROC) << k_lineinfo << "fcntl(F_GETFL): " << perror << "\n"; + return ret; + } + if (block) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) + { + kdError(PTYPROC) << k_lineinfo << "fcntl(F_SETFL): " << perror << "\n"; + return ret; + } + + int nbytes; + char buf[256]; + while (1) + { + nbytes = read(fd, buf, 255); + if (nbytes == -1) + { + if (errno == EINTR) + continue; + else break; + } + if (nbytes == 0) + break; // eof + + buf[nbytes] = '\000'; + inbuf += buf; + + pos = inbuf.find('\n'); + if (pos == -1) + { + ret = inbuf; + inbuf.resize(0); + } else + { + ret = inbuf.left(pos); + inbuf = inbuf.mid(pos+1); + } + break; + + } + + return ret; +} + +void MyPtyProcess::writeLine(QCString line, bool addnl) +{ + if (!line.isEmpty()) + write(m_Fd, line, line.length()); + if (addnl) + write(m_Fd, "\n", 1); +} + +void MyPtyProcess::unreadLineFrom(QCString inbuf, QCString line, bool addnl) +{ + if (addnl) + line += '\n'; + if (!line.isEmpty()) + inbuf.prepend(line); +} + + +/* + * Fork and execute the command. This returns in the parent. + */ + +int MyPtyProcess::exec(QCString command, QCStringList args) +{ + kdDebug(PTYPROC) << "MyPtyProcess::exec(): " << command << endl;// << ", args = " << args << endl; + + if (init() < 0) + return -1; + + // Open the pty slave before forking. See SetupTTY() + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(PTYPROC) << k_lineinfo << "Could not open slave pty.\n"; + return -1; + } + + // Also create a socket pair to connect to standard in/out. + // This will allow use to bypass the terminal. + int inout[2]; + int err[2]; + int ok = 1; + ok &= socketpair(AF_UNIX, SOCK_STREAM, 0, inout) >= 0; + ok &= socketpair(AF_UNIX, SOCK_STREAM, 0, err ) >= 0; + if( !ok ) { + kdDebug(PTYPROC) << "Could not create socket" << endl; + return -1; + } + m_stdinout = inout[0]; + m_err = err[0]; + + if ((m_Pid = fork()) == -1) + { + kdError(PTYPROC) << k_lineinfo << "fork(): " << perror << "\n"; + return -1; + } + + // Parent + if (m_Pid) + { + close(slave); + close(inout[1]); + close(err[1]); + return 0; + } + + // Child + + ok = 1; + ok &= dup2(inout[1], STDIN_FILENO) >= 0; + ok &= dup2(inout[1], STDOUT_FILENO) >= 0; + ok &= dup2(err[1], STDERR_FILENO) >= 0; + + if( !ok ) + { + kdError(PTYPROC) << "dup of socket descriptor failed" << endl; + _exit(1); + } + + close(inout[1]); + close(inout[0]); + close(err[1]); + close(err[0]); + + if (SetupTTY(slave) < 0) + _exit(1); + + // From now on, terminal output goes through the tty. + QCString path; + if (command.contains('/')) + path = command; + else + { + QString file = KStandardDirs::findExe(command); + if (file.isEmpty()) + { + kdError(PTYPROC) << k_lineinfo << command << " not found\n"; + _exit(1); + } + path = QFile::encodeName(file); + } + + int i; + const char * argp[32]; + argp[0] = path; + QCStringList::Iterator it; + for (i=1, it=args.begin(); it!=args.end() && i<31; it++) { + argp[i++] = *it; + kdDebug(PTYPROC) << *it << endl; + } + argp[i] = 0L; + execv(path, (char * const *)argp); + kdError(PTYPROC) << k_lineinfo << "execv(\"" << path << "\"): " << perror << "\n"; + _exit(1); + return -1; // Shut up compiler. Never reached. +} + +/* + * Wait until the terminal is set into no echo mode. At least one su + * (RH6 w/ Linux-PAM patches) sets noecho mode AFTER writing the Password: + * prompt, using TCSAFLUSH. This flushes the terminal I/O queues, possibly + * taking the password with it. So we wait until no echo mode is set + * before writing the password. + * Note that this is done on the slave fd. While Linux allows tcgetattr() on + * the master side, Solaris doesn't. + */ + +int MyPtyProcess::WaitSlave() +{ + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(PTYPROC) << k_lineinfo << "Could not open slave tty.\n"; + return -1; + } + + struct termios tio; + struct timeval tv; + while (1) + { + if (tcgetattr(slave, &tio) < 0) + { + kdError(PTYPROC) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + close(slave); + return -1; + } + if (tio.c_lflag & ECHO) + { + kdDebug(PTYPROC) << k_lineinfo << "Echo mode still on." << endl; + // sleep 1/10 sec + tv.tv_sec = 0; tv.tv_usec = 100000; + select(slave, 0L, 0L, 0L, &tv); + continue; + } + break; + } + close(slave); + return 0; +} + + +int MyPtyProcess::enableLocalEcho(bool enable) +{ + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(PTYPROC) << k_lineinfo << "Could not open slave tty.\n"; + return -1; + } + struct termios tio; + if (tcgetattr(slave, &tio) < 0) + { + kdError(PTYPROC) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + close(slave); return -1; + } + if (enable) + tio.c_lflag |= ECHO; + else + tio.c_lflag &= ~ECHO; + if (tcsetattr(slave, TCSANOW, &tio) < 0) + { + kdError(PTYPROC) << k_lineinfo << "tcsetattr(): " << perror << "\n"; + close(slave); return -1; + } + close(slave); + return 0; +} + + +/* + * Copy output to stdout until the child process exists, or a line of output + * matches `m_Exit'. + * We have to use waitpid() to test for exit. Merely waiting for EOF on the + * pty does not work, because the target process may have children still + * attached to the terminal. + */ + +int MyPtyProcess::waitForChild() +{ + int ret, state, retval = 1; + struct timeval tv; + + fd_set fds; + FD_ZERO(&fds); + + while (1) + { + tv.tv_sec = 1; tv.tv_usec = 0; + FD_SET(m_Fd, &fds); + ret = select(m_Fd+1, &fds, 0L, 0L, &tv); + if (ret == -1) + { + if (errno == EINTR) continue; + else + { + kdError(PTYPROC) << k_lineinfo << "select(): " << perror << "\n"; + return -1; + } + } + + if (ret) + { + QCString line = readLine(false); + while (!line.isNull()) + { + if (!m_Exit.isEmpty() && !qstrnicmp(line, m_Exit, m_Exit.length())) + kill(m_Pid, SIGTERM); + if (m_bTerminal) + { + fputs(line, stdout); + fputc('\n', stdout); + } + line = readLine(false); + } + } + + // Check if the process is still alive + ret = waitpid(m_Pid, &state, WNOHANG); + if (ret < 0) + { + if (errno == ECHILD) + retval = 0; + else + kdError(PTYPROC) << k_lineinfo << "waitpid(): " << perror << "\n"; + break; + } + if (ret == m_Pid) + { + if (WIFEXITED(state)) + retval = WEXITSTATUS(state); + break; + } + } + + return -retval; +} + +/* + * SetupTTY: Creates a new session. The filedescriptor "fd" should be + * connected to the tty. It is closed after the tty is reopened to make it + * our controlling terminal. This way the tty is always opened at least once + * so we'll never get EIO when reading from it. + */ + +int MyPtyProcess::SetupTTY(int fd) +{ + // Reset signal handlers + for (int sig = 1; sig < NSIG; sig++) + signal(sig, SIG_DFL); + signal(SIGHUP, SIG_IGN); + + // Close all file handles +// struct rlimit rlp; +// getrlimit(RLIMIT_NOFILE, &rlp); +// for (int i = 0; i < (int)rlp.rlim_cur; i++) +// if (i != fd) close(i); + + // Create a new session. + setsid(); + + // Open slave. This will make it our controlling terminal + int slave = open(m_TTY, O_RDWR); + if (slave < 0) + { + kdError(PTYPROC) << k_lineinfo << "Could not open slave side: " << perror << "\n"; + return -1; + } + close(fd); + +#if defined(__SVR4) && defined(sun) + + // Solaris STREAMS environment. + // Push these modules to make the stream look like a terminal. + ioctl(slave, I_PUSH, "ptem"); + ioctl(slave, I_PUSH, "ldterm"); + +#endif + + // Connect stdin, stdout and stderr +// dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); +// if (slave > 2) +// close(slave); + + // Disable OPOST processing. Otherwise, '\n' are (on Linux at least) + // translated to '\r\n'. + struct termios tio; + if (tcgetattr(slave, &tio) < 0) + { + kdError(PTYPROC) << k_lineinfo << "tcgetattr(): " << perror << "\n"; + return -1; + } + tio.c_oflag &= ~OPOST; + if (tcsetattr(slave, TCSANOW, &tio) < 0) + { + kdError(PTYPROC) << k_lineinfo << "tcsetattr(): " << perror << "\n"; + return -1; + } + + return 0; +} diff --git a/kioslave/sftp/process.h b/kioslave/sftp/process.h new file mode 100644 index 000000000..64dcfb973 --- /dev/null +++ b/kioslave/sftp/process.h @@ -0,0 +1,148 @@ +/* vi: ts=8 sts=4 sw=4 + * + * + * This file is part of the KDE project, module kdesu. + * Copyright (C) 1999,2000 Geert Jansen <jansen@kde.org> + * + * This is free software; you can use this library under the GNU Library + * General Public License, version 2. See the file "COPYING.LIB" for the + * exact licensing terms. + */ + +#ifndef __Process_h_Included__ +#define __Process_h_Included__ + +#include <qcstring.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qvaluelist.h> + +#define PTYPROC 7120 + +class PTY; +typedef QValueList<QCString> QCStringList; + +/** + * Synchronous communication with tty programs. + * + * PtyProcess provides synchronous communication with tty based programs. + * The communications channel used is a pseudo tty (as opposed to a pipe) + * This means that programs which require a terminal will work. + */ + +class MyPtyProcess +{ +public: + MyPtyProcess(); + virtual ~MyPtyProcess(); + + /** + * Fork off and execute a command. The command's standard in and output + * are connected to the pseudo tty. They are accessible with @ref #readLine + * and @ref #writeLine. + * @param command The command to execute. + * @param args The arguments to the command. + */ + int exec(QCString command, QCStringList args); + + /** + * Read a line from the program's standard out. Depending on the @em block + * parameter, this call blocks until a single, full line is read. + * @param block Block until a full line is read? + * @return The output string. + */ + QCString readLine(bool block = true) + { return readLineFrom(m_Fd, m_ptyBuf, block); } + + QCString readLineFromPty(bool block = true) + { return readLineFrom(m_Fd, m_ptyBuf, block); } + + QCString readLineFromStdout(bool block = true) + { return readLineFrom(m_stdinout, m_stdoutBuf, block); } + + QCString readLineFromStderr(bool block = true) + { return readLineFrom(m_err, m_stderrBuf, block); } + + /** + * Write a line of text to the program's standard in. + * @param line The text to write. + * @param addNewline Adds a '\n' to the line. + */ + void writeLine(QCString line, bool addNewline=true); + + /** + * Put back a line of input. + * @param line The line to put back. + * @param addNewline Adds a '\n' to the line. + */ + + void unreadLine(QCString line, bool addNewline = true) + { unreadLineFrom(m_ptyBuf, line, addNewline); } + + void unreadLineFromPty(QCString line, bool addNewline = true) + { unreadLineFrom(m_ptyBuf, line, addNewline); } + + void unreadLineFromStderr(QCString line, bool addNewline = true) + { unreadLineFrom(m_stderrBuf, line, addNewline); } + + void unreadLineFromStdout(QCString line, bool addNewline = true) + { unreadLineFrom(m_stdoutBuf, line, addNewline); } + + /** + * Set exit string. If a line of program output matches this, + * @ref #waitForChild() will terminate the program and return. + */ + void setExitString(QCString exit) { m_Exit = exit; } + + /** + * Wait for the child to exit. See also @ref #setExitString. + */ + int waitForChild(); + + /** + * Wait until the pty has cleared the ECHO flag. This is useful + * when programs write a password prompt before they disable ECHO. + * Disabling it might flush any input that was written. + */ + int WaitSlave(); + + /** Enables/disables local echo on the pseudo tty. */ + int enableLocalEcho(bool enable=true); + + /** Enable/disable terminal output. Relevant only to some subclasses. */ + void setTerminal(bool terminal) { m_bTerminal = terminal; } + + /** Overwritte the password as soon as it is used. Relevant only to + * some subclasses. */ + void setErase(bool erase) { m_bErase = erase; } + + /** Return the filedescriptor of the process. */ + int fd() {return m_Fd;} + + /** Return the pid of the process. */ + int pid() {return m_Pid;} + + int stdioFd() {return m_stdinout;} + + int stderrFd() {return m_err;} + +protected: + bool m_bErase, m_bTerminal; + int m_Pid, m_Fd, m_stdinout, m_err; + QCString m_Command, m_Exit; + +private: + int init(); + int SetupTTY(int fd); + + PTY *m_pPTY; + QCString m_TTY; + QCString m_ptyBuf, m_stderrBuf, m_stdoutBuf; + + QCString readLineFrom(int fd, QCString& inbuf, bool block); + void unreadLineFrom(QCString inbuf, QCString line, bool addnl); + class PtyProcessPrivate; + PtyProcessPrivate *d; +}; + +#endif diff --git a/kioslave/sftp/sftp.h b/kioslave/sftp/sftp.h new file mode 100644 index 000000000..95518130d --- /dev/null +++ b/kioslave/sftp/sftp.h @@ -0,0 +1,91 @@ +/* $OpenBSD: sftp.h,v 1.3 2001/03/07 10:11:23 djm Exp $ */ + +/* + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * draft-ietf-secsh-filexfer-01.txt + */ + +/* version */ +#define SSH2_FILEXFER_VERSION 3 + +/* client to server */ +#define SSH2_FXP_INIT 1 +#define SSH2_FXP_OPEN 3 +#define SSH2_FXP_CLOSE 4 +#define SSH2_FXP_READ 5 +#define SSH2_FXP_WRITE 6 +#define SSH2_FXP_LSTAT 7 +#define SSH2_FXP_FSTAT 8 +#define SSH2_FXP_SETSTAT 9 +#define SSH2_FXP_FSETSTAT 10 +#define SSH2_FXP_OPENDIR 11 +#define SSH2_FXP_READDIR 12 +#define SSH2_FXP_REMOVE 13 +#define SSH2_FXP_MKDIR 14 +#define SSH2_FXP_RMDIR 15 +#define SSH2_FXP_REALPATH 16 +#define SSH2_FXP_STAT 17 +#define SSH2_FXP_RENAME 18 +#define SSH2_FXP_READLINK 19 +#define SSH2_FXP_SYMLINK 20 + +/* server to client */ +#define SSH2_FXP_VERSION 2 +#define SSH2_FXP_STATUS 101 +#define SSH2_FXP_HANDLE 102 +#define SSH2_FXP_DATA 103 +#define SSH2_FXP_NAME 104 +#define SSH2_FXP_ATTRS 105 + +#define SSH2_FXP_EXTENDED 200 +#define SSH2_FXP_EXTENDED_REPLY 201 + +/* attributes */ +#define SSH2_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH2_FILEXFER_ATTR_UIDGID 0x00000002 +#define SSH2_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH2_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH2_FILEXFER_ATTR_EXTENDED 0x80000000 + +/* portable open modes */ +#define SSH2_FXF_READ 0x00000001 +#define SSH2_FXF_WRITE 0x00000002 +#define SSH2_FXF_APPEND 0x00000004 +#define SSH2_FXF_CREAT 0x00000008 +#define SSH2_FXF_TRUNC 0x00000010 +#define SSH2_FXF_EXCL 0x00000020 + +/* status messages */ +#define SSH2_FX_OK 0 +#define SSH2_FX_EOF 1 +#define SSH2_FX_NO_SUCH_FILE 2 +#define SSH2_FX_PERMISSION_DENIED 3 +#define SSH2_FX_FAILURE 4 +#define SSH2_FX_BAD_MESSAGE 5 +#define SSH2_FX_NO_CONNECTION 6 +#define SSH2_FX_CONNECTION_LOST 7 +#define SSH2_FX_OP_UNSUPPORTED 8 +#define SSH2_FX_MAX 8 diff --git a/kioslave/sftp/sftp.protocol b/kioslave/sftp/sftp.protocol new file mode 100644 index 000000000..177cd86e9 --- /dev/null +++ b/kioslave/sftp/sftp.protocol @@ -0,0 +1,84 @@ +[Protocol] +exec=kio_sftp +protocol=sftp +input=none +listing=Name,Type,Size,Date,Access,Owner,Group,Link +output=filesystem +copyToFile=true +copyFromFile=true +reading=true +writing=true +makedir=true +deleting=true +moving=true +Icon=ftp +Description=A kioslave for sftp +Description[af]='n Kioslave vir sftp +Description[be]=Kioslave Ð´Ð»Ñ sftp +Description[bg]=kioslave за sftp +Description[bn]=à¦à¦¸.à¦à¦«.টি.পি-র জনà§à¦¯ à¦à¦•à¦Ÿà¦¿ kioslave +Description[br]=Ur kioslave evit sftp +Description[bs]=kioslave za SFTP +Description[ca]=Un kioslave per a sftp +Description[cs]=Protokol KDE pro sftp +Description[csb]=Plugins dlô procedurë òbsÅ‚użënkù sftp +Description[da]=En kioslave for sftp +Description[de]=Ein-/Ausgabemodul für das sftp-Protokoll +Description[el]=Ένας kioslave για sftp +Description[eo]=kenelsklavo por sftp +Description[es]=Un kioslave para sftp +Description[et]=SFTP IO-moodul +Description[eu]=Sftp-rako kioslave bat +Description[fa]=یک kioslave برای sftp +Description[fi]=Sftp:n liitin +Description[fr]=Un module d'entrées / sorties pour le sftp +Description[fy]=Een kioslave Foar sftp +Description[ga]=kioslave le haghaidh sftp +Description[gl]=Un kioslave para sftp +Description[he]=ממשק kioslave עבור sftp +Description[hi]=à¤à¤¸à¤à¤«à¤Ÿà¥€à¤ªà¥€ के लिठकेआईओसà¥à¤²à¥‡à¤µ +Description[hr]=Kioslave za SFTP +Description[hu]=KDE-protokoll az sftp-hez +Description[is]=kioslave fyrir sftp +Description[it]=Un kioslave per sftp +Description[ja]=sftp ã®ãŸã‚ã® kioslave +Description[kk]=sftp-ке арналған енгізу-шығару модулі +Description[km]=kioslave របស់ sftp +Description[ko]=SFTP KIO ìŠ¬ë ˆì´ë¸Œ +Description[lt]=Kiovergas sftp protokolui +Description[lv]=KIO vergs priekÅ¡ sftp +Description[mk]=kio-Ñлужител за sftp +Description[ms]=Kioslave untuk sftp +Description[nb]=En kioslave for sftp +Description[nds]=En In-/Utgaavdeenst för sftpl +Description[ne]=sftp का लागि à¤à¤‰à¤Ÿà¤¾ किओसà¥à¤²à¥‡à¤ +Description[nl]=Een kioslave voor sftp +Description[nn]=Ein kioslave for sftp +Description[pa]=sftp ਲਈ kioslave +Description[pl]=Wtyczka protokoÅ‚u sftp +Description[pt]=Um 'kioslave' para sftp +Description[pt_BR]=Uma implementação para o sftp +Description[ro]=Un dispozitiv de I/E pentru SFTP +Description[ru]=Модуль ввода-вывода Ð´Ð»Ñ sftp +Description[rw]=kioslave ya sftp +Description[se]=Å O-Å¡láva sftp-protokolla várás +Description[sk]=kioslave pre sftp +Description[sl]=kioslave za sftp +Description[sr]=Kioslave за SFTP +Description[sr@Latn]=Kioslave za SFTP +Description[sv]=En I/O-slav för SFTP +Description[ta]=sftpகà¯à®•à®¾à®© ஒர௠கà¯à®¯à¯‹à®¸à¯à®²à¯‡à®µà¯ +Description[te]=ఎసౠఎఫౠటి పి కొరకౠà°à°’ బానిస +Description[th]=ตัวนำข้à¸à¸¡à¸¹à¸¥à¹€à¸‚้า-à¸à¸à¸à¸ªà¸³à¸«à¸£à¸±à¸š sftp +Description[tr]=Sftp için kioslave +Description[tt]=sftp öçen kioslave +Description[uk]=Підлеглий Ð’/Ð’ Ð´Ð»Ñ sftp +Description[uz]=SFTP uchun KCH-sleyv +Description[uz@cyrillic]=SFTP учун КЧ-Ñлейв +Description[vi]=A kioslave (Ä‘Ã y tá»› và o ra KDE) cho sftp +Description[wa]=On kioslave po sftp +Description[zh_CN]=sftp çš„ kioslave +Description[zh_TW]=sftp çš„ kioslave +DocPath=kioslave/sftp.html +Icon=ftp +Class=:internet diff --git a/kioslave/sftp/sftpfileattr.cpp b/kioslave/sftp/sftpfileattr.cpp new file mode 100644 index 000000000..07aada36d --- /dev/null +++ b/kioslave/sftp/sftpfileattr.cpp @@ -0,0 +1,346 @@ +/*************************************************************************** + sftpfileattr.cpp - description + ------------------- + begin : Sat Jun 30 2001 + copyright : (C) 2001 by Lucas Fisher + email : ljfisher@iastate.edu + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "sftpfileattr.h" + +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <qstring.h> +#include <qdatastream.h> + +#include <kio/global.h> +#include <kremoteencoding.h> + +using namespace KIO; + +sftpFileAttr::sftpFileAttr(){ + clear(); + mDirAttrs = false; +} + +sftpFileAttr::sftpFileAttr(KRemoteEncoding* encoding){ + clear(); + mDirAttrs = false; + mEncoding = encoding; +} + + +/** Constructor to initialize the file attributes on declaration. */ +sftpFileAttr::sftpFileAttr(Q_ULLONG size, uid_t uid, gid_t gid, + mode_t permissions, time_t atime, + time_t mtime, Q_UINT32 extendedCount) { + clear(); + mDirAttrs = false; + mSize = size; + mUid = uid; + mGid = gid; + mAtime = atime; + mMtime = mtime; + mPermissions = permissions; + mExtendedCount = extendedCount; +} + +sftpFileAttr::~sftpFileAttr(){ +} + +/** Returns a UDSEntry describing the file. +The UDSEntry is generated from the sftp file attributes. */ +UDSEntry sftpFileAttr::entry() { + UDSEntry entry; + UDSAtom atom; + + atom.m_uds = UDS_NAME; + atom.m_str = mFilename; + entry.append(atom); + + if( mFlags & SSH2_FILEXFER_ATTR_SIZE ) { + atom.m_uds = UDS_SIZE; + atom.m_long = mSize; + entry.append(atom); + } + + if( mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) { + atom.m_uds = UDS_ACCESS_TIME; + atom.m_long = mAtime; + entry.append(atom); + + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = mMtime; + entry.append(atom); + } + + if( mFlags & SSH2_FILEXFER_ATTR_UIDGID ) { + if( mUserName.isEmpty() || mGroupName.isEmpty() ) + getUserGroupNames(); + + atom.m_uds = UDS_USER; + atom.m_str = mUserName; + entry.append(atom); + + atom.m_uds = UDS_GROUP; + atom.m_str = mGroupName; + entry.append(atom); + } + + if( mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) { + atom.m_uds = UDS_ACCESS; + atom.m_long = mPermissions; + entry.append(atom); + + mode_t type = fileType(); + + // Set the type if we know what it is + if( type != 0 ) { + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = (mLinkType ? mLinkType:type); + entry.append(atom); + } + + if( S_ISLNK(type) ) { + atom.m_uds = UDS_LINK_DEST; + atom.m_str = mLinkDestination; + entry.append(atom); + } + } + + return entry; +} + +/** Use to output the file attributes to a sftp packet */ +QDataStream& operator<< (QDataStream& s, const sftpFileAttr& fa) { + s << (Q_UINT32)fa.mFlags; + + if( fa.mFlags & SSH2_FILEXFER_ATTR_SIZE ) + { s << (Q_ULLONG)fa.mSize; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_UIDGID ) + { s << (Q_UINT32)fa.mUid << (Q_UINT32)fa.mGid; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) + { s << (Q_UINT32)fa.mPermissions; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) + { s << (Q_UINT32)fa.mAtime << (Q_UINT32)fa.mMtime; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) { + s << (Q_UINT32)fa.mExtendedCount; + // XXX: Write extensions to data stream here + // s.writeBytes(extendedtype).writeBytes(extendeddata); + } + return s; +} + + +/** Use to read a file attribute from a sftp packet */ +QDataStream& operator>> (QDataStream& s, sftpFileAttr& fa) { + + // XXX Add some error checking in here in case + // we get a bad sftp packet. + + fa.clear(); + + if( fa.mDirAttrs ) { + QCString fn; + s >> fn; + fn.truncate( fn.size() ); + + fa.mFilename = fa.mEncoding->decode( fn ); + + s >> fa.mLongname; + fa.mLongname.truncate( fa.mLongname.size() ); + //kdDebug() << ">>: ftpfileattr long filename (" << fa.mLongname.size() << ")= " << fa.mLongname << endl; + } + + s >> fa.mFlags; // get flags + + if( fa.mFlags & SSH2_FILEXFER_ATTR_SIZE ) { + Q_ULLONG fileSize; + s >> fileSize; + fa.setFileSize(fileSize); + } + + Q_UINT32 x; + + if( fa.mFlags & SSH2_FILEXFER_ATTR_UIDGID ) { + s >> x; fa.setUid(x); + s >> x; fa.setGid(x); + } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) { + s >> x; fa.setPermissions(x); + } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) { + s >> x; fa.setAtime(x); + s >> x; fa.setMtime(x); + } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) { + s >> x; fa.setExtendedCount(x); + // XXX: Read in extensions from data stream here + // s.readBytes(extendedtype).readBytes(extendeddata); + } + + fa.getUserGroupNames(); + return s; +} +/** Parse longname for the owner and group names. */ +void sftpFileAttr::getUserGroupNames(){ + // Get the name of the owner and group of the file from longname. + QString user, group; + if( mLongname.isEmpty() ) { + // do not have the user name so use the user id instead + user.setNum(mUid); + group.setNum(mGid); + } + else { + int field = 0; + int i = 0; + int l = mLongname.length(); + + QString longName = mEncoding->decode( mLongname ); + + kdDebug(7120) << "Decoded: " << longName << endl; + + // Find the beginning of the third field which contains the user name. + while( field != 2 ) { + if( longName[i].isSpace() ) { + field++; i++; + while( i < l && longName[i].isSpace() ) { i++; } + } + else { i++; } + } + // i is the index of the first character of the third field. + while( i < l && !longName[i].isSpace() ) { + user.append(longName[i]); + i++; + } + + // i is the first character of the space between fields 3 and 4 + // user contains the owner's user name + while( i < l && longName[i].isSpace() ) { + i++; + } + + // i is the first character of the fourth field + while( i < l && !longName[i].isSpace() ) { + group.append(longName[i]); + i++; + } + // group contains the name of the group. + } + + mUserName = user; + mGroupName = group; +} + +/** No descriptions */ +kdbgstream& operator<< (kdbgstream& s, sftpFileAttr& a) { + s << "Filename: " << a.mFilename + << ", Uid: " << a.mUid + << ", Gid: " << a.mGid + << ", Username: " << a.mUserName + << ", GroupName: " << a.mGroupName + << ", Permissions: " << a.mPermissions + << ", size: " << a.mSize + << ", atime: " << a.mAtime + << ", mtime: " << a.mMtime + << ", extended cnt: " << a.mExtendedCount; + + if (S_ISLNK(a.mLinkType)) { + s << ", Link Type: " << a.mLinkType; + s << ", Link Destination: " << a.mLinkDestination; + } + + return s; +} + +/** Make sure it builds with NDEBUG */ +kndbgstream& operator<< (kndbgstream& s, sftpFileAttr& ) { + return s; +} + +/** Clear all attributes and flags. */ +void sftpFileAttr::clear(){ + clearAtime(); + clearMtime(); + clearGid(); + clearUid(); + clearFileSize(); + clearPermissions(); + clearExtensions(); + mFilename = QString::null; + mGroupName = QString::null; + mUserName = QString::null; + mLinkDestination = QString::null; + mFlags = 0; + mLongname = "\0"; + mLinkType = 0; +} + +/** Return the size of the sftp attribute. */ +Q_UINT32 sftpFileAttr::size() const{ + Q_UINT32 size = 4; // for the attr flag + if( mFlags & SSH2_FILEXFER_ATTR_SIZE ) + size += 8; + + if( mFlags & SSH2_FILEXFER_ATTR_UIDGID ) + size += 8; + + if( mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) + size += 4; + + if( mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) + size += 8; + + if( mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) { + size += 4; + // add size of extensions + } + return size; +} + +/** Returns the file type as determined from the file permissions */ +mode_t sftpFileAttr::fileType() const{ + mode_t type = 0; + + if( S_ISLNK(mPermissions) ) + type |= S_IFLNK; + + if( S_ISREG(mPermissions) ) + type |= S_IFREG; + else if( S_ISDIR(mPermissions) ) + type |= S_IFDIR; + else if( S_ISCHR(mPermissions) ) + type |= S_IFCHR; + else if( S_ISBLK(mPermissions) ) + type |= S_IFBLK; + else if( S_ISFIFO(mPermissions) ) + type |= S_IFIFO; + else if( S_ISSOCK(mPermissions) ) + type |= S_IFSOCK; + + return type; +} + +void sftpFileAttr::setEncoding( KRemoteEncoding* encoding ) +{ + mEncoding = encoding; +} +// vim:ts=4:sw=4 diff --git a/kioslave/sftp/sftpfileattr.h b/kioslave/sftp/sftpfileattr.h new file mode 100644 index 000000000..28743504b --- /dev/null +++ b/kioslave/sftp/sftpfileattr.h @@ -0,0 +1,261 @@ +/*************************************************************************** + sftpfileattr.h - description + ------------------- + begin : Sat Jun 30 2001 + copyright : (C) 2001 by Lucas Fisher + email : ljfisher@iastate.edu + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef SFTPFILEATTR_H +#define SFTPFILEATTR_H + +#include <sys/types.h> + +#include <qglobal.h> +#include <qstring.h> +#include <qdatastream.h> + +#include <kio/global.h> +#include <kdebug.h> + +#include "sftp.h" + +/** + *@author Lucas Fisher + */ + +class KRemoteEncoding; + +class sftpFileAttr { + +private: // Private attributes + /** Name of file. */ + QString mFilename; + + /** Specifies which fields of the file attribute are available. */ + Q_UINT32 mFlags; + + /** Size of the file in bytes. Should be 64 bit safe. */ + Q_ULLONG mSize; + + /** User id of the owner of the file. */ + uid_t mUid; + + /** Group id of the group to which the file belongs. */ + gid_t mGid; + + /** POSIX permissions of the file. */ + mode_t mPermissions; + + /** Last access time of the file in seconds from Jan 1, 1970. */ + time_t mAtime; + + /** Last modification time of file in seconds since Jan. 1, 1970. */ + time_t mMtime; + + /** Number of file attribute extensions. + Not currently implemented */ + Q_UINT32 mExtendedCount; + + /** Longname of the file as found in a SSH_FXP_NAME sftp packet. + These contents are parse to return the file's owner name and + gr oup name. */ + QCString mLongname; + + QString mUserName; + QString mGroupName; + + /** If file is a link, contains the destination of the link */ + QString mLinkDestination; + + /** If resource is a link, contains the type the link,e.g. file,dir... */ + mode_t mLinkType; + + /** Whether >> operator should read filename and longname from the stream. */ + bool mDirAttrs; + + /** Holds the encoding of the remote host */ + KRemoteEncoding* mEncoding; + +public: + sftpFileAttr(); + + sftpFileAttr(KRemoteEncoding* encoding); + + ~sftpFileAttr(); + + /** Constructor to initialize the file attributes on declaration. */ + sftpFileAttr(Q_ULLONG size_, uid_t uid_, gid_t gid_, mode_t permissions_, + time_t atime_, time_t mtime_, Q_UINT32 extendedCount_ = 0); + + /** Return the size of the sftp attribute not including filename or longname*/ + Q_UINT32 size() const; + + /** Clear all attributes and flags. */ + void clear(); + + /** Set the size of the file. */ + void setFileSize(Q_ULLONG s) + { mSize = s; mFlags |= SSH2_FILEXFER_ATTR_SIZE; } + + /** The size file attribute will not be included in the UDSEntry + or when the file attribute is written to the sftp packet. */ + void clearFileSize() + { mSize = 0; mFlags &= ~SSH2_FILEXFER_ATTR_SIZE; } + + /** Returns the size of the file. */ + Q_ULLONG fileSize() const { return mSize; } + + /** Sets the POSIX permissions of the file. */ + void setPermissions(mode_t p) + { mPermissions = p; mFlags |= SSH2_FILEXFER_ATTR_PERMISSIONS; } + + /** The permissions file attribute will not be included in the UDSEntry + or when the file attribute is written to the sftp packet. */ + void clearPermissions() + { mPermissions = 0; mFlags &= ~SSH2_FILEXFER_ATTR_PERMISSIONS; } + + /** Returns the POSIX permissons of the file. */ + mode_t permissions() const { return mPermissions; } + + /** Sets the group id of the file. */ + void setGid(gid_t id) + { mGid = id; mFlags |= SSH2_FILEXFER_ATTR_UIDGID; } + + /** Neither the gid or uid file attributes will not be included in the UDSEntry + or when the file attribute is written to the sftp packet. This is + equivalent to clearUid() */ + void clearGid() + { mGid = 0; mFlags &= SSH2_FILEXFER_ATTR_UIDGID; } + + /** Returns the group id of the file. */ + gid_t gid() const { return mGid; } + + /** Sets the uid of the file. */ + void setUid(uid_t id) + { mUid = id; mFlags |= SSH2_FILEXFER_ATTR_UIDGID; } + + /** Neither the gid or uid file attributes will not be included in the UDSEntry + or when the file attribute is written to the sftp packet. This is + equivalent to clearGid() */ + void clearUid() + { mUid = 0; mFlags &= SSH2_FILEXFER_ATTR_UIDGID; } + + /** Returns the user id of the file. */ + gid_t uid() const { return mUid; } + + /** Set the modificatoin time of the file in seconds since Jan. 1, 1970. */ + void setMtime(time_t t) + { mMtime = t; mFlags |= SSH2_FILEXFER_ATTR_ACMODTIME; } + + /** Neither the mtime or atime file attributes will not be included in the UDSEntry + or when the file attribute is written to the sftp packet. This is + equivalent to clearAtime() */ + void clearMtime() + { mMtime = 0; mFlags &= SSH2_FILEXFER_ATTR_ACMODTIME; } + + /** Returns the modification time of the file in seconds since Jan. 1, 1970. */ + time_t mtime() const { return mMtime; } + + /** Sets the access time of the file in seconds since Jan. 1, 1970. */ + void setAtime(time_t t) + { mAtime = t; mFlags |= SSH2_FILEXFER_ATTR_ACMODTIME; } + + /** Neither the atime or mtime file attributes will not be included in the UDSEntry + or when the file attribute is written to the sftp packet. This is + equivalent to clearMtime() */ + void clearAtime() + { mAtime = 0; mFlags &= SSH2_FILEXFER_ATTR_ACMODTIME; } + + /** Returns the last access time of the file in seconds since Jan. 1, 1970. */ + time_t atime() const { return mAtime; } + + /** Sets the number of file attribute extensions. */ + void setExtendedCount(unsigned int c) + { mExtendedCount = c; mFlags |= SSH2_FILEXFER_ATTR_EXTENDED; } + + /** No extensions will be included when the file attribute is written + to a sftp packet. */ + void clearExtensions() + { mExtendedCount = 0; mFlags &= ~SSH2_FILEXFER_ATTR_EXTENDED; } + + /** Returns the number of file attribute extentsions. */ + unsigned int extendedCount() const { return mExtendedCount; } + + /** Returns the flags for the sftp file attributes. */ + unsigned int flags() const { return mFlags; } + + /** Sets file's longname. See sftpFileAttr::longname. */ + void setLongname(QString l) { mLongname = l.latin1(); } + + /** Returns a string describing the file attributes. The format is specific + to the implementation of the sftp server. In most cases (ie OpenSSH) + this is similar to the long output of 'ls'. */ + QString longname() const { return mLongname; } + + void setLinkDestination(const QString& target) + { mLinkDestination = target; } + + QString linkDestination() + { return mLinkDestination; } + + /** Sets the actual type a symbolic link points to. */ + void setLinkType (mode_t type) { mLinkType = type; } + + mode_t linkType() const { return mLinkType; } + + /** No descriptions */ + void setFilename(const QString& fn) + { mFilename = fn; } + + QString filename() const + { return mFilename; } + + /** Returns a UDSEntry describing the file. + The UDSEntry is generated from the sftp file attributes. */ + KIO::UDSEntry entry(); + + /** Use to output the file attributes to a sftp packet + This will only write the sftp ATTR structure to the stream. + It will never write the filename and longname because the client + never sends those to the server. */ + friend QDataStream& operator<< (QDataStream&, const sftpFileAttr&); + + /** Use to read a file attribute from a sftp packet. + Read this carefully! If the DirAttrs flag is true, this will + read the filename, longname, and file attributes from the stream. + This is for use with listing directories. + If the DirAttrs flag is false, this will only read file attributes + from the stream. + BY DEFAULT, A NEW INSTANCE HAS DirAttrs == false */ + friend QDataStream& operator>> (QDataStream&, sftpFileAttr&); + + /** Parse longname for the owner and group names. */ + void getUserGroupNames(); + + /** Sets the DirAttrs flag. This flag affects how the >> operator works on data streams. */ + void setDirAttrsFlag(bool flag){ mDirAttrs = flag; } + + /** Gets the DirAttrs flag. */ + bool getDirAttrsFlag() const { return mDirAttrs; } + + friend kdbgstream& operator<< (kdbgstream& s, sftpFileAttr& a); + friend kndbgstream& operator<< (kndbgstream& s, sftpFileAttr& a); + + /** Returns the file type as determined from the file permissions */ + mode_t fileType() const; + + /** Set the encoding of the remote file system */ + void setEncoding( KRemoteEncoding* encoding ); +}; + +#endif diff --git a/kioslave/smb/Makefile.am b/kioslave/smb/Makefile.am new file mode 100644 index 000000000..a75cf6c6f --- /dev/null +++ b/kioslave/smb/Makefile.am @@ -0,0 +1,37 @@ +## Makefile.am of kdebase/kioslave/man + +INCLUDES=$(all_includes) + +kde_module_LTLIBRARIES = kio_smb.la +kio_smb_la_SOURCES = kio_smb.cpp \ + kio_smb_auth.cpp \ + kio_smb_browse.cpp \ + kio_smb_config.cpp \ + kio_smb_dir.cpp \ + kio_smb_file.cpp \ + kio_smb_internal.cpp \ + kio_smb_mount.cpp + +kio_smb_la_LIBADD = -lkio -lsmbclient $(SMBCLIENT_EXTRA_LIBS) + +kio_smb_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = kio_smb.h kio_smb_internal.h + +kdelnk_DATA = smb.protocol + +kdelnkdir = $(kde_servicesdir) + +dirtree_DATA = smb-network.desktop +dirtreedir = $(kde_datadir)/konqueror/dirtree/remote + +remote_DATA = smb-network.desktop +remotedir = $(kde_datadir)/remoteview + +METASOURCES = AUTO + +mimetypedir = $(kde_mimedir)/application +mimetype_DATA = x-smb-workgroup.desktop x-smb-server.desktop + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_smb.pot diff --git a/kioslave/smb/configure.in.bot b/kioslave/smb/configure.in.bot new file mode 100644 index 000000000..0d478c414 --- /dev/null +++ b/kioslave/smb/configure.in.bot @@ -0,0 +1,9 @@ +if test "x$with_samba" = xcheck && test "x$have_libsmbclient" = xno; then + echo "" + echo "You're missing libsmbclient from samba 3.0" + echo "KDE will not be able to browse windows shares without it," + echo "consider installing it." + echo "Look at kioslave/smb/libsmbclient-HOWTO.txt" + echo "" + all_tests=bad +fi diff --git a/kioslave/smb/configure.in.in b/kioslave/smb/configure.in.in new file mode 100644 index 000000000..0a0cdcc86 --- /dev/null +++ b/kioslave/smb/configure.in.in @@ -0,0 +1,34 @@ +AC_DEFUN([SMB_CHECK], +[ +AC_REQUIRE([KDE_CHECK_LARGEFILE]) + +AC_ARG_WITH(samba, + [AC_HELP_STRING(--with-samba, + [enable the samba ioslave @<:@default=check@:>@])], + [], with_samba=check) + +have_libsmbclient=no +if test "x$with_samba" != xno; then + have_libsmbclient=yes + KDE_CHECK_HEADER(libsmbclient.h, [], [have_libsmbclient=no]) + KDE_CHECK_LIB(smbclient, smbc_new_context, [], [have_libsmbclient=no]) + + SMBCLIENT_EXTRA_LIBS="" + AC_CHECK_FUNC(yp_get_default_domain, [], + [ + KDE_CHECK_LIB(nsl, yp_get_default_domain, + [SMBCLIENT_EXTRA_LIBS="-lnsl"] ) + ]) + + AC_SUBST(SMBCLIENT_EXTRA_LIBS) + + if test "x$with_samba" != xcheck && test "x$have_libsmbclient" = xno; then + AC_MSG_ERROR([--with-samba was given, but test for libsmbclient failed]) + fi +fi + +AM_CONDITIONAL(include_kioslave_smb, test "x$have_libsmbclient" = xyes) + +]) + +SMB_CHECK diff --git a/kioslave/smb/kio_smb.cpp b/kioslave/smb/kio_smb.cpp new file mode 100644 index 000000000..05db789ee --- /dev/null +++ b/kioslave/smb/kio_smb.cpp @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: Top level implementation file for kio_smb.cpp +// +// Abstract: member function implementations for SMBSlave +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + +#include "kio_smb.h" +#include "kio_smb_internal.h" + +//=========================================================================== +SMBSlave::SMBSlave(const QCString& pool, const QCString& app) + : SlaveBase( "smb", pool, app ) +{ + m_initialized_smbc = false; + + //read in the default workgroup info... + reparseConfiguration(); + + //initialize the library... + auth_initialize_smbc(); +} + + +//=========================================================================== +SMBSlave::~SMBSlave() +{ +} + + +//=========================================================================== +// pointer to the slave created in kdemain +SMBSlave* G_TheSlave; + +//=========================================================================== +int KDE_EXPORT kdemain( int argc, char **argv ) +{ + + KInstance instance( "kio_smb" ); + if( argc != 4 ) + { + kdDebug(KIO_SMB) << "Usage: kio_smb protocol domain-socket1 domain-socket2" + << endl; + return -1; + } + + SMBSlave slave( argv[2], argv[3] ); + + G_TheSlave = &slave; + slave.dispatchLoop(); + + return 0; +} + diff --git a/kioslave/smb/kio_smb.h b/kioslave/smb/kio_smb.h new file mode 100644 index 000000000..a04d7b3fa --- /dev/null +++ b/kioslave/smb/kio_smb.h @@ -0,0 +1,301 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb.h +// +// Abstract: The main kio slave class declaration. For convenience, +// in concurrent devlopment, the implementation for this class +// is separated into several .cpp files -- the file containing +// the implementation should be noted in the comments for each +// member function. +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + + +#ifndef KIO_SMB_H_INCLUDED +#define KIO_SMB_H_INCLUDED + +//------------- +// QT includes +//------------- +#include <qstring.h> +#include <qptrlist.h> +#include <qstringlist.h> +#include <qtextstream.h> +#include <qstrlist.h> + +//-------------- +// KDE includes +//-------------- +#include <kdebug.h> +#include <kinstance.h> +#include <kio/global.h> +#include <kio/slavebase.h> +#include <kurl.h> +#include <klocale.h> + +//----------------------------- +// Standard C library includes +//----------------------------- +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <errno.h> +#include <time.h> +#include <qobject.h> + +//------------------------------- +// Samba client library includes +//------------------------------- +extern "C" +{ +#include <libsmbclient.h> +} + +//--------------------------- +// kio_smb internal includes +//--------------------------- +#include "kio_smb_internal.h" + +#define MAX_XFER_BUF_SIZE 16348 +#define KIO_SMB 7106 + +using namespace KIO; +class KProcess; + +//=========================================================================== + + +class SMBSlave : public QObject, public KIO::SlaveBase +{ + Q_OBJECT + +private: + //--------------------------------------------------------------------- + // please make sure your private data does not duplicate existing data + //--------------------------------------------------------------------- + bool m_initialized_smbc; + + /** + * From Controlcenter + */ + QString m_default_user; +// QString m_default_workgroup; //currently unused, Alex <neundorf@kde.org> + QString m_default_password; + QString m_default_encoding; + + /** + * we store the current url, it's needed for + * callback authorisation method + */ + SMBUrl m_current_url; + + /** + * From Controlcenter, show SHARE$ or not + */ +// bool m_showHiddenShares; //currently unused, Alex <neundorf@kde.org> + + /** + * libsmbclient need global variables to store in, + * else it crashes on exit next method after use cache_stat, + * looks like gcc (C/C++) failure + */ + struct stat st; +protected: + //--------------------------------------------- + // Authentication functions (kio_smb_auth.cpp) + //--------------------------------------------- + // (please prefix functions with auth) + + + /** + * Description : Initilizes the libsmbclient + * Return : true on success false with errno set on error + */ + bool auth_initialize_smbc(); + + bool checkPassword(SMBUrl &url); + + + //--------------------------------------------- + // Cache functions (kio_smb_auth.cpp) + //--------------------------------------------- + + //Stat methods + + //----------------------------------------- + // Browsing functions (kio_smb_browse.cpp) + //----------------------------------------- + // (please prefix functions with browse) + + /** + * Description : Return a stat of given SMBUrl. Calls cache_stat and + * pack it in UDSEntry. UDSEntry will not be cleared + * Parameter : SMBUrl the url to stat + * ignore_errors do not call error(), but warning() + * Return : false if any error occoured (errno), else true + */ + bool browse_stat_path(const SMBUrl& url, UDSEntry& udsentry, bool ignore_errors); + + /** + * Description : call smbc_stat and return stats of the url + * Parameter : SMBUrl the url to stat + * Return : stat* of the url + * Note : it has some problems with stat in method, looks like + * something leave(or removed) on the stack. If your + * method segfault on returning try to change the stat* + * variable + */ + int cache_stat( const SMBUrl& url, struct stat* st ); + + //--------------------------------------------- + // Configuration functions (kio_smb_config.cpp) + //--------------------------------------------- + // (please prefix functions with config) + + + //--------------------------------------- + // Directory functions (kio_smb_dir.cpp) + //--------------------------------------- + // (please prefix functions with dir) + + + //-------------------------------------- + // File IO functions (kio_smb_file.cpp) + //-------------------------------------- + // (please prefix functions with file) + + //---------------------------- + // Misc functions (this file) + //---------------------------- + + + /** + * Description : correct a given URL + * valid URL's are + * + * smb://[[domain;]user[:password]@]server[:port][/share[/path[/file]]] + * smb:/[[domain;]user[:password]@][group/[server[/share[/path[/file]]]]] + * domain = workgroup(domain) of the user + * user = username + * password = password of useraccount + * group = workgroup(domain) of server + * server = host to connect + * share = a share of the server (host) + * path = a path of the share + * Parameter : KURL the url to check + * Return : new KURL if its corrected. else the same KURL + */ + KURL checkURL(const KURL& kurl) const; + + void reportError(const SMBUrl &kurl); + +public: + + //----------------------------------------------------------------------- + // smbclient authentication callback (note that this is called by the + // global ::auth_smbc_get_data() call. + void auth_smbc_get_data(const char *server,const char *share, + char *workgroup, int wgmaxlen, + char *username, int unmaxlen, + char *password, int pwmaxlen); + + + //----------------------------------------------------------------------- + // Overwritten functions from the base class that define the operation of + // this slave. (See the base class headerfile slavebase.h for more + // details) + //----------------------------------------------------------------------- + + // Functions overwritten in kio_smb.cpp + SMBSlave(const QCString& pool, const QCString& app); + virtual ~SMBSlave(); + + // Functions overwritten in kio_smb_browse.cpp + virtual void listDir( const KURL& url ); + virtual void stat( const KURL& url ); + + // Functions overwritten in kio_smb_config.cpp + virtual void reparseConfiguration(); + + // Functions overwritten in kio_smb_dir.cpp + virtual void copy( const KURL& src, const KURL &dest, int permissions, bool overwrite ); + virtual void del( const KURL& kurl, bool isfile); + virtual void mkdir( const KURL& kurl, int permissions ); + virtual void rename( const KURL& src, const KURL& dest, bool overwrite ); + + // Functions overwritten in kio_smb_file.cpp + virtual void get( const KURL& kurl ); + virtual void put( const KURL& kurl, int permissions, bool overwrite, bool resume ); + + // Functions not implemented (yet) + //virtual void setHost(const QString& host, int port, const QString& user, const QString& pass); + //virtual void openConnection(); + //virtual void closeConnection(); + //virtual void slave_status(); + virtual void special( const QByteArray & ); + +private slots: + void readOutput(KProcess *proc, char *buffer, int buflen); + void readStdErr(KProcess *proc, char *buffer, int buflen); + +private: + QString mybuf, mystderr; + +}; + +//=========================================================================== +// pointer to the slave created in kdemain +extern SMBSlave* G_TheSlave; + + +//========================================================================== +// the global libsmbclient authentication callback function +extern "C" +{ + +void auth_smbc_get_data(const char *server,const char *share, + char *workgroup, int wgmaxlen, + char *username, int unmaxlen, + char *password, int pwmaxlen); + +} + + +//=========================================================================== +// Main slave entrypoint (see kio_smb.cpp) +extern "C" +{ + +int kdemain( int argc, char **argv ); + +} + + +#endif //#endif KIO_SMB_H_INCLUDED diff --git a/kioslave/smb/kio_smb_auth.cpp b/kioslave/smb/kio_smb_auth.cpp new file mode 100644 index 000000000..60d9ac285 --- /dev/null +++ b/kioslave/smb/kio_smb_auth.cpp @@ -0,0 +1,206 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb_auth.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB directory access +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + +#include "kio_smb.h" +#include "kio_smb_internal.h" + +#include <ksimpleconfig.h> +#include <qdir.h> +#include <stdlib.h> + +// call for libsmbclient +//========================================================================== +void auth_smbc_get_data(const char *server,const char *share, + char *workgroup, int wgmaxlen, + char *username, int unmaxlen, + char *password, int pwmaxlen) +//========================================================================== +{ + G_TheSlave->auth_smbc_get_data(server, share, + workgroup,wgmaxlen, + username, unmaxlen, + password, pwmaxlen); +} + +//-------------------------------------------------------------------------- +void SMBSlave::auth_smbc_get_data(const char *server,const char *share, + char *workgroup, int wgmaxlen, + char *username, int unmaxlen, + char *password, int pwmaxlen) +//-------------------------------------------------------------------------- +{ + //check this to see if we "really" need to authenticate... + SMBUrlType t = m_current_url.getType(); + if( t == SMBURLTYPE_ENTIRE_NETWORK ) + { + kdDebug(KIO_SMB) << "we don't really need to authenticate for this top level url, returning" << endl; + return; + } + kdDebug(KIO_SMB) << "AAAAAAAAAAAAAA auth_smbc_get_dat: set user=" << username << ", workgroup=" << workgroup + << " server=" << server << ", share=" << share << endl; + + QString s_server = QString::fromUtf8(server); + QString s_share = QString::fromUtf8(share); + workgroup[wgmaxlen - 1] = 0; + QString s_workgroup = QString::fromUtf8(workgroup); + username[unmaxlen - 1] = 0; + QString s_username = QString::fromUtf8(username); + password[pwmaxlen - 1] = 0; + QString s_password = QString::fromUtf8(password); + + KIO::AuthInfo info; + info.url = KURL("smb:///"); + info.url.setHost(s_server); + info.url.setPath("/" + s_share); + + info.username = s_username; + info.password = s_password; + info.verifyPath = true; + + kdDebug(KIO_SMB) << "libsmb-auth-callback URL:" << info.url << endl; + + if ( !checkCachedAuthentication( info ) ) + { + if ( m_default_user.isEmpty() ) + { + // ok, we do not know the password. Let's try anonymous before we try for real + info.username = "anonymous"; + info.password = QString::null; + } + else + { + // user defined a default username/password in kcontrol; try this + info.username = m_default_user; + info.password = m_default_password; + } + + } else + kdDebug(KIO_SMB) << "got password through cache" << endl; + + strncpy(username, info.username.utf8(), unmaxlen - 1); + strncpy(password, info.password.utf8(), pwmaxlen - 1); +} + +bool SMBSlave::checkPassword(SMBUrl &url) +{ + kdDebug(KIO_SMB) << "checkPassword for " << url << endl; + + KIO::AuthInfo info; + info.url = KURL("smb:///"); + info.url.setHost(url.host()); + + QString share = url.path(); + int index = share.find('/', 1); + if (index > 1) + share = share.left(index); + if (share.at(0) == '/') + share = share.mid(1); + info.url.setPath("/" + share); + info.verifyPath = true; + + if ( share.isEmpty() ) + info.prompt = i18n( + "<qt>Please enter authentication information for <b>%1</b></qt>" ) + .arg( url.host() ); + else + info.prompt = i18n( + "Please enter authentication information for:\n" + "Server = %1\n" + "Share = %2" ) + .arg( url.host() ) + .arg( share ); + + info.username = url.user(); + kdDebug(KIO_SMB) << "call openPassDlg for " << info.url << endl; + + if ( openPassDlg(info) ) { + kdDebug(KIO_SMB) << "openPassDlg returned " << info.username << endl; + url.setUser(info.username); + return true; + } + kdDebug(KIO_SMB) << "no value from openPassDlg\n"; + return false; +} + +//-------------------------------------------------------------------------- +// Initalizes the smbclient library +// +// Returns: 0 on success -1 with errno set on error +bool SMBSlave::auth_initialize_smbc() +{ + SMBCCTX *smb_context = NULL; + + kdDebug(KIO_SMB) << "auth_initialize_smbc " << endl; + if(m_initialized_smbc == false) + { + kdDebug(KIO_SMB) << "smbc_init call" << endl; + KSimpleConfig cfg( "kioslaverc", true ); + + cfg.setGroup( "SMB" ); + int debug_level = cfg.readNumEntry( "DebugLevel", 0 ); + +#if 0 + /* old API initialisation routine does not allow to set flags */ + + if(smbc_init(::auth_smbc_get_data,debug_level) == -1) + { + SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to initialize")); + return false; + } +#endif + smb_context = smbc_new_context(); + if (smb_context == NULL) { + SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to create context")); + return false; + } + + smb_context->debug = debug_level; + smb_context->callbacks.auth_fn = ::auth_smbc_get_data; + + if (!smbc_init_context(smb_context)) { + smbc_free_context(smb_context, false); + smb_context = NULL; + SlaveBase::error(ERR_INTERNAL, i18n("libsmbclient failed to initialize context")); + return false; + } + +#if defined(SMB_CTX_FLAG_USE_KERBEROS) && defined(SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS) + smb_context->flags |= SMB_CTX_FLAG_USE_KERBEROS | SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS; +#endif + + smbc_set_context(smb_context); + + m_initialized_smbc = true; + } + + return true; +} + diff --git a/kioslave/smb/kio_smb_browse.cpp b/kioslave/smb/kio_smb_browse.cpp new file mode 100644 index 000000000..de6bf4ed2 --- /dev/null +++ b/kioslave/smb/kio_smb_browse.cpp @@ -0,0 +1,476 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb_browse.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB browsing +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + +#include <config.h> +#include <pwd.h> +#include <grp.h> + +#include <qtextcodec.h> + +#include <kglobal.h> + +#include "kio_smb.h" +#include "kio_smb_internal.h" + +using namespace KIO; + +int SMBSlave::cache_stat(const SMBUrl &url, struct stat* st ) +{ + int result = smbc_stat( url.toSmbcUrl(), st); + kdDebug(KIO_SMB) << "smbc_stat " << url << " " << errno << " " << result << endl; + kdDebug(KIO_SMB) << "size " << (KIO::filesize_t)st->st_size << endl; + return result; +} + +//--------------------------------------------------------------------------- +bool SMBSlave::browse_stat_path(const SMBUrl& _url, UDSEntry& udsentry, bool ignore_errors) + // Returns: true on success, false on failure +{ + UDSAtom udsatom; + + SMBUrl url = _url; + + if(cache_stat(url, &st) == 0) + { + if(!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + { + kdDebug(KIO_SMB)<<"SMBSlave::browse_stat_path mode: "<<st.st_mode<<endl; + warning(i18n("%1:\n" + "Unknown file type, neither directory or file.").arg(url.prettyURL())); + return false; + } + + udsatom.m_uds = KIO::UDS_FILE_TYPE; + udsatom.m_long = st.st_mode & S_IFMT; + udsentry.append(udsatom); + + udsatom.m_uds = KIO::UDS_SIZE; + udsatom.m_long = st.st_size; + udsentry.append(udsatom); + + udsatom.m_uds = KIO::UDS_USER; + uid_t uid = st.st_uid; + struct passwd *user = getpwuid( uid ); + if ( user ) + udsatom.m_str = user->pw_name; + else + udsatom.m_str = QString::number( uid ); + udsentry.append(udsatom); + + udsatom.m_uds = KIO::UDS_GROUP; + gid_t gid = st.st_gid; + struct group *grp = getgrgid( gid ); + if ( grp ) + udsatom.m_str = grp->gr_name; + else + udsatom.m_str = QString::number( gid ); + udsentry.append(udsatom); + + udsatom.m_uds = KIO::UDS_ACCESS; + udsatom.m_long = st.st_mode & 07777; + udsentry.append(udsatom); + + udsatom.m_uds = UDS_MODIFICATION_TIME; + udsatom.m_long = st.st_mtime; + udsentry.append(udsatom); + + udsatom.m_uds = UDS_ACCESS_TIME; + udsatom.m_long = st.st_atime; + udsentry.append(udsatom); + + udsatom.m_uds = UDS_CREATION_TIME; + udsatom.m_long = st.st_ctime; + udsentry.append(udsatom); + + } + else + { + if (!ignore_errors) { + if (errno == EPERM || errno == EACCES) + if (checkPassword(url)) { + redirection( url ); + return false; + } + + reportError(url); + } else if (errno == ENOENT || errno == ENOTDIR) { + warning(i18n("File does not exist: %1").arg(url.url())); + } + kdDebug(KIO_SMB) << "SMBSlave::browse_stat_path ERROR!!"<< endl; + return false; + } + + return true; +} + +//=========================================================================== +void SMBSlave::stat( const KURL& kurl ) +{ + kdDebug(KIO_SMB) << "SMBSlave::stat on "<< kurl << endl; + // make a valid URL + KURL url = checkURL(kurl); + + // if URL is not valid we have to redirect to correct URL + if (url != kurl) + { + kdDebug() << "redirection " << url << endl; + redirection(url); + finished(); + return; + } + + m_current_url = url; + + UDSAtom udsatom; + UDSEntry udsentry; + // Set name + udsatom.m_uds = KIO::UDS_NAME; + udsatom.m_str = kurl.fileName(); + udsentry.append( udsatom ); + + switch(m_current_url.getType()) + { + case SMBURLTYPE_UNKNOWN: + error(ERR_MALFORMED_URL,m_current_url.prettyURL()); + finished(); + return; + + case SMBURLTYPE_ENTIRE_NETWORK: + case SMBURLTYPE_WORKGROUP_OR_SERVER: + udsatom.m_uds = KIO::UDS_FILE_TYPE; + udsatom.m_long = S_IFDIR; + udsentry.append(udsatom); + break; + + case SMBURLTYPE_SHARE_OR_PATH: + if (browse_stat_path(m_current_url, udsentry, false)) + break; + else { + kdDebug(KIO_SMB) << "SMBSlave::stat ERROR!!"<< endl; + finished(); + return; + } + default: + kdDebug(KIO_SMB) << "SMBSlave::stat UNKNOWN " << url << endl; + finished(); + return; + } + + statEntry(udsentry); + finished(); +} + +//=========================================================================== +// TODO: complete checking +KURL SMBSlave::checkURL(const KURL& kurl) const +{ + kdDebug(KIO_SMB) << "checkURL " << kurl << endl; + QString surl = kurl.url(); + if (surl.startsWith("smb:/")) { + if (surl.length() == 5) // just the above + return kurl; // unchanged + + if (surl.at(5) != '/') { + surl = "smb://" + surl.mid(5); + kdDebug(KIO_SMB) << "checkURL return1 " << surl << " " << KURL(surl) << endl; + return KURL(surl); + } + } + + // smb:/ normaly have no userinfo + // we must redirect ourself to remove the username and password + if (surl.contains('@') && !surl.contains("smb://")) { + KURL url(kurl); + url.setPath("/"+kurl.url().right( kurl.url().length()-kurl.url().find('@') -1)); + QString userinfo = kurl.url().mid(5, kurl.url().find('@')-5); + if(userinfo.contains(':')) { + url.setUser(userinfo.left(userinfo.find(':'))); + url.setPass(userinfo.right(userinfo.length()-userinfo.find(':')-1)); + } else { + url.setUser(userinfo); + } + kdDebug(KIO_SMB) << "checkURL return2 " << url << endl; + return url; + } + + // no emtpy path + KURL url(kurl); + + if (url.path().isEmpty()) + url.setPath("/"); + + kdDebug(KIO_SMB) << "checkURL return3 " << url << endl; + return url; +} + +void SMBSlave::reportError(const SMBUrl &url) +{ + kdDebug(KIO_SMB) << "reportError " << url << " " << perror << endl; + switch(errno) + { + case ENOENT: + if (url.getType() == SMBURLTYPE_ENTIRE_NETWORK) + error( ERR_SLAVE_DEFINED, i18n("Unable to find any workgroups in your local network. This might be caused by an enabled firewall.")); + else + error( ERR_DOES_NOT_EXIST, url.prettyURL()); + break; +#ifdef ENOMEDIUM + case ENOMEDIUM: + error( ERR_SLAVE_DEFINED, + i18n( "No media in device for %1" ).arg( url.prettyURL() ) ); + break; +#endif +#ifdef EHOSTDOWN + case EHOSTDOWN: +#endif + case ECONNREFUSED: + error( ERR_SLAVE_DEFINED, + i18n( "Could not connect to host for %1" ).arg( url.prettyURL() ) ); + break; + case ENOTDIR: + error( ERR_CANNOT_ENTER_DIRECTORY, url.prettyURL()); + break; + case EFAULT: + case EINVAL: + error( ERR_DOES_NOT_EXIST, url.prettyURL()); + break; + case EPERM: + case EACCES: + error( ERR_ACCESS_DENIED, url.prettyURL() ); + break; + case EIO: + case ENETUNREACH: + if ( url.getType() == SMBURLTYPE_ENTIRE_NETWORK || url.getType() == SMBURLTYPE_WORKGROUP_OR_SERVER ) + error( ERR_SLAVE_DEFINED, i18n( "Error while connecting to server responsible for %1" ).arg( url.prettyURL() ) ); + else + error( ERR_CONNECTION_BROKEN, url.prettyURL()); + break; + case ENOMEM: + error( ERR_OUT_OF_MEMORY, url.prettyURL() ); + break; + case ENODEV: + error( ERR_SLAVE_DEFINED, i18n("Share could not be found on given server")); + break; + case EBADF: + error( ERR_INTERNAL, i18n("BAD File descriptor")); + break; + case ETIMEDOUT: + error( ERR_SERVER_TIMEOUT, url.host() ); + break; +#ifdef ENOTUNIQ + case ENOTUNIQ: + error( ERR_SLAVE_DEFINED, i18n( "The given name could not be resolved to a unique server. " + "Make sure your network is setup without any name conflicts " + "between names used by Windows and by UNIX name resolution." ) ); + break; +#endif + case 0: // success + error( ERR_INTERNAL, i18n("libsmbclient reported an error, but did not specify " + "what the problem is. This might indicate a severe problem " + "with your network - but also might indicate a problem with " + "libsmbclient.\n" + "If you want to help us, please provide a tcpdump of the " + "network interface while you try to browse (be aware that " + "it might contain private data, so do not post it if you are " + "unsure about that - you can send it privately to the developers " + "if they ask for it)") ); + break; + default: + error( ERR_INTERNAL, i18n("Unknown error condition in stat: %1").arg(QString::fromLocal8Bit( strerror(errno))) ); + } +} + +//=========================================================================== +void SMBSlave::listDir( const KURL& kurl ) +{ + kdDebug(KIO_SMB) << "SMBSlave::listDir on " << kurl << endl; + + // check (correct) URL + KURL url = checkURL(kurl); + // if URL is not valid we have to redirect to correct URL + if (url != kurl) + { + redirection(url); + finished(); + return; + } + + m_current_url = kurl; + + int dirfd; + struct smbc_dirent *dirp = NULL; + UDSEntry udsentry; + UDSAtom atom; + + dirfd = smbc_opendir( m_current_url.toSmbcUrl() ); + kdDebug(KIO_SMB) << "SMBSlave::listDir open " << m_current_url.toSmbcUrl() << " " << m_current_url.getType() << " " << dirfd << endl; + if(dirfd >= 0) + { + do { + kdDebug(KIO_SMB) << "smbc_readdir " << endl; + dirp = smbc_readdir(dirfd); + if(dirp == 0) + break; + + // Set name + atom.m_uds = KIO::UDS_NAME; + QString dirpName = QString::fromUtf8( dirp->name ); + // We cannot trust dirp->commentlen has it might be with or without the NUL character + // See KDE bug #111430 and Samba bug #3030 + QString comment = QString::fromUtf8( dirp->comment ); + if ( dirp->smbc_type == SMBC_SERVER || dirp->smbc_type == SMBC_WORKGROUP ) { + atom.m_str = dirpName.lower(); + atom.m_str.at( 0 ) = dirpName.at( 0 ).upper(); + if ( !comment.isEmpty() && dirp->smbc_type == SMBC_SERVER ) + atom.m_str += " (" + comment + ")"; + } else + atom.m_str = dirpName; + + kdDebug(KIO_SMB) << "dirp->name " << dirp->name << " " << dirpName << " '" << comment << "'" << " " << dirp->smbc_type << endl; + + udsentry.append( atom ); + if (atom.m_str.upper()=="IPC$" || atom.m_str=="." || atom.m_str == ".." || + atom.m_str.upper() == "ADMIN$" || atom.m_str.lower() == "printer$" || atom.m_str.lower() == "print$" ) + { +// fprintf(stderr,"----------- hide: -%s-\n",dirp->name); + // do nothing and hide the hidden shares + } + else if(dirp->smbc_type == SMBC_FILE) + { + // Set stat information + m_current_url.addPath(dirpName); + browse_stat_path(m_current_url, udsentry, true); + m_current_url.cd(".."); + + // Call base class to list entry + listEntry(udsentry, false); + } + else if(dirp->smbc_type == SMBC_DIR) + { + m_current_url.addPath(dirpName); + browse_stat_path(m_current_url, udsentry, true); + m_current_url.cd(".."); + + // Call base class to list entry + listEntry(udsentry, false); + } + else if(dirp->smbc_type == SMBC_SERVER || + dirp->smbc_type == SMBC_FILE_SHARE) + { + // Set type + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + udsentry.append( atom ); + + // Set permissions + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); + udsentry.append(atom); + + if (dirp->smbc_type == SMBC_SERVER) { + atom.m_uds = KIO::UDS_URL; + // QString workgroup = m_current_url.host().upper(); + KURL u("smb:/"); + u.setHost(dirpName); + atom.m_str = u.url(); + + // when libsmbclient knows + // atom.m_str = QString("smb://%1?WORKGROUP=%2").arg(dirpName).arg(workgroup.upper()); + kdDebug(KIO_SMB) << "list item " << atom.m_str << endl; + udsentry.append(atom); + + atom.m_uds = KIO::UDS_MIME_TYPE; + atom.m_str = QString::fromLatin1("application/x-smb-server"); + udsentry.append(atom); + } + + // Call base class to list entry + listEntry(udsentry, false); + } + else if(dirp->smbc_type == SMBC_WORKGROUP) + { + // Set type + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + udsentry.append( atom ); + + // Set permissions + atom.m_uds = KIO::UDS_ACCESS; + atom.m_long = (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); + udsentry.append(atom); + + atom.m_uds = KIO::UDS_MIME_TYPE; + atom.m_str = QString::fromLatin1("application/x-smb-workgroup"); + udsentry.append(atom); + + atom.m_uds = KIO::UDS_URL; + // QString workgroup = m_current_url.host().upper(); + KURL u("smb:/"); + u.setHost(dirpName); + atom.m_str = u.url(); + udsentry.append(atom); + + // Call base class to list entry + listEntry(udsentry, false); + } + else + { + kdDebug(KIO_SMB) << "SMBSlave::listDir SMBC_UNKNOWN :" << dirpName << endl; + // TODO: we don't handle SMBC_IPC_SHARE, SMBC_PRINTER_SHARE + // SMBC_LINK, SMBC_COMMS_SHARE + //SlaveBase::error(ERR_INTERNAL, TEXT_UNSUPPORTED_FILE_TYPE); + // continue; + } + udsentry.clear(); + } while (dirp); // checked already in the head + + // clean up + smbc_closedir(dirfd); + } + else + { + if (errno == EPERM || errno == EACCES) + if (checkPassword(m_current_url)) { + redirection( m_current_url ); + finished(); + return; + } + + reportError(m_current_url); + finished(); + return; + } + + listEntry(udsentry, true); + finished(); +} + diff --git a/kioslave/smb/kio_smb_config.cpp b/kioslave/smb/kio_smb_config.cpp new file mode 100644 index 000000000..c772f8b94 --- /dev/null +++ b/kioslave/smb/kio_smb_config.cpp @@ -0,0 +1,66 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb_config.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// KDE/SMB slave configuration +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + +#include "kio_smb.h" +#include "kio_smb_internal.h" +#include <kconfig.h> + +#include <qtextcodec.h> +//=========================================================================== +void SMBSlave::reparseConfiguration() +{ + KConfig *cfg = new KConfig("kioslaverc", true); + cfg->setGroup("Browser Settings/SMBro"); + m_default_user=cfg->readEntry("User"); +// m_default_workgroup=cfg->readEntry("Workgroup"); +// m_showHiddenShares=cfg->readBoolEntry("ShowHiddenShares",false); + + QString m_encoding = QTextCodec::codecForLocale()->name(); + m_default_encoding = cfg->readEntry( "Encoding", m_encoding.lower() ); + + // unscramble, taken from Nicola Brodu's smb ioslave + //not really secure, but better than storing the plain password + QString scrambled = cfg->readEntry( "Password" ); + m_default_password = ""; + for (uint i=0; i<scrambled.length()/3; i++) + { + QChar qc1 = scrambled[i*3]; + QChar qc2 = scrambled[i*3+1]; + QChar qc3 = scrambled[i*3+2]; + unsigned int a1 = qc1.latin1() - '0'; + unsigned int a2 = qc2.latin1() - 'A'; + unsigned int a3 = qc3.latin1() - '0'; + unsigned int num = ((a1 & 0x3F) << 10) | ((a2& 0x1F) << 5) | (a3 & 0x1F); + m_default_password[i] = QChar((uchar)((num - 17) ^ 173)); // restore + } + + delete cfg; +} diff --git a/kioslave/smb/kio_smb_dir.cpp b/kioslave/smb/kio_smb_dir.cpp new file mode 100644 index 000000000..936ec15af --- /dev/null +++ b/kioslave/smb/kio_smb_dir.cpp @@ -0,0 +1,345 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb_dir.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB directory access +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +////--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + +#include "kio_smb.h" +#include "kio_smb_internal.h" + + +//=========================================================================== +// TODO: add when libsmbclient supports it +void SMBSlave::copy( const KURL& ksrc, + const KURL& kdst, + int permissions, + bool overwrite) +{ + + SMBUrl src; + SMBUrl dst; + mode_t initialmode; + int n; + int dstflags; + int srcfd = -1; + int dstfd = -1; + KIO::filesize_t processed_size = 0; + unsigned char buf[MAX_XFER_BUF_SIZE]; + + kdDebug(KIO_SMB) << "SMBSlave::copy with src = " << ksrc << "and dest = " << kdst << endl; + + // setup urls + src = ksrc; + dst = kdst; + + // Obtain information about source + if(cache_stat(src, &st ) == -1) + { + if ( errno == EACCES ) + { + error( KIO::ERR_ACCESS_DENIED, src.prettyURL()); + } + else + { + error( KIO::ERR_DOES_NOT_EXIST, src.prettyURL()); + } + return; + } + if ( S_ISDIR( st.st_mode ) ) + { + error( KIO::ERR_IS_DIRECTORY, src.prettyURL() ); + return; + } + totalSize(st.st_size); + + // Check to se if the destination exists + if(cache_stat(dst, &st) != -1) + { + if(S_ISDIR(st.st_mode)) + { + error( KIO::ERR_DIR_ALREADY_EXIST, dst.prettyURL()); + return; + } + if(!overwrite) + { + error( KIO::ERR_FILE_ALREADY_EXIST, dst.prettyURL()); + return; + } + } + + // Open the source file + srcfd = smbc_open(src.toSmbcUrl(), O_RDONLY, 0); + if(srcfd < 0) + { + if(errno == EACCES) + { + error( KIO::ERR_ACCESS_DENIED, src.prettyURL() ); + } + else + { + error( KIO::ERR_DOES_NOT_EXIST, src.prettyURL() ); + } + return; + } + + // Determine initial creation mode + if(permissions != -1) + { + initialmode = permissions | S_IWUSR; + } + else + { + initialmode = 0 | S_IWUSR;//0666; + } + + + // Open the destination file + dstflags = O_CREAT | O_TRUNC | O_WRONLY; + if(!overwrite) + { + dstflags |= O_EXCL; + } + dstfd = smbc_open(dst.toSmbcUrl(), dstflags, initialmode); + if(dstfd < 0) + { + if(errno == EACCES) + { + error(KIO::ERR_WRITE_ACCESS_DENIED, dst.prettyURL()); + } + else + { + error(KIO::ERR_CANNOT_OPEN_FOR_READING, dst.prettyURL()); + } + if(srcfd >= 0 ) + { + smbc_close(srcfd); + } + return; + } + + + // Perform copy + while(1) + { + n = smbc_read(srcfd, buf, MAX_XFER_BUF_SIZE ); + if(n > 0) + { + n = smbc_write(dstfd, buf, n); + if(n == -1) + { + kdDebug(KIO_SMB) << "SMBSlave::copy copy now KIO::ERR_COULD_NOT_WRITE" << endl; + error( KIO::ERR_COULD_NOT_WRITE, dst.prettyURL()); + break; + } + + processed_size += n; + processedSize(processed_size); + } + else if(n == 0) + { + break; // finished + } + else + { + error( KIO::ERR_COULD_NOT_READ, src.prettyURL()); + break; + } + } + + + // FINISHED: + + if(srcfd >= 0 ) + { + smbc_close(srcfd); + } + + if(dstfd >= 0) + { + if(smbc_close(dstfd) == 0) + { + + // TODO: set final permissions + } + else + { + error( KIO::ERR_COULD_NOT_WRITE, dst.prettyURL()); + return; + } + } + + finished(); +} + +//=========================================================================== +void SMBSlave::del( const KURL &kurl, bool isfile) +{ + kdDebug(KIO_SMB) << "SMBSlave::del on " << kurl << endl; + m_current_url = kurl; + + if(isfile) + { + // Delete file + kdDebug(KIO_SMB) << "SMBSlave:: unlink " << kurl << endl; + if(smbc_unlink(m_current_url.toSmbcUrl()) == -1) + { + switch(errno) + { + case EISDIR: + error( KIO::ERR_IS_DIRECTORY, m_current_url.prettyURL()); + break; + default: + reportError(kurl); + } + } + } + else + { + kdDebug(KIO_SMB) << "SMBSlave:: rmdir " << kurl << endl; + // Delete directory + if(smbc_rmdir(m_current_url.toSmbcUrl()) == -1) + { + reportError(kurl); + } + } + + finished(); +} + +//=========================================================================== +void SMBSlave::mkdir( const KURL &kurl, int permissions ) +{ + kdDebug(KIO_SMB) << "SMBSlave::mkdir on " << kurl << endl; + m_current_url = kurl; + + if(smbc_mkdir(m_current_url.toSmbcUrl(), 0777) != 0) + { + if (errno == EEXIST) { + if(cache_stat(m_current_url, &st ) == 0) + { + if(S_ISDIR(st.st_mode )) + { + error( KIO::ERR_DIR_ALREADY_EXIST, m_current_url.prettyURL()); + } + } + else + { + error( KIO::ERR_FILE_ALREADY_EXIST, m_current_url.prettyURL()); + } + } else + reportError(kurl); + kdDebug(KIO_SMB) << "SMBSlave::mkdir exit with error " << kurl << endl; + } + else + { + if(permissions != -1) + { + // TODO enable the following when complete + //smbc_chmod( url.toSmbcUrl(), permissions ); + } + } + + finished(); +} + + +//=========================================================================== +void SMBSlave::rename( const KURL& ksrc, const KURL& kdest, bool overwrite ) +{ + + SMBUrl src; + SMBUrl dst; + + kdDebug(KIO_SMB) << "SMBSlave::rename, old name = " << ksrc << ", new name = " << kdest << endl; + + src = ksrc; + dst = kdest; + + // Check to se if the destination exists + + kdDebug(KIO_SMB) << "SMBSlave::rename stat dst" << endl; + if(cache_stat(dst, &st) != -1) + { + if(S_ISDIR(st.st_mode)) + { + kdDebug(KIO_SMB) << "SMBSlave::rename KIO::ERR_DIR_ALREADY_EXIST" << endl; + error( KIO::ERR_DIR_ALREADY_EXIST, dst.prettyURL()); + finished(); + return; + } + if(!overwrite) + { + kdDebug(KIO_SMB) << "SMBSlave::rename KIO::ERR_FILE_ALREADY_EXIST" << endl; + error( KIO::ERR_FILE_ALREADY_EXIST, dst.prettyURL()); + finished(); + return; + } + } + kdDebug(KIO_SMB ) << "smbc_rename " << src.toSmbcUrl() << " " << dst.toSmbcUrl() << endl; + if(smbc_rename(src.toSmbcUrl(), dst.toSmbcUrl())!=0) + { + kdDebug(KIO_SMB ) << "failed " << perror << endl; + switch(errno) + { + case ENOENT: + if(cache_stat(src, &st) == -1) + { + if(errno == EACCES) + { + kdDebug(KIO_SMB) << "SMBSlave::rename KIO::ERR_ACCESS_DENIED" << endl; + error(KIO::ERR_ACCESS_DENIED, src.prettyURL()); + } + else + { + kdDebug(KIO_SMB) << "SMBSlave::rename KIO::ERR_DOES_NOT_EXIST" << endl; + error(KIO::ERR_DOES_NOT_EXIST, src.prettyURL()); + } + } + break; + + case EACCES: + case EPERM: + kdDebug(KIO_SMB) << "SMBSlave::rename KIO::ERR_ACCESS_DENIED" << endl; + error( KIO::ERR_ACCESS_DENIED, dst.prettyURL() ); + break; + + default: + kdDebug(KIO_SMB) << "SMBSlave::rename KIO::ERR_CANNOT_RENAME" << endl; + error( KIO::ERR_CANNOT_RENAME, src.prettyURL() ); + + } + + kdDebug(KIO_SMB) << "SMBSlave::rename exit with error" << endl; + return; + } + + kdDebug(KIO_SMB ) << "everything fine\n"; + finished(); +} + + diff --git a/kioslave/smb/kio_smb_file.cpp b/kioslave/smb/kio_smb_file.cpp new file mode 100644 index 000000000..67fe0d213 --- /dev/null +++ b/kioslave/smb/kio_smb_file.cpp @@ -0,0 +1,279 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb_file.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB file access +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + + +#include "kio_smb.h" +#include "kio_smb_internal.h" + +#include <kmimetype.h> + +//=========================================================================== +void SMBSlave::get( const KURL& kurl ) +{ + char buf[MAX_XFER_BUF_SIZE]; + int filefd = 0; + ssize_t bytesread = 0; + // time_t curtime = 0; + time_t lasttime = 0; + time_t starttime = 0; + KIO::filesize_t totalbytesread = 0; + QByteArray filedata; + SMBUrl url; + + kdDebug(KIO_SMB) << "SMBSlave::get on " << kurl << endl; + + // check (correct) URL + KURL kvurl = checkURL(kurl); + // if URL is not valid we have to redirect to correct URL + if (kvurl != kurl) { + redirection(kvurl); + finished(); + return; + } + + if(!auth_initialize_smbc()) + return; + + + // Stat + url = kurl; + if(cache_stat(url,&st) == -1 ) + { + if ( errno == EACCES ) + error( KIO::ERR_ACCESS_DENIED, url.prettyURL()); + else + error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + return; + } + if ( S_ISDIR( st.st_mode ) ) { + error( KIO::ERR_IS_DIRECTORY, url.prettyURL()); + return; + } + + // Set the total size + totalSize( st.st_size ); + + // Open and read the file + filefd = smbc_open(url.toSmbcUrl(),O_RDONLY,0); + if(filefd >= 0) + { + if(buf) + { + bool isFirstPacket = true; + lasttime = starttime = time(NULL); + while(1) + { + bytesread = smbc_read(filefd, buf, MAX_XFER_BUF_SIZE); + if(bytesread == 0) + { + // All done reading + break; + } + else if(bytesread < 0) + { + error( KIO::ERR_COULD_NOT_READ, url.prettyURL()); + return; + } + + filedata.setRawData(buf,bytesread); + if (isFirstPacket) + { + // We need a KMimeType::findByNameAndContent(filename,data) + // For now we do: find by extension, and if not found (or extension not reliable) + // then find by content. + bool accurate = false; + KMimeType::Ptr mime = KMimeType::findByURL( kurl, st.st_mode, false, true, &accurate ); + if ( !mime || mime->name() == KMimeType::defaultMimeType() + || !accurate ) + { + KMimeType::Ptr p_mimeType = KMimeType::findByContent(filedata); + if ( p_mimeType && p_mimeType->name() != KMimeType::defaultMimeType() ) + mime = p_mimeType; + } + mimeType(mime->name()); + isFirstPacket = false; + } + data( filedata ); + filedata.resetRawData(buf,bytesread); + + // increment total bytes read + totalbytesread += bytesread; + + processedSize(totalbytesread); + } + } + + smbc_close(filefd); + data( QByteArray() ); + processedSize(static_cast<KIO::filesize_t>(st.st_size)); + + } + else + { + error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL()); + return; + } + + finished(); +} + + +//=========================================================================== +void SMBSlave::put( const KURL& kurl, + int permissions, + bool overwrite, + bool resume ) +{ + + void *buf; + size_t bufsize; + + m_current_url = kurl; + + int filefd; + bool exists; + mode_t mode; + QByteArray filedata; + + kdDebug(KIO_SMB) << "SMBSlave::put on " << kurl << endl; + + + exists = (cache_stat(m_current_url, &st) != -1 ); + if ( exists && !overwrite && !resume) + { + if (S_ISDIR(st.st_mode)) + { + kdDebug(KIO_SMB) << "SMBSlave::put on " << kurl <<" already isdir !!"<< endl; + error( KIO::ERR_DIR_ALREADY_EXIST, m_current_url.prettyURL()); + } + else + { + kdDebug(KIO_SMB) << "SMBSlave::put on " << kurl <<" already exist !!"<< endl; + error( KIO::ERR_FILE_ALREADY_EXIST, m_current_url.prettyURL()); + } + return; + } + + if (exists && !resume && overwrite) + { + kdDebug(KIO_SMB) << "SMBSlave::put exists try to remove " << m_current_url.toSmbcUrl()<< endl; + // remove(m_current_url.url().local8Bit()); + } + + + if (resume) + { + // append if resuming + kdDebug(KIO_SMB) << "SMBSlave::put resume " << m_current_url.toSmbcUrl()<< endl; + filefd = smbc_open(m_current_url.toSmbcUrl(), O_RDWR, 0 ); + smbc_lseek(filefd, 0, SEEK_END); + } + else + { + if (permissions != -1) + { + mode = permissions | S_IWUSR | S_IRUSR; + } + else + { + mode = 600;//0666; + } + + kdDebug(KIO_SMB) << "SMBSlave::put NO resume " << m_current_url.toSmbcUrl()<< endl; + filefd = smbc_open(m_current_url.toSmbcUrl(), O_CREAT | O_TRUNC | O_WRONLY, mode); + } + + if ( filefd < 0 ) + { + if ( errno == EACCES ) + { + kdDebug(KIO_SMB) << "SMBSlave::put error " << kurl <<" access denied !!"<< endl; + error( KIO::ERR_WRITE_ACCESS_DENIED, m_current_url.prettyURL()); + } + else + { + kdDebug(KIO_SMB) << "SMBSlave::put error " << kurl <<" can not open for writing !!"<< endl; + error( KIO::ERR_CANNOT_OPEN_FOR_WRITING, m_current_url.prettyURL()); + } + finished(); + return; + } + + // Loop until we got 0 (end of data) + while(1) + { + kdDebug(KIO_SMB) << "SMBSlave::put request data "<< endl; + dataReq(); // Request for data + kdDebug(KIO_SMB) << "SMBSlave::put write " << m_current_url.toSmbcUrl()<< endl; + + if (readData(filedata) <= 0) + { + kdDebug(KIO_SMB) << "readData <= 0" << endl; + break; + } + kdDebug(KIO_SMB) << "SMBSlave::put write " << m_current_url.toSmbcUrl()<< endl; + buf = filedata.data(); + bufsize = filedata.size(); + int size = smbc_write(filefd, buf, bufsize); + if ( size < 0) + { + kdDebug(KIO_SMB) << "SMBSlave::put error " << kurl <<" could not write !!"<< endl; + error( KIO::ERR_COULD_NOT_WRITE, m_current_url.prettyURL()); + finished(); + return; + } + kdDebug(KIO_SMB ) << "wrote " << size << endl; + } + kdDebug(KIO_SMB) << "SMBSlave::put close " << m_current_url.toSmbcUrl()<< endl; + + if(smbc_close(filefd)) + { + kdDebug(KIO_SMB) << "SMBSlave::put on " << kurl <<" could not write !!"<< endl; + error( KIO::ERR_COULD_NOT_WRITE, m_current_url.prettyURL()); + finished(); + return; + } + + // set final permissions, if the file was just created + if ( permissions != -1 && !exists ) + { + // TODO: did the smbc_chmod fail? + // TODO: put in call to chmod when it is working! + // smbc_chmod(url.toSmbcUrl(),permissions); + } + + // We have done our job => finish + finished(); +} + + + + diff --git a/kioslave/smb/kio_smb_internal.cpp b/kioslave/smb/kio_smb_internal.cpp new file mode 100644 index 000000000..27cdfb8d3 --- /dev/null +++ b/kioslave/smb/kio_smb_internal.cpp @@ -0,0 +1,135 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb_internal.cpp +// +// Abstract: Utility class implementation used by SMBSlave +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + +#include "kio_smb.h" +#include "kio_smb_internal.h" + +#include <qtextcodec.h> + +#include <kconfig.h> +#include <kglobal.h> + + +//=========================================================================== +// SMBUrl Function Implementation +//=========================================================================== + +//----------------------------------------------------------------------- +SMBUrl::SMBUrl() +{ + m_type = SMBURLTYPE_UNKNOWN; +} + +//----------------------------------------------------------------------- +SMBUrl::SMBUrl(const KURL& kurl) + : KURL(kurl) + //----------------------------------------------------------------------- +{ + updateCache(); +} + + +//----------------------------------------------------------------------- +void SMBUrl::addPath(const QString &filedir) +{ + KURL::addPath(filedir); + updateCache(); +} + +//----------------------------------------------------------------------- +bool SMBUrl::cd(const QString &filedir) +{ + if (!KURL::cd(filedir)) + return false; + updateCache(); + return true; +} + +//----------------------------------------------------------------------- +void SMBUrl::updateCache() + //----------------------------------------------------------------------- +{ + cleanPath(); + + // SMB URLs are UTF-8 encoded + kdDebug(KIO_SMB) << "updateCache " << KURL::path() << endl; + if (KURL::url() == "smb:/") + m_surl = "smb://"; + else { + QString surl = "smb://"; + if (KURL::hasUser()) { + surl += KURL::encode_string(KURL::user(), 106); + if (KURL::hasPass()) { + surl += ":" + KURL::encode_string(KURL::pass(), 106); + } + surl += "@"; + } + surl += KURL::encode_string(KURL::host().upper(), 106); + surl += KURL::encode_string(KURL::path(), 106); + m_surl = surl.utf8(); + } + m_type = SMBURLTYPE_UNKNOWN; + // update m_type + (void)getType(); +} + +//----------------------------------------------------------------------- +SMBUrlType SMBUrl::getType() const + // Returns the type of this SMBUrl: + // SMBURLTYPE_UNKNOWN - Type could not be determined. Bad SMB Url. + // SMBURLTYPE_ENTIRE_NETWORK - "smb:/" is entire network + // SMBURLTYPE_WORKGROUP_OR_SERVER - "smb:/mygroup" or "smb:/myserver" + // SMBURLTYPE_SHARE_OR_PATH - "smb:/mygroupe/mymachine/myshare/mydir" + //----------------------------------------------------------------------- +{ + if(m_type != SMBURLTYPE_UNKNOWN) + return m_type; + + if (protocol() != "smb") + { + m_type = SMBURLTYPE_UNKNOWN; + return m_type; + } + + if (path(1) == "/") + { + if (host().isEmpty()) + m_type = SMBURLTYPE_ENTIRE_NETWORK; + else + m_type = SMBURLTYPE_WORKGROUP_OR_SERVER; + return m_type; + } + + // Check for the path if we get this far + m_type = SMBURLTYPE_SHARE_OR_PATH; + + return m_type; +} + diff --git a/kioslave/smb/kio_smb_internal.h b/kioslave/smb/kio_smb_internal.h new file mode 100644 index 000000000..aabfc7ab7 --- /dev/null +++ b/kioslave/smb/kio_smb_internal.h @@ -0,0 +1,118 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB kioslave for KDE2 +// +// File: kio_smb_internal.h +// +// Abstract: Utility classes used by SMBSlave +// +// Author(s): Matthew Peterson <mpeterson@caldera.com> +// Frank Schwanz <schwanz@fh-brandenburg.de> +//--------------------------------------------------------------------------- +// +// Copyright (c) 2000 Caldera Systems, Inc. +// +// 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.1 of the License, or +// (at your option) any later version. +// +// This program 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; see the file COPYING. If not, please obtain +// a copy from http://www.gnu.org/copyleft/gpl.html +// +///////////////////////////////////////////////////////////////////////////// + +#ifndef KIO_SMB_INTERNAL_H_INCLUDED +#define KIO_SMB_INTERNAL_H_INCLUDED + +#include <kio/authinfo.h> + +/** + * Types of a SMBURL : + * SMBURLTYPE_UNKNOWN - Type could not be determined. Bad SMB Url. + * SMBURLTYPE_ENTIRE_NETWORK - "smb:/" is entire network + * SMBURLTYPE_WORKGROUP_OR_SERVER - "smb:/mygroup" or "smb:/myserver" + * URLTYPE_SHARE_OR_PATH - "smb:/mygroupe/mymachine/myshare/mydir" + */ +enum SMBUrlType { + SMBURLTYPE_UNKNOWN = 0, SMBURLTYPE_ENTIRE_NETWORK = 1, + SMBURLTYPE_WORKGROUP_OR_SERVER = 2, SMBURLTYPE_SHARE_OR_PATH = 3 +}; + + +//=========================================================================== +/** + * Class to handle URL's + * it can convert KURL to smbUrl + * and Handle UserInfo + * it also check the correctness of the URL + */ +class SMBUrl : public KURL +{ + + +public: + SMBUrl(); + SMBUrl(const KURL & kurl); + + /** + * Appends the specified file and dir to this SMBUrl + * "smb://server/share" --> "smb://server/share/filedir" + */ + void addPath(const QString &filedir); + + bool cd(const QString &dir); + + /** + * Returns the type of this SMBUrl: + * SMBURLTYPE_UNKNOWN - Type could not be determined. Bad SMB Url. + * SMBURLTYPE_ENTIRE_NETWORK - "smb:/" is entire network + * SMBURLTYPE_WORKGROUP_OR_SERVER - "smb:/mygroup" or "smb:/myserver" + * URLTYPE_SHARE_OR_PATH - "smb:/mygroupe/mymachine/myshare/mydir" + */ + SMBUrlType getType() const; + + void setPass( const QString& _txt ) { KURL::setPass(_txt); updateCache(); } + void setUser( const QString& _txt ) { KURL::setUser(_txt); updateCache(); } + void setHost( const QString& _txt ) { KURL::setHost(_txt); updateCache(); } + + /** + * Returns the workgroup if it given in url + */ +// QString getWorkgroup() const; + + /** + * Returns path after workgroup + */ +// QString getServerShareDir() const; + + /** + * Return a URL that is suitable for libsmbclient + */ + QCString toSmbcUrl() const { return m_surl; } + +private: + /** + * Change from QString to QCString (MS Windows's character encoding) + */ + QCString fromUnicode( const QString &_str ) const; + + void updateCache(); + QCString m_surl; + + /** + * Type of URL + * @see _SMBUrlType + */ + mutable SMBUrlType m_type; +}; + + +#endif + diff --git a/kioslave/smb/kio_smb_mount.cpp b/kioslave/smb/kio_smb_mount.cpp new file mode 100644 index 000000000..08d62ed39 --- /dev/null +++ b/kioslave/smb/kio_smb_mount.cpp @@ -0,0 +1,211 @@ +/* This file is part of the KDE project + + Copyright (C) 2000 Alexander Neundorf <neundorf@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kio_smb.h" +#include <kstandarddirs.h> +#include <qcstring.h> +#include <unistd.h> +#include <qdir.h> +#include <kprocess.h> + +void SMBSlave::readOutput(KProcess *, char *buffer, int buflen) +{ + mybuf += QString::fromLocal8Bit(buffer, buflen); +} + +void SMBSlave::readStdErr(KProcess *, char *buffer, int buflen) +{ + mystderr += QString::fromLocal8Bit(buffer, buflen); +} + +void SMBSlave::special( const QByteArray & data) +{ + kdDebug(KIO_SMB)<<"Smb::special()"<<endl; + int tmp; + QDataStream stream(data, IO_ReadOnly); + stream >> tmp; + //mounting and umounting are both blocking, "guarded" by a SIGALARM in the future + switch (tmp) + { + case 1: + case 3: + { + QString remotePath, mountPoint, user; + stream >> remotePath >> mountPoint; + + QStringList sl=QStringList::split("/",remotePath); + QString share,host; + if (sl.count()>=2) + { + host=(*sl.at(0)).mid(2); + share=(*sl.at(1)); + kdDebug(KIO_SMB)<<"special() host -"<< host <<"- share -" << share <<"-"<<endl; + } + + remotePath.replace('\\', '/'); // smbmounterplugin sends \\host/share + + kdDebug(KIO_SMB) << "mounting: " << remotePath.local8Bit() << " to " << mountPoint.local8Bit() << endl; + + if (tmp==3) { + if (!KStandardDirs::makeDir(mountPoint)) { + error(KIO::ERR_COULD_NOT_MKDIR, mountPoint); + return; + } + } + mybuf.truncate(0); + mystderr.truncate(0); + + SMBUrl smburl("smb:///"); + smburl.setHost(host); + smburl.setPath("/" + share); + + if ( !checkPassword(smburl) ) + { + finished(); + return; + } + + // using smbmount instead of "mount -t smbfs", because mount does not allow a non-root + // user to do a mount, but a suid smbmnt does allow this + + KProcess proc; + proc.setUseShell(true); // to have the path to smbmnt (which is used by smbmount); see man smbmount + proc << "smbmount"; + + QString options; + + if ( smburl.user().isEmpty() ) + { + user = "guest"; + options = "-o guest"; + } + else + { + options = "-o username=" + KProcess::quote(smburl.user()); + user = smburl.user(); + + if ( ! smburl.pass().isEmpty() ) + options += ",password=" + KProcess::quote(smburl.pass()); + } + + // TODO: check why the control center uses encodings with a blank char, e.g. "cp 1250" + //if ( ! m_default_encoding.isEmpty() ) + //options += ",codepage=" + KProcess::quote(m_default_encoding); + + proc << KProcess::quote(remotePath.local8Bit()); + proc << KProcess::quote(mountPoint.local8Bit()); + proc << options; + + connect(&proc, SIGNAL( receivedStdout(KProcess *, char *, int )), + SLOT(readOutput(KProcess *, char *, int))); + + connect(&proc, SIGNAL( receivedStderr(KProcess *, char *, int )), + SLOT(readStdErr(KProcess *, char *, int))); + + if (!proc.start( KProcess::Block, KProcess::AllOutput )) + { + error(KIO::ERR_CANNOT_LAUNCH_PROCESS, + "smbmount"+i18n("\nMake sure that the samba package is installed properly on your system.")); + return; + } + + kdDebug(KIO_SMB) << "mount exit " << proc.exitStatus() << endl + << "stdout:" << mybuf << endl << "stderr:" << mystderr << endl; + + if (proc.exitStatus() != 0) + { + error( KIO::ERR_COULD_NOT_MOUNT, + i18n("Mounting of share \"%1\" from host \"%2\" by user \"%3\" failed.\n%4") + .arg(share).arg(host).arg(user).arg(mybuf + "\n" + mystderr)); + return; + } + + finished(); + } + break; + case 2: + case 4: + { + QString mountPoint; + stream >> mountPoint; + + KProcess proc; + proc.setUseShell(true); + proc << "smbumount"; + proc << KProcess::quote(mountPoint); + + mybuf.truncate(0); + mystderr.truncate(0); + + connect(&proc, SIGNAL( receivedStdout(KProcess *, char *, int )), + SLOT(readOutput(KProcess *, char *, int))); + + connect(&proc, SIGNAL( receivedStderr(KProcess *, char *, int )), + SLOT(readStdErr(KProcess *, char *, int))); + + if ( !proc.start( KProcess::Block, KProcess::AllOutput ) ) + { + error(KIO::ERR_CANNOT_LAUNCH_PROCESS, + "smbumount"+i18n("\nMake sure that the samba package is installed properly on your system.")); + return; + } + + kdDebug(KIO_SMB) << "smbumount exit " << proc.exitStatus() << endl + << "stdout:" << mybuf << endl << "stderr:" << mystderr << endl; + + if (proc.exitStatus() != 0) + { + error(KIO::ERR_COULD_NOT_UNMOUNT, + i18n("Unmounting of mountpoint \"%1\" failed.\n%2") + .arg(mountPoint).arg(mybuf + "\n" + mystderr)); + return; + } + + if ( tmp == 4 ) + { + bool ok; + + QDir dir(mountPoint); + dir.cdUp(); + ok = dir.rmdir(mountPoint); + if ( ok ) + { + QString p=dir.path(); + dir.cdUp(); + ok = dir.rmdir(p); + } + + if ( !ok ) + { + error(KIO::ERR_COULD_NOT_RMDIR, mountPoint); + return; + } + } + + finished(); + } + break; + default: + break; + } + finished(); +} + +#include "kio_smb.moc" diff --git a/kioslave/smb/libsmbclient-HOWTO.txt b/kioslave/smb/libsmbclient-HOWTO.txt new file mode 100644 index 000000000..65ed7d086 --- /dev/null +++ b/kioslave/smb/libsmbclient-HOWTO.txt @@ -0,0 +1,11 @@ +HOWTO get the libsmbclient built, so that you can get the new smb ioslave built... + +1. Check out the samba sources from cvs with ( full directions are at: http://www.samba.org/samba/cvs.html + cvs -d :pserver:cvs@pserver.samba.org:/cvsroot login + use "cvs" as the password + cvs -z5 -d :pserver:cvs@pserver.samba.org:/cvsroot co -r SAMBA_3_0 samba/source +2. cd samba/source +3. sh autogen.sh +4. run ./configure && make +5. when compiling is done- shouldn't take more than 5-15 minutes depending on your machine, + you need to "make installclientlib installdat" - if you want all of samba, do "make install" diff --git a/kioslave/smb/smb-network.desktop b/kioslave/smb/smb-network.desktop new file mode 100644 index 000000000..de5406775 --- /dev/null +++ b/kioslave/smb/smb-network.desktop @@ -0,0 +1,80 @@ +[Desktop Entry] +Icon=network +Name=Samba Shares +Name[af]=Samba Hulpbronne +Name[ar]=مشاركات Samba +Name[az]=Samba SahÉ™lÉ™ri +Name[be]=ÐÐ³ÑƒÐ»ÑŒÐ½Ñ‹Ñ Ñ‚Ñчкі Samba +Name[bg]=РеÑурÑи на Samba +Name[bn]=সামà§à¦¬à¦¾ শেয়ার +Name[br]=Rannadoù Samba +Name[bs]=Samba dijeljenje +Name[ca]=Recurs compartit de Samba +Name[cs]=SdÃlené disky Samby +Name[csb]=Dostónczi Sambë +Name[cy]=Cydranniad Samba +Name[da]=Samba-shares +Name[de]=Samba-Freigaben +Name[el]=ΚοινόχÏηστοι πόÏοι Samba +Name[eo]=Sambo-opuzaĵoj +Name[es]=Comparticiones Samba +Name[et]=SMB jagatud ressursid +Name[eu]=Samba partekaketak +Name[fa]=مشترکات Samba +Name[fi]=Samba-jaot +Name[fr]=Partages Samba +Name[fy]=Sambanetwurk +Name[ga]=Comhranna Samba +Name[gl]=Comparticións de Samba +Name[he]=שיתופי Samba +Name[hi]=सामà¥à¤¬à¤¾ साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Name[hr]=Samba dijeljenja +Name[hu]=Samba-megosztások +Name[is]=Samba sameignir +Name[it]=Condivisioni Samba +Name[ja]=Samba 共有 +Name[ka]=Samba-ს სáƒáƒ–იáƒáƒ áƒáƒ”ბი +Name[kk]=Samba реÑурÑтары +Name[ko]=삼바 ê³µìœ +Name[lo]=ສັບພະàºàº²àºàºàº™àº—ີ່ໃຊ້ຮ່ວມàºàº±àº™àº‚àºàº‡ Samba +Name[lt]=Samba bendri diskai +Name[lv]=Samba Å Äres +Name[mk]=Samba-заеднички +Name[mn]=Samba-Ðөөцүүд +Name[ms]=Perkongsian Samba +Name[mt]=Riżorsi Samba +Name[nb]=Samba-ressurser +Name[nds]=Samba-Freegaven +Name[ne]=सामà¥à¤¬à¤¾ साà¤à¥‡à¤¦à¤¾à¤°à¥€ +Name[nl]=Sambanetwerk +Name[nn]=Samba-ressursar +Name[nso]=Dikabagano tsa Samba +Name[pa]=ਸਾਂਬਾ ਸਾਂਠ+Name[pl]=Zasoby Samby +Name[pt]=Partilhas de Samba +Name[pt_BR]=Compartilhamentos do Samba +Name[ro]=Partajări Samba +Name[ru]=РеÑурÑÑ‹ Samba +Name[rw]=Imigabane Samba +Name[se]=Samba-resurssat +Name[sk]=Stav Samby +Name[sl]=Souporabe Sambe +Name[sr]=Samba дељења +Name[sr@Latn]=Samba deljenja +Name[sv]=Samba-utdelningar +Name[ta]=சமà¯à®ªà®¾ பஙà¯à®•à¯à®•à®³à¯ +Name[tg]=Иштирокоти Samba +Name[th]=ทรัพยาà¸à¸£ Samba ที่ใช้ร่วมà¸à¸±à¸™ +Name[tr]=Samba Payları +Name[tt]=Samba Urtaqları +Name[uk]=Спільні реÑурÑи Samba +Name[ven]=Mikovhe ya Samba +Name[vi]=Chia sẻ Samba +Name[wa]=PÃ¥rtaedjes Samba +Name[xh]=Izahlulo ze Samba +Name[zh_CN]=Samba 共享 +Name[zh_TW]=Samba 資æºåˆ†äº« +Name[zu]=Izabelo ze-Samba +Open=false +Type=Link +URL=smb:/ diff --git a/kioslave/smb/smb.protocol b/kioslave/smb/smb.protocol new file mode 100644 index 000000000..dbfca9129 --- /dev/null +++ b/kioslave/smb/smb.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=kio_smb +protocol=smb +input=none +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group +reading=true +writing=true +makedir=true +deleting=true +DocPath=kioslave/smb.html +Icon=samba diff --git a/kioslave/smb/x-smb-server.desktop b/kioslave/smb/x-smb-server.desktop new file mode 100644 index 000000000..2daee7cf5 --- /dev/null +++ b/kioslave/smb/x-smb-server.desktop @@ -0,0 +1,76 @@ +[Desktop Entry] +Comment=Windows Server +Comment[af]=Windows Bediener +Comment[ar]=خادم Windows +Comment[be]=Сервер Windows +Comment[bg]=Сървър Windows +Comment[bn]=উইণà§à¦¡à§‹à¦¸ সারà§à¦à¦¾à¦° +Comment[br]=Servijer Windows +Comment[bs]=Windows server +Comment[ca]=Servidor Windows +Comment[cs]=Windows server +Comment[csb]=Serwera Windowsa +Comment[cy]=Grŵp Gwaith Windows +Comment[da]=Windows server +Comment[de]=Windows-Server +Comment[el]=Διακομιστής των Windows +Comment[eo]=Vindoza servilo +Comment[es]=Servidor Windows +Comment[et]=Windowsi server +Comment[eu]=Windows zerbitzaria +Comment[fa]=کارساز ویندوز +Comment[fi]=Windows-palvelin +Comment[fr]=Serveur Windows +Comment[fy]=Windows-tsjinner +Comment[ga]=Freastalaà Windows +Comment[he]=שרת Windows +Comment[hi]=विंडोज़ सरà¥à¤µà¤° +Comment[hr]=Windows poslužitelj +Comment[hu]=Windows kiszolgáló +Comment[is]=Windows þjónn +Comment[it]=Server Windows +Comment[ja]=Windows サーム+Comment[ka]=Windows სერვერი +Comment[kk]=Windows Ñервері +Comment[km]=ម៉ាស៊ីន​បម្រើ Windows +Comment[ko]=ìœˆë„ ê³µìœ +Comment[lt]=Windows serveris +Comment[lv]=Windows serveris +Comment[mk]=Windows-Ñервер +Comment[ms]=Pelayan Windows +Comment[mt]=Server tal-Windows +Comment[nb]=Windows-tjener +Comment[nds]=Windows-Server +Comment[ne]=सञà¥à¤à¥à¤¯à¤¾à¤² सरà¥à¤à¤° +Comment[nl]=Windows-server +Comment[nn]=Windows-tenar +Comment[pa]=ਵਿੰਡੋ ਸਰਵਰ +Comment[pl]=Serwer Windows +Comment[pt]=Servidor de Windows +Comment[pt_BR]=Servidor Windows +Comment[ro]=Servere Windows +Comment[ru]=Сервер Windows +Comment[rw]=Windows Seriveri +Comment[se]=Windows-bálvá +Comment[sk]=Server Windows +Comment[sl]=Strežnik za Windows +Comment[sr]=Windows Ñервер +Comment[sr@Latn]=Windows server +Comment[sv]=Windows server +Comment[ta]=சாளர சேவகன௠+Comment[te]=విండొసౠసెరà±à°µà°°à± +Comment[tg]=Хидматгоҳи Windows +Comment[th]=เครื่à¸à¸‡à¹ƒà¸«à¹‰à¸šà¸£à¸´à¸à¸²à¸£à¸§à¸´à¸™à¹‚ดว์ส +Comment[tr]=Windows Sunucusu +Comment[uk]=Сервер Windows +Comment[uz]=Windows serveri +Comment[uz@cyrillic]=Windows Ñервери +Comment[vi]=Máy phục vụ Windows +Comment[wa]=Sierveu Windows +Comment[zh_CN]=Windows æœåŠ¡å™¨ +Comment[zh_TW]=視窗伺æœå™¨ +Icon=server +Type=MimeType +MimeType=application/x-smb-server +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/smb/x-smb-workgroup.desktop b/kioslave/smb/x-smb-workgroup.desktop new file mode 100644 index 000000000..c1ad4b95f --- /dev/null +++ b/kioslave/smb/x-smb-workgroup.desktop @@ -0,0 +1,74 @@ +[Desktop Entry] +Comment=Windows Workgroup +Comment[af]=Windows Werkgroep +Comment[ar]=مجموعة عمل Windows +Comment[be]=ÐŸÑ€Ð°Ñ†Ð¾ÑžÐ½Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð° Windows +Comment[bg]=Работна група на Windows +Comment[bn]=উইণà§à¦¡à§‹à¦¸ ওয়ারà§à¦•à¦—à§à¦°à§à¦ª +Comment[br]=Strollad labour Windows +Comment[bs]=Windows radna grupa +Comment[ca]=Grup de treball de Windows +Comment[cs]=Pracovnà skupina Windows +Comment[csb]=Robòczé karno Windowsa +Comment[cy]=Gr?p Gwaith Windows +Comment[de]=Windows-Arbeitsgruppe +Comment[el]=Ομάδα εÏγασίας Windows +Comment[eo]=Vindoza laborgrupo +Comment[es]=Grupo de trabajo de Windows +Comment[et]=Windowsi töögrupp +Comment[eu]=Windows lan taldea +Comment[fa]=گروه کاری ویندوز +Comment[fi]=Windows-työryhmä +Comment[fr]=Groupe de travail Windows +Comment[fy]=Windows-wurkkeppel +Comment[ga]=Grúpa Oibre Windows +Comment[gl]=Grupo de Traballo de Windows +Comment[he]=קבוצת עבודה של ×—×œ×•× ×•×ª +Comment[hi]=विंडोज़ वरà¥à¤• बà¥à¤• +Comment[hr]=Windows radna grupa +Comment[hu]=Windows-os munkacsoport +Comment[is]=Windows vinnuhópur +Comment[it]=Gruppo di lavoro di Windows +Comment[ja]=Windows ワークグループ +Comment[ka]=Windows სáƒáƒ›áƒ£áƒ¨áƒáƒ გჯუფი +Comment[kk]=Windows Ð¶Ò±Ð¼Ñ‹Ñ Ñ‚Ð¾Ð±Ñ‹ +Comment[lt]=Windows darbo grupÄ— +Comment[lv]=Windows darba grupa +Comment[mk]=Windows работна група +Comment[mn]=Цонхтой ажиллах бүлÑг +Comment[ms]=Kumpulan Kerja Windows +Comment[mt]=Workgroup tal-Windows +Comment[nb]=Windows-arbeidsgruppe +Comment[nds]=Windows-Arbeitkoppel +Comment[ne]=सञà¥à¤à¥à¤¯à¤¾à¤² कारà¥à¤¯ समूह +Comment[nl]=Windows-werkgroep +Comment[nn]=Windows-arbeidsgruppe +Comment[pa]=ਵਿੰਡੋ ਵਰਕਗਰà©à©±à¨ª +Comment[pl]=Grupa robocza Windows +Comment[pt]=Grupos de Trabalho do Windows +Comment[pt_BR]=Grupo de trabalho do Windows +Comment[ro]=Grupuri de lucru Windows +Comment[ru]=Ð Ð°Ð±Ð¾Ñ‡Ð°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð° Windows +Comment[rw]=Itsindaumurimo Windows +Comment[se]=Windows-bargojoavku +Comment[sk]=Pracovná skupina Windows +Comment[sl]=Delovna skupina Windows +Comment[sv]=Windows arbetsgrupp +Comment[ta]=சாளரஙà¯à®•à®³à¯ பணிகà¯à®´à¯ +Comment[te]=విండొసౠవరà±à°•à± à°—à±à°°à±‚పౠ+Comment[tg]=Гурӯҳи кори Windows +Comment[th]=à¸à¸¥à¸¸à¹ˆà¸¡à¸‡à¸²à¸™à¸§à¸´à¸™à¹‚ดว์ส +Comment[tr]=Windows Çalışma Grubu +Comment[tt]=Windows EÅŸtörkeme +Comment[uk]=Робоча група Windows +Comment[uz]=Windows ishchi guruhi +Comment[uz@cyrillic]=Windows ишчи гуруҳи +Comment[vi]=Nhóm là m việc Windows +Comment[wa]=Groupe d' ovraedje Windows +Comment[zh_CN]=Windows 工作组 +Comment[zh_TW]=視窗工作群組 +Icon=network_local +Type=MimeType +MimeType=application/x-smb-workgroup +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/smtp/Makefile.am b/kioslave/smtp/Makefile.am new file mode 100644 index 000000000..4ebe8ddd8 --- /dev/null +++ b/kioslave/smtp/Makefile.am @@ -0,0 +1,37 @@ + +INCLUDES= -I$(srcdir)/../.. -I$(srcdir)/.. $(SSL_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kio_smtp.la + +kio_smtp_la_SOURCES = smtp.cc request.cc response.cc capabilities.cc command.cc transactionstate.cc +kio_smtp_la_LIBADD = $(LIB_KIO) $(SASL2_LIBS) +kio_smtp_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = smtp.h request.h response.h capabilities.h command.h transactionstate.h + +kdelnk_DATA = smtp.protocol smtps.protocol +kdelnkdir = $(kde_servicesdir) + +TESTS = test_headergeneration test_responseparser test_commands + +check_PROGRAMS = $(TESTS) interactivesmtpserver + +test_headergeneration_SOURCES = test_headergeneration.cc +test_headergeneration_LDADD = $(LIB_KDECORE) +test_headergeneration_LDFLAGS = $(all_libraries) + +test_responseparser_SOURCES = test_responseparser.cc +test_responseparser_LDADD = $(LIB_KDECORE) +test_responseparser_LDFLAGS = $(all_libraries) + +test_commands_SOURCES = test_commands.cc +test_commands_LDADD = $(kio_smtp_la_LIBADD) +test_commands_LDFLAGS = $(all_libraries) + +interactivesmtpserver_SOURCES = interactivesmtpserver.cc +interactivesmtpserver_LDADD = $(LIB_QT) +interactivesmtpserver_LDFLAGS = $(all_libraries) +interactivesmtpserver_METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cc -o $(podir)/kio_smtp.pot diff --git a/kioslave/smtp/TODO b/kioslave/smtp/TODO new file mode 100644 index 000000000..cad79f139 --- /dev/null +++ b/kioslave/smtp/TODO @@ -0,0 +1,11 @@ +1. Double check the error handling and review error message in various + failure modes. +2. Implement the CHUNKING extension (rfc 3030; as soon as I find an + SMTP server that supports it). +3. Better error message (translated standard meanings of the known + response codes, ENHANCEDSTATUSCODES extension (rfc2034)). +4. (KIO) MultiPutJob to support pipelining across messages. +5. Ged rid of slave's header generation after checking who on earth + uses that... + +and further refactoring to make the code pleasant to look at ;-) diff --git a/kioslave/smtp/capabilities.cc b/kioslave/smtp/capabilities.cc new file mode 100644 index 000000000..a26626ce1 --- /dev/null +++ b/kioslave/smtp/capabilities.cc @@ -0,0 +1,143 @@ +/* -*- c++ -*- + capabilities.cc + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <config.h> + +#include "capabilities.h" + +#include "response.h" + +#include <qstrlist.h> + +namespace KioSMTP { + + Capabilities Capabilities::fromResponse( const Response & ehlo ) { + Capabilities c; + + // first, check whether the response was valid and indicates success: + if ( !ehlo.isOk() + || ehlo.code() / 10 != 25 // ### restrict to 250 only? + || ehlo.lines().empty() ) + return c; + + QCStringList l = ehlo.lines(); + + for ( QCStringList::const_iterator it = ++l.begin() ; it != l.end() ; ++it ) + c.add( *it ); + + return c; + } + + void Capabilities::add( const QString & cap, bool replace ) { + QStringList tokens = QStringList::split( ' ', cap.upper() ); + if ( tokens.empty() ) + return; + QString name = tokens.front(); tokens.pop_front(); + add( name, tokens, replace ); + } + + void Capabilities::add( const QString & name, const QStringList & args, bool replace ) { + if ( replace ) + mCapabilities[name] = args; + else + mCapabilities[name] += args; + } + + QString Capabilities::asMetaDataString() const { + QString result; + for ( QMap<QString,QStringList>::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { + result += it.key(); + if ( !it.data().empty() ) + result += ' ' + it.data().join( " " ); + result += '\n'; + } + return result; + } + + QString Capabilities::authMethodMetaData() const { + QStringList sl = saslMethodsQSL(); + QString result; + for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) + result += "SASL/" + *it + '\n'; + return result; + } + + QStrIList Capabilities::saslMethods() const { + QStrIList result( true ); // deep copies to be safe + QStringList sl = saslMethodsQSL(); + for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) + result.append( (*it).latin1() ); + return result; + } + + QString Capabilities::createSpecialResponse( bool tls ) const { + QStringList result; + if ( tls ) + result.push_back( "STARTTLS" ); + result += saslMethodsQSL(); + if ( have( "PIPELINING" ) ) + result.push_back( "PIPELINING" ); + if ( have( "8BITMIME" ) ) + result.push_back( "8BITMIME" ); + if ( have( "SIZE" ) ) { + bool ok = false; + unsigned int size = mCapabilities["SIZE"].front().toUInt( &ok ); + if ( ok && !size ) + result.push_back( "SIZE=*" ); // any size + else if ( ok ) + result.push_back( "SIZE=" + QString::number( size ) ); // fixed max + else + result.push_back( "SIZE" ); // indetermined + } + return result.join( " " ); + } + + QStringList Capabilities::saslMethodsQSL() const { + QStringList result; + for ( QMap<QString,QStringList>::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { + if ( it.key() == "AUTH" ) + result += it.data(); + else if ( it.key().startsWith( "AUTH=" ) ) { + result.push_back( it.key().mid( qstrlen("AUTH=") ) ); + result += it.data(); + } + } + result.sort(); + QStringList::iterator it = result.begin(); + for (QStringList::iterator ot = it++; it != result.end(); ot = it++) + if (*ot == *it) result.remove(ot); + return result; + } + + + +} // namespace KioSMTP + diff --git a/kioslave/smtp/capabilities.h b/kioslave/smtp/capabilities.h new file mode 100644 index 000000000..1ae5972aa --- /dev/null +++ b/kioslave/smtp/capabilities.h @@ -0,0 +1,77 @@ +/* -*- c++ -*- + capabilities.h + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KIOSMTP_CAPABILITIES_H__ +#define __KIOSMTP_CAPABILITIES_H__ + +#include <qmap.h> +#include <qcstring.h> +#include <qstring.h> +#include <qstringlist.h> + +class QStrIList; + +namespace KioSMTP { + + class Response; + + class Capabilities { + public: + Capabilities() {} + + static Capabilities fromResponse( const Response & response ); + + void add( const QString & cap, bool replace=false ); + void add( const QString & name, const QStringList & args, bool replace=false ); + void clear() { mCapabilities.clear(); } + + bool have( const QString & cap ) const { + return mCapabilities.find( cap.upper() ) != mCapabilities.end(); + } + bool have( const QCString & cap ) const { return have( QString( cap.data() ) ); } + bool have( const char * cap ) const { return have( QString::fromLatin1( cap ) ); } + + QString asMetaDataString() const; + + QString authMethodMetaData() const; + QStrIList saslMethods() const; + + QString createSpecialResponse( bool tls ) const; + + QStringList saslMethodsQSL() const; + private: + + QMap<QString,QStringList> mCapabilities; + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_CAPABILITIES_H__ diff --git a/kioslave/smtp/command.cc b/kioslave/smtp/command.cc new file mode 100644 index 000000000..9fb7281c9 --- /dev/null +++ b/kioslave/smtp/command.cc @@ -0,0 +1,606 @@ +/* -*- c++ -*- + command.cc + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <config.h> + +#include "command.h" + +#include "smtp.h" +#include "response.h" +#include "transactionstate.h" + +#include <kidna.h> +#include <klocale.h> +#include <kdebug.h> +#include <kmdcodec.h> +#include <kio/slavebase.h> // for test_commands, where SMTPProtocol is not derived from TCPSlaveBase + +#include <assert.h> + +namespace KioSMTP { + +#ifdef HAVE_LIBSASL2 +static sasl_callback_t callbacks[] = { + { SASL_CB_ECHOPROMPT, NULL, NULL }, + { SASL_CB_NOECHOPROMPT, NULL, NULL }, + { SASL_CB_GETREALM, NULL, NULL }, + { SASL_CB_USER, NULL, NULL }, + { SASL_CB_AUTHNAME, NULL, NULL }, + { SASL_CB_PASS, NULL, NULL }, + { SASL_CB_CANON_USER, NULL, NULL }, + { SASL_CB_LIST_END, NULL, NULL } +}; +#endif + + // + // Command (base class) + // + + Command::Command( SMTPProtocol * smtp, int flags ) + : mSMTP( smtp ), + mComplete( false ), mNeedResponse( false ), mFlags( flags ) + { + assert( smtp ); + } + + Command::~Command() {} + + bool Command::processResponse( const Response & r, TransactionState * ) { + mComplete = true; + mNeedResponse = false; + return r.isOk(); + } + + void Command::ungetCommandLine( const QCString &, TransactionState * ) { + mComplete = false; + } + + Command * Command::createSimpleCommand( int which, SMTPProtocol * smtp ) { + switch ( which ) { + case STARTTLS: return new StartTLSCommand( smtp ); + case DATA: return new DataCommand( smtp ); + case NOOP: return new NoopCommand( smtp ); + case RSET: return new RsetCommand( smtp ); + case QUIT: return new QuitCommand( smtp ); + default: return 0; + } + } + + // + // relay methods: + // + + void Command::parseFeatures( const Response & r ) { + mSMTP->parseFeatures( r ); + } + + int Command::startTLS() { + return mSMTP->startTLS(); + } + + bool Command::usingSSL() const { + return mSMTP->usingSSL(); + } + + bool Command::usingTLS() const { + return mSMTP->usingTLS(); + } + + bool Command::haveCapability( const char * cap ) const { + return mSMTP->haveCapability( cap ); + } + + // + // EHLO / HELO + // + + QCString EHLOCommand::nextCommandLine( TransactionState * ) { + mNeedResponse = true; + mComplete = mEHLONotSupported; + const char * cmd = mEHLONotSupported ? "HELO " : "EHLO " ; + return cmd + KIDNA::toAsciiCString( mHostname ) + "\r\n"; + } + + bool EHLOCommand::processResponse( const Response & r, TransactionState * ) { + mNeedResponse = false; + // "command not {recognized,implemented}" response: + if ( r.code() == 500 || r.code() == 502 ) { + if ( mEHLONotSupported ) { // HELO failed... + mSMTP->error( KIO::ERR_INTERNAL_SERVER, + i18n("The server rejected both EHLO and HELO commands " + "as unknown or unimplemented.\n" + "Please contact the server's system administrator.") ); + return false; + } + mEHLONotSupported = true; // EHLO failed, but that's ok. + return true; + } + mComplete = true; + if ( r.code() / 10 == 25 ) { // 25x: success + parseFeatures( r ); + return true; + } + mSMTP->error( KIO::ERR_UNKNOWN, + i18n("Unexpected server response to %1 command.\n%2") + .arg( mEHLONotSupported ? "HELO" : "EHLO" ) + .arg( r.errorMessage() ) ); + return false; + } + + // + // STARTTLS - rfc 3207 + // + + QCString StartTLSCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "STARTTLS\r\n"; + } + + bool StartTLSCommand::processResponse( const Response & r, TransactionState * ) { + mNeedResponse = false; + if ( r.code() != 220 ) { + mSMTP->error( r.errorCode(), + i18n("Your SMTP server does not support TLS. " + "Disable TLS, if you want to connect " + "without encryption.") ); + return false; + } + + int tlsrc = startTLS(); + + if ( tlsrc == 1 ) + return true; + + if ( tlsrc != -3 ) + //kdDebug(7112) << "TLS negotiation failed!" << endl; + mSMTP->messageBox(KIO::SlaveBase::Information, + i18n("Your SMTP server claims to " + "support TLS, but negotiation " + "was unsuccessful.\nYou can " + "disable TLS in KDE using the " + "crypto settings module."), + i18n("Connection Failed")); + return false; + } + + +#define SASLERROR mSMTP->error(KIO::ERR_COULD_NOT_AUTHENTICATE, \ + i18n("An error occured during authentication: %1").arg \ + ( QString::fromUtf8( sasl_errdetail( conn ) ))); + + // + // AUTH - rfc 2554 + // + AuthCommand::AuthCommand( SMTPProtocol * smtp, + const char *mechanisms, + const QString &aFQDN, + KIO::AuthInfo &ai ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ), + mAi( &ai ), + mFirstTime( true ) + { +#ifdef HAVE_LIBSASL2 + int result; + mMechusing = 0; + conn = 0; + client_interact = 0; + mOut = 0; mOutlen = 0; + mOneStep = false; + + result = sasl_client_new( "smtp", aFQDN.latin1(), + 0, 0, callbacks, 0, &conn ); + if ( result != SASL_OK ) { + SASLERROR + return; + } + do { + result = sasl_client_start(conn, mechanisms, + &client_interact, &mOut, &mOutlen, &mMechusing); + + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact ) ) { + return; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + SASLERROR + return; + } + if ( result == SASL_OK ) mOneStep = true; + kdDebug(7112) << "Mechanism: " << mMechusing << " one step: " << mOneStep << endl; +#else + mSMTP->error(KIO::ERR_COULD_NOT_AUTHENTICATE, + i18n("Authentication support is not compiled into kio_smtp.")); +#endif + } + + AuthCommand::~AuthCommand() + { +#ifdef HAVE_LIBSASL2 + if ( conn ) { + kdDebug(7112) << "dispose sasl connection" << endl; + sasl_dispose( &conn ); + conn = 0; + } +#endif + } + + bool AuthCommand::saslInteract( void *in ) + { +#ifdef HAVE_LIBSASL2 + kdDebug(7112) << "saslInteract: " << endl; + sasl_interact_t *interact = ( sasl_interact_t * ) in; + + //some mechanisms do not require username && pass, so don't need a popup + //window for getting this info + for ( ; interact->id != SASL_CB_LIST_END; interact++ ) { + if ( interact->id == SASL_CB_AUTHNAME || + interact->id == SASL_CB_PASS ) { + + if ( mAi->username.isEmpty() || mAi->password.isEmpty()) { + if (!mSMTP->openPassDlg(*mAi)) { + mSMTP->error(KIO::ERR_ABORTED, i18n("No authentication details supplied.")); + return false; + } + } + break; + } + } + + interact = ( sasl_interact_t * ) in; + while( interact->id != SASL_CB_LIST_END ) { + switch( interact->id ) { + case SASL_CB_USER: + case SASL_CB_AUTHNAME: + kdDebug(7112) << "SASL_CB_[USER|AUTHNAME]: " << mAi->username << endl; + interact->result = strdup( mAi->username.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + case SASL_CB_PASS: + kdDebug(7112) << "SASL_CB_PASS: [HIDDEN]" << endl; + interact->result = strdup( mAi->password.utf8() ); + interact->len = strlen( (const char *) interact->result ); + break; + default: + interact->result = NULL; interact->len = 0; + break; + } + interact++; + } + return true; +#else + return false; +#endif + } + + bool AuthCommand::doNotExecute( const TransactionState * ) const { + return !mMechusing; + } + + void AuthCommand::ungetCommandLine( const QCString & s, TransactionState * ) { + mUngetSASLResponse = s; + mComplete = false; + } + + QCString AuthCommand::nextCommandLine( TransactionState * ) { + mNeedResponse = true; + QCString cmd; +#ifdef HAVE_LIBSASL2 + QByteArray tmp, challenge; + if ( !mUngetSASLResponse.isNull() ) { + // implement un-ungetCommandLine + cmd = mUngetSASLResponse; + mUngetSASLResponse = 0; + } else if ( mFirstTime ) { + QString firstCommand = "AUTH " + QString::fromLatin1( mMechusing ); + + tmp.setRawData( mOut, mOutlen ); + KCodecs::base64Encode( tmp, challenge ); + tmp.resetRawData( mOut, mOutlen ); + if ( !challenge.isEmpty() ) { + firstCommand += " "; + firstCommand += QString::fromLatin1( challenge.data(), challenge.size() ); + } + cmd = firstCommand.latin1(); + + if ( mOneStep ) mComplete = true; + } else { +// kdDebug(7112) << "SS: '" << mLastChallenge << "'" << endl; + tmp.setRawData( mLastChallenge.data(), mLastChallenge.length() ); + KCodecs::base64Decode( tmp, challenge ); + tmp.resetRawData( mLastChallenge.data(), mLastChallenge.length() ); + int result; + do { + result = sasl_client_step(conn, challenge.isEmpty() ? 0 : challenge.data(), + challenge.size(), + &client_interact, + &mOut, &mOutlen); + if (result == SASL_INTERACT) + if ( !saslInteract( client_interact ) ) { + return ""; + }; + } while ( result == SASL_INTERACT ); + if ( result != SASL_CONTINUE && result != SASL_OK ) { + kdDebug(7112) << "sasl_client_step failed with: " << result << endl; + SASLERROR + return ""; + } + tmp.setRawData( mOut, mOutlen ); + cmd = KCodecs::base64Encode( tmp ); + tmp.resetRawData( mOut, mOutlen ); + +// kdDebug(7112) << "CC: '" << cmd << "'" << endl; + mComplete = ( result == SASL_OK ); + } +#endif //HAVE_LIBSASL2 + cmd += "\r\n"; + return cmd; + } + + bool AuthCommand::processResponse( const Response & r, TransactionState * ) { + if ( !r.isOk() ) { + if ( mFirstTime ) + if ( haveCapability( "AUTH" ) ) + mSMTP->error( KIO::ERR_COULD_NOT_LOGIN, + i18n("Your SMTP server does not support %1.\nChoose a different authentication method.\n%2") + .arg( mMechusing ).arg( r.errorMessage() ) ); + else + mSMTP->error( KIO::ERR_COULD_NOT_LOGIN, + i18n("Your SMTP server does not support authentication.\n" + " %2").arg( r.errorMessage() ) ); + else + mSMTP->error( KIO::ERR_COULD_NOT_LOGIN, + i18n("Authentication failed.\n" + "Most likely the password is wrong.\n" + "%1").arg( r.errorMessage() ) ); + return false; + } + mFirstTime = false; + mLastChallenge = r.lines().front(); // ### better join all lines with \n? + mNeedResponse = false; + return true; + } + + // + // MAIL FROM: + // + + QCString MailFromCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + QCString cmdLine = "MAIL FROM:<" + mAddr + '>'; + if ( m8Bit && haveCapability("8BITMIME") ) + cmdLine += " BODY=8BITMIME"; + if ( mSize && haveCapability("SIZE") ) + cmdLine += " SIZE=" + QCString().setNum( mSize ); + return cmdLine + "\r\n"; + } + + bool MailFromCommand::processResponse( const Response & r, TransactionState * ts ) { + assert( ts ); + mNeedResponse = false; + + if ( r.code() == 250 ) + return true; + + ts->setMailFromFailed( mAddr, r ); + return false; + } + + // + // RCPT TO: + // + + QCString RcptToCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "RCPT TO:<" + mAddr + ">\r\n"; + } + + bool RcptToCommand::processResponse( const Response & r, TransactionState * ts ) { + assert( ts ); + mNeedResponse = false; + + if ( r.code() == 250 ) { + ts->setRecipientAccepted(); + return true; + } + + ts->addRejectedRecipient( mAddr, r.errorMessage() ); + return false; + } + + // + // DATA (only initial processing!) + // + + QCString DataCommand::nextCommandLine( TransactionState * ts ) { + assert( ts ); + mComplete = true; + mNeedResponse = true; + ts->setDataCommandIssued( true ); + return "DATA\r\n"; + } + + void DataCommand::ungetCommandLine( const QCString &, TransactionState * ts ) { + assert( ts ); + mComplete = false; + ts->setDataCommandIssued( false ); + } + + bool DataCommand::processResponse( const Response & r, TransactionState * ts ) { + assert( ts ); + mNeedResponse = false; + + if ( r.code() == 354 ) { + ts->setDataCommandSucceeded( true, r ); + return true; + } + + ts->setDataCommandSucceeded( false, r ); + return false; + } + + // + // DATA (data transfer) + // + void TransferCommand::ungetCommandLine( const QCString & cmd, TransactionState * ) { + if ( cmd.isEmpty() ) + return; // don't change state when we can't detect the unget in + // the next nextCommandLine !! + mWasComplete = mComplete; + mComplete = false; + mNeedResponse = false; + mUngetBuffer = cmd; + } + + bool TransferCommand::doNotExecute( const TransactionState * ts ) const { + assert( ts ); + return ts->failed(); + } + + QCString TransferCommand::nextCommandLine( TransactionState * ts ) { + assert( ts ); // let's rely on it ( at least for the moment ) + assert( !isComplete() ); + assert( !ts->failed() ); + + static const QCString dotCRLF = ".\r\n"; + static const QCString CRLFdotCRLF = "\r\n.\r\n"; + + if ( !mUngetBuffer.isEmpty() ) { + const QCString ret = mUngetBuffer; + mUngetBuffer = 0; + if ( mWasComplete ) { + mComplete = true; + mNeedResponse = true; + } + return ret; // don't prepare(), it's slave-generated or already prepare()d + } + + // normal processing: + + kdDebug(7112) << "requesting data" << endl; + mSMTP->dataReq(); + QByteArray ba; + int result = mSMTP->readData( ba ); + kdDebug(7112) << "got " << result << " bytes" << endl; + if ( result > 0 ) + return prepare( ba ); + else if ( result < 0 ) { + ts->setFailedFatally( KIO::ERR_INTERNAL, + i18n("Could not read data from application.") ); + mComplete = true; + mNeedResponse = true; + return 0; + } + mComplete = true; + mNeedResponse = true; + return mLastChar == '\n' ? dotCRLF : CRLFdotCRLF ; + } + + bool TransferCommand::processResponse( const Response & r, TransactionState * ts ) { + mNeedResponse = false; + assert( ts ); + ts->setComplete(); + if ( !r.isOk() ) { + ts->setFailed(); + mSMTP->error( r.errorCode(), + i18n("The message content was not accepted.\n" + "%1").arg( r.errorMessage() ) ); + return false; + } + return true; + } + + static QCString dotstuff_lf2crlf( const QByteArray & ba, char & last ) { + QCString result( ba.size() * 2 + 1 ); // worst case: repeated "[.]\n" + const char * s = ba.data(); + const char * const send = ba.data() + ba.size(); + char * d = result.data(); + + while ( s < send ) { + const char ch = *s++; + if ( ch == '\n' && last != '\r' ) + *d++ = '\r'; // lf2crlf + else if ( ch == '.' && last == '\n' ) + *d++ = '.'; // dotstuff + last = *d++ = ch; + } + + result.truncate( d - result.data() ); + return result; + } + + QCString TransferCommand::prepare( const QByteArray & ba ) { + if ( ba.isEmpty() ) + return 0; + if ( mSMTP->metaData("lf2crlf+dotstuff") == "slave" ) { + kdDebug(7112) << "performing dotstuffing and LF->CRLF transformation" << endl; + return dotstuff_lf2crlf( ba, mLastChar ); + } else { + mLastChar = ba[ ba.size() - 1 ]; + return QCString( ba.data(), ba.size() + 1 ); + } + } + + // + // NOOP + // + + QCString NoopCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "NOOP\r\n"; + } + + // + // RSET + // + + QCString RsetCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "RSET\r\n"; + } + + // + // QUIT + // + + QCString QuitCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "QUIT\r\n"; + } + +} // namespace KioSMTP + diff --git a/kioslave/smtp/command.h b/kioslave/smtp/command.h new file mode 100644 index 000000000..e67f02556 --- /dev/null +++ b/kioslave/smtp/command.h @@ -0,0 +1,283 @@ +/* -*- c++ -*- + command.h + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KIOSMTP_COMMAND_H__ +#define __KIOSMTP_COMMAND_H__ + + +#include <qstring.h> +#include <qcstring.h> + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include <sasl/sasl.h> +} +#endif + +#include <kio/authinfo.h> + +class SMTPProtocol; +class QStrIList; + +namespace KioSMTP { + + class Response; + class TransactionState; + + /** + * @short Represents an SMTP command + * + * Semantics: A command consists of a series of "command lines" + * (though that's stretching it a bit for @ref TransferJob and @ref + * AuthCommand) and responses. There's typically one response for + * one command line and the command is completed. + * + * However, some commands consist of a dialog (command line, + * response, command line, response,...) where each successive + * command line is dependant on the previously received response + * (and thus those commands are not pipelinable). That's why each + * command signals completion by having it's @ref #isComplete() + * method return true @em after the last command line to be sent, + * but @em before the last response to receive. @ref AuthCommand is + * the principal representative of this kind of command. Because + * @ref EHLOCommand automatically falls back to HELO in case EHLO + * isn't supported, it is also of this kind. If completion is + * signalled before the first command line is issued, it is not to + * be executed at all. + * + * Other commands need to send multiple "command lines" before + * receiving a single (final) response. @ref TransferCommand is the + * only representative of this kind of "command". That's why each + * command signals whether it now expects a response before being + * able to issue the next command line (if any) by having it's @ref + * #needsResponse() method return true. + * + * Commands whose @ref #nextCommandLine() does not support being + * called multiple times in a row without changing command state, + * must reimplement @ref #ungetCommandLine(). + **/ + class Command { + public: + enum Flags { + OnlyLastInPipeline = 1, + OnlyFirstInPipeline = 2, + CloseConnectionOnError = 4 + }; + + Command( SMTPProtocol * smtp, int flags=0 ); + virtual ~Command(); + + enum Type { + STARTTLS, DATA, NOOP, RSET, QUIT + }; + + static Command * createSimpleCommand( int which, SMTPProtocol * smtp ); + + virtual QCString nextCommandLine( TransactionState * ts=0 ) = 0; + /* Reimplement this if your @ref #nextCommandLine() implementation + changes state other than @ref mComplete. The default + implementation just resets @ref mComplete to false. */ + virtual void ungetCommandLine( const QCString & cmdLine, TransactionState * ts=0 ); + /* Reimplement this if your command need more sophisicated + response processing than just checking for @ref + Response::isOk(). The default implementation sets @ref + mComplete to true, @ref mNeedResponse to false and returns + whether the response isOk(). */ + virtual bool processResponse( const Response & response, TransactionState * ts=0 ); + + virtual bool doNotExecute( const TransactionState * ) const { return false; } + + bool isComplete() const { return mComplete; } + /** @return whether the command expects a response now. Some + commands (most notably AUTH) may consist of a series of + commands and associated responses until they are + complete. Others (most notably @ref TransferCommand usually + send multiple "command lines" before expecting a response. */ + bool needsResponse() const { return mNeedResponse; } + /** @return whether an error in executing this command is so fatal + that closing the connection is the only option */ + bool closeConnectionOnError() const { + return mFlags & CloseConnectionOnError; + } + bool mustBeLastInPipeline() const { + return mFlags & OnlyLastInPipeline; + } + bool mustBeFirstInPipeline() const { + return mFlags & OnlyFirstInPipeline; + } + + protected: + SMTPProtocol * mSMTP; + bool mComplete; + bool mNeedResponse; + const int mFlags; + + protected: + // only relay methods to enable access to slave-protected methods + // for subclasses of Command: + void parseFeatures( const Response & r ); + int startTLS(); + bool usingSSL() const; + bool usingTLS() const; + bool haveCapability( const char * cap ) const; + }; + + class EHLOCommand : public Command { + public: + EHLOCommand( SMTPProtocol * smtp, const QString & hostname ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ), + mEHLONotSupported( false ), + mHostname( hostname.stripWhiteSpace() ) {} + + QCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + private: + bool mEHLONotSupported; + QString mHostname; + }; + + class StartTLSCommand : public Command { + public: + StartTLSCommand( SMTPProtocol * smtp ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {} + + QCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + }; + + class AuthCommand : public Command { + public: + AuthCommand( SMTPProtocol * smtp, const char *mechanisms, + const QString &aFQDN, KIO::AuthInfo &ai ); + ~AuthCommand(); + bool doNotExecute( const TransactionState * ts ) const; + QCString nextCommandLine( TransactionState * ); + void ungetCommandLine( const QCString & cmdLine, TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + private: + bool saslInteract( void *in ); + +#ifdef HAVE_LIBSASL2 + sasl_conn_t *conn; + sasl_interact_t *client_interact; +#endif + const char *mOut, *mMechusing; + uint mOutlen; + bool mOneStep; + + KIO::AuthInfo *mAi; + QCString mLastChallenge; + QCString mUngetSASLResponse; + bool mFirstTime; + }; + + class MailFromCommand : public Command { + public: + MailFromCommand( SMTPProtocol * smtp, const QCString & addr, + bool eightBit=false, unsigned int size=0 ) + : Command( smtp ), mAddr( addr ), m8Bit( eightBit ), mSize( size ) {} + + QCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + private: + QCString mAddr; + bool m8Bit; + unsigned int mSize; + }; + + class RcptToCommand : public Command { + public: + RcptToCommand( SMTPProtocol * smtp, const QCString & addr ) + : Command( smtp ), mAddr( addr ) {} + + QCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + private: + QCString mAddr; + }; + + /** Handles only the initial intermediate response and compltetes at + the point where the mail contents need to be sent */ + class DataCommand : public Command { + public: + DataCommand( SMTPProtocol * smtp ) + : Command( smtp, OnlyLastInPipeline ) {} + + QCString nextCommandLine( TransactionState * ); + void ungetCommandLine( const QCString & cmd, TransactionState * ts ); + bool processResponse( const Response & response, TransactionState * ); + }; + + /** Handles the data transfer following a successful DATA command */ + class TransferCommand : public Command { + public: + TransferCommand( SMTPProtocol * smtp, const QCString & initialBuffer ) + : Command( smtp, OnlyFirstInPipeline ), + mUngetBuffer( initialBuffer ), mLastChar( '\n' ), mWasComplete( false ) {} + + bool doNotExecute( const TransactionState * ts ) const; + QCString nextCommandLine( TransactionState * ); + void ungetCommandLine( const QCString & cmd, TransactionState * ts ); + bool processResponse( const Response & response, TransactionState * ); + private: + QCString prepare( const QByteArray & ba ); + QCString mUngetBuffer; + char mLastChar; + bool mWasComplete; // ... before ungetting + }; + + class NoopCommand : public Command { + public: + NoopCommand( SMTPProtocol * smtp ) + : Command( smtp, OnlyLastInPipeline ) {} + + QCString nextCommandLine( TransactionState * ); + }; + + class RsetCommand : public Command { + public: + RsetCommand( SMTPProtocol * smtp ) + : Command( smtp, CloseConnectionOnError ) {} + + QCString nextCommandLine( TransactionState * ); + }; + + class QuitCommand : public Command { + public: + QuitCommand( SMTPProtocol * smtp ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {} + + QCString nextCommandLine( TransactionState * ); + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_COMMAND_H__ diff --git a/kioslave/smtp/compliance.txt b/kioslave/smtp/compliance.txt new file mode 100644 index 000000000..b6b9874c8 --- /dev/null +++ b/kioslave/smtp/compliance.txt @@ -0,0 +1,33 @@ +The SMTP kioslave currently conforms to the following SMTP-related RFCs: + +Base Spec: +2821 Simple Mail Transfer Protocol. J. Klensin, Ed.. April 2001. + (Format: TXT=192504 bytes) (Obsoletes RFC0821, RFC0974, RFC1869) + (Status: PROPOSED STANDARD) + +Encryption/Auth: +3207 SMTP Service Extension for Secure SMTP over Transport Layer + Security. P. Hoffman. February 2002. (Format: TXT=18679 bytes) + (Obsoletes RFC2487) (Status: PROPOSED STANDARD) + +2554 SMTP Service Extension for Authentication. J. Myers. March 1999. + (Format: TXT=20534 bytes) (Status: PROPOSED STANDARD) +(with all SASL mechanisms supported by KDESasl) + +General: +1652 SMTP Service Extension for 8bit-MIMEtransport. J. Klensin, N. + Freed, M. Rose, E. Stefferud, D. Crocker. July 1994. (Format: + TXT=11842 bytes) (Obsoletes RFC1426) (Status: DRAFT STANDARD) + +1870 SMTP Service Extension for Message Size Declaration. J. Klensin, + N. Freed, K. Moore. November 1995. (Format: TXT=18226 bytes) + (Obsoletes RFC1653) (Also STD0010) (Status: STANDARD) + +2920 SMTP Service Extension for Command Pipelining. N. Freed. + September 2000. (Format: TXT=17065 bytes) (Obsoletes RFC2197) (Also + STD0060) (Status: STANDARD) + +Known shortcomings: +- Doesn't enforce the CRLF lineending convention on user-supplied data. +- Due to the lack of a Mulit_Put_ in the KIO infrastructure, pipelining + across messages isn't supported. diff --git a/kioslave/smtp/interactivesmtpserver.cc b/kioslave/smtp/interactivesmtpserver.cc new file mode 100644 index 000000000..4deddd3ca --- /dev/null +++ b/kioslave/smtp/interactivesmtpserver.cc @@ -0,0 +1,127 @@ +/* -*- c++ -*- + interactivesmtpserver.cc + + Code based on the serverSocket example by Jesper Pedersen. + + This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2004 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <config.h> + +#include <qserversocket.h> +#include <qsocket.h> +#include <qwidget.h> +#include <qapplication.h> +#include <qhostaddress.h> +#include <qtextedit.h> +#include <qlineedit.h> +#include <qlabel.h> +#include <qstring.h> +#include <qlayout.h> +#include <qpushbutton.h> + +#include <cassert> + +#include "interactivesmtpserver.h" + +static const QHostAddress localhost( 0x7f000001 ); // 127.0.0.1 + +InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow() { + if ( mSocket ) { + mSocket->close(); + if ( mSocket->state() == QSocket::Closing ) + connect( mSocket, SIGNAL(delayedCloseFinished()), + mSocket, SLOT(deleteLater()) ); + else + mSocket->deleteLater(); + mSocket = 0; + } +} + +void InteractiveSMTPServerWindow::slotSendResponse() +{ + const QString line = mLineEdit->text(); + mLineEdit->clear(); + QTextStream s( mSocket ); + s << line + "\r\n"; + slotDisplayServer( line ); +} + +InteractiveSMTPServer::InteractiveSMTPServer( QObject* parent ) + : QServerSocket( localhost, 2525, 1, parent ) +{ +} + +int main( int argc, char * argv[] ) { + QApplication app( argc, argv ); + + InteractiveSMTPServer server; + + qDebug( "Server should now listen on localhost:2525" ); + qDebug( "Hit CTRL-C to quit." ); + return app.exec(); +}; + + +InteractiveSMTPServerWindow::InteractiveSMTPServerWindow( QSocket * socket, QWidget * parent, const char * name, WFlags f ) + : QWidget( parent, name, f ), mSocket( socket ) +{ + QPushButton * but; + assert( socket ); + + QVBoxLayout * vlay = new QVBoxLayout( this, 6 ); + + mTextEdit = new QTextEdit( this ); + mTextEdit->setTextFormat( QTextEdit::LogText ); + vlay->addWidget( mTextEdit, 1 ); + + QHBoxLayout * hlay = new QHBoxLayout( vlay ); + + mLineEdit = new QLineEdit( this ); + but = new QPushButton( "&Send", this ); + hlay->addWidget( new QLabel( mLineEdit, "&Response:", this ) ); + hlay->addWidget( mLineEdit, 1 ); + hlay->addWidget( but ); + + connect( mLineEdit, SIGNAL(returnPressed()), SLOT(slotSendResponse()) ); + connect( but, SIGNAL(clicked()), SLOT(slotSendResponse()) ); + + but = new QPushButton( "&Close Connection", this ); + vlay->addWidget( but ); + + connect( but, SIGNAL(clicked()), SLOT(slotConnectionClosed()) ); + + connect( socket, SIGNAL(connectionClosed()), SLOT(slotConnectionClosed()) ); + connect( socket, SIGNAL(error(int)), SLOT(slotError(int)) ); + connect( socket, SIGNAL(readyRead()), SLOT(slotReadyRead()) ); + + mLineEdit->setText( "220 hi there" ); + mLineEdit->setFocus(); +} + +#include "interactivesmtpserver.moc" diff --git a/kioslave/smtp/interactivesmtpserver.h b/kioslave/smtp/interactivesmtpserver.h new file mode 100644 index 000000000..3ae210a74 --- /dev/null +++ b/kioslave/smtp/interactivesmtpserver.h @@ -0,0 +1,121 @@ +#ifndef INTERACTIVESMTPSERVER_H +#define INTERACTIVESMTPSERVER_H + +/* -*- c++ -*- + interactivesmtpserver.h + + Code based on the serverSocket example by Jesper Pedersen. + + This file is part of the testsuite of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2004 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <qwidget.h> + + +static QString err2str( int err ) { + switch ( err ) { + case QSocket::ErrConnectionRefused: return "Connection refused"; + case QSocket::ErrHostNotFound: return "Host not found"; + case QSocket::ErrSocketRead: return "Failed to read from socket"; + default: return "Unknown error"; + } +} + + +static QString escape( QString s ) { + return s + .replace( '&', "&" ) + .replace( '>', ">" ) + .replace( '<', "<" ) + .replace( '"', """ ) + ; +} + + +static QString trim( const QString & s ) { + if ( s.endsWith( "\r\n" ) ) + return s.left( s.length() - 2 ); + if ( s.endsWith( "\r" ) || s.endsWith( "\n" ) ) + return s.left( s.length() - 1 ); + return s; +} + + +class InteractiveSMTPServerWindow : public QWidget { + Q_OBJECT +public: + InteractiveSMTPServerWindow( QSocket * socket, QWidget * parent=0, const char * name=0, WFlags f=0 ); + ~InteractiveSMTPServerWindow(); + +public slots: + void slotSendResponse(); + void slotDisplayClient( const QString & s ) { + mTextEdit->append( "C:" + escape(s) ); + } + void slotDisplayServer( const QString & s ) { + mTextEdit->append( "S:" + escape(s) ); + } + void slotDisplayMeta( const QString & s ) { + mTextEdit->append( "<font color=\"red\">" + escape(s) + "</font>" ); + } + void slotReadyRead() { + while ( mSocket->canReadLine() ) + slotDisplayClient( trim( mSocket->readLine() ) ); + } + void slotError( int err ) { + slotDisplayMeta( QString( "E: %1 (%2)" ).arg( err2str( err ) ).arg( err ) ); + } + void slotConnectionClosed() { + slotDisplayMeta( "Connection closed by peer" ); + } + void slotCloseConnection() { + mSocket->close(); + } +private: + QSocket * mSocket; + QTextEdit * mTextEdit; + QLineEdit * mLineEdit; +}; + +class InteractiveSMTPServer : public QServerSocket { + Q_OBJECT +public: + InteractiveSMTPServer( QObject * parent=0 ); + ~InteractiveSMTPServer() {} + + /*! \reimp */ + void newConnection( int fd ) { + QSocket * socket = new QSocket(); + socket->setSocket( fd ); + InteractiveSMTPServerWindow * w = new InteractiveSMTPServerWindow( socket ); + w->show(); + } +}; + + +#endif diff --git a/kioslave/smtp/request.cc b/kioslave/smtp/request.cc new file mode 100644 index 000000000..566d2c758 --- /dev/null +++ b/kioslave/smtp/request.cc @@ -0,0 +1,189 @@ +/* -*- c++ -*- + request.cc + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <config.h> + +#include "request.h" + +#include <kurl.h> +#include <kidna.h> +#include <kmdcodec.h> +#include <kdebug.h> + +#include <assert.h> + +namespace KioSMTP { + + Request Request::fromURL( const KURL & url ) { + Request request; + + const QStringList query = QStringList::split( '&', url.query().mid(1) ); +#ifndef NDEBUG + kdDebug(7112) << "Parsing request from query:\n" + query.join("\n" ) << endl; +#endif + for ( QStringList::const_iterator it = query.begin() ; it != query.end() ; ++it ) { + int equalsPos = (*it).find( '=' ); + if ( equalsPos <= 0 ) + continue; + + const QString key = (*it).left( equalsPos ).lower(); + const QString value = KURL::decode_string( (*it).mid( equalsPos + 1 ) ); + + if ( key == "to" ) + request.addTo( value ); + else if ( key == "cc" ) + request.addCc( value ); + else if ( key == "bcc" ) + request.addBcc( value ); + else if ( key == "headers" ) { + request.setEmitHeaders( value == "0" ); + request.setEmitHeaders( false ); // ### ??? + } + else if ( key == "subject" ) + request.setSubject( value ); + else if ( key == "from" ) + request.setFromAddress( value ); + else if ( key == "profile" ) + request.setProfileName( value ); + else if ( key == "hostname" ) + request.setHeloHostname( value ); + else if ( key == "body" ) + request.set8BitBody( value.upper() == "8BIT" ); + else if ( key == "size" ) + request.setSize( value.toUInt() ); + else + kdWarning(7112) << "while parsing query: unknown query item \"" + << key << "\" with value \"" << value << "\"" << endl; + } + + return request; + } + + QCString Request::heloHostnameCString() const { + return KIDNA::toAsciiCString( heloHostname() ); + } + + static bool isUsAscii( const QString & s ) { + for ( uint i = 0 ; i < s.length() ; ++i ) + if ( s[i].unicode() > 127 ) return false; + return true; + } + + + + static inline bool isSpecial( char ch ) { + static const QCString specials = "()<>[]:;@\\,.\""; + return specials.find( ch ) >= 0; + } + + + + static inline bool needsQuoting( char ch ) { + return ch == '\\' || ch == '"' || ch == '\n' ; + } + + + + static inline QCString rfc2047Encode( const QString & s ) { + QCString r = KCodecs::base64Encode( s.stripWhiteSpace().utf8(), false ); + return "=?utf-8?b?" + r + "?=" ; // use base64 since that always gives a valid encoded-word + } + + + + static QCString quote( const QString & s ) { + assert( isUsAscii( s ) ); + + QCString r( s.length() * 2 ); + bool needsQuotes = false; + + unsigned int j = 0; + for ( unsigned int i = 0 ; i < s.length() ; ++i ) { + char ch = s[i].latin1(); + if ( isSpecial( ch ) ) { + if ( needsQuoting( ch ) ) + r[j++] = '\\'; + needsQuotes = true; + } + r[j++] = ch; + } + r.truncate( j ); + + if ( needsQuotes ) + return '"' + r + '"'; + else + return r; + } + + + + static QCString formatFromAddress( const QString & fromRealName, const QString & fromAddress ) { + if ( fromRealName.isEmpty() ) + return fromAddress.latin1(); // no real name: return "joe@user.org" + + // return "Joe User <joe@user.org>", "\"User, Joe\" <joe@user.org>" + // or "=?utf-8?q?Joe_User?= <joe@user.org>", depending on real name's nature. + QCString r = isUsAscii( fromRealName ) ? quote( fromRealName ) : rfc2047Encode( fromRealName ); + return r + " <" + fromAddress.latin1() + '>'; + } + + + + static QCString formatSubject( QString s ) { + if ( isUsAscii( s ) ) + return s.remove( '\n' ).latin1(); // don't break header folding, + // so remove any line break + // that happen to be around + else + return rfc2047Encode( s ); + } + + + + QCString Request::headerFields( const QString & fromRealName ) const { + if ( !emitHeaders() ) + return 0; + + assert( hasFromAddress() ); // should have been checked for by + // caller (MAIL FROM comes before DATA) + + QCString result = "From: " + formatFromAddress( fromRealName, fromAddress() ) + "\r\n"; + + if ( !subject().isEmpty() ) + result += "Subject: " + formatSubject( subject() ) + "\r\n"; + if ( !to().empty() ) + result += QCString( "To: " ) + to().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; + if ( !cc().empty() ) + result += QCString( "Cc: " ) + cc().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; + return result; + } + +} // namespace KioSMTP diff --git a/kioslave/smtp/request.h b/kioslave/smtp/request.h new file mode 100644 index 000000000..15bbd76b9 --- /dev/null +++ b/kioslave/smtp/request.h @@ -0,0 +1,100 @@ +/* -*- c++ -*- + request.h + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KIOSMTP_REQUEST_H__ +#define __KIOSMTP_REQUEST_H__ + +#include <qstring.h> +#include <qstringlist.h> + +class KURL; + +namespace KioSMTP { + + class Request { + public: + Request() + : mSubject( "missing subject" ), mEmitHeaders( true ), + m8Bit( false ), mSize( 0 ) {} + + static Request fromURL( const KURL & url ); + + QString profileName() const { return mProfileName; } + void setProfileName( const QString & profileName ) { mProfileName = profileName; } + bool hasProfile() const { return !profileName().isNull(); } + + QString subject() const { return mSubject; } + void setSubject( const QString & subject ) { mSubject = subject; } + + QString fromAddress() const { return mFromAddress; } + void setFromAddress( const QString & fromAddress ) { mFromAddress = fromAddress; } + bool hasFromAddress() const { return !mFromAddress.isEmpty(); } + + QStringList recipients() const { return to() + cc() + bcc() ; } + bool hasRecipients() const { return !to().empty() || !cc().empty() || !bcc().empty() ; } + + QStringList to() const { return mTo; } + QStringList cc() const { return mCc; } + QStringList bcc() const { return mBcc; } + void addTo( const QString & to ) { mTo.push_back( to ); } + void addCc( const QString & cc ) { mCc.push_back( cc ); } + void addBcc( const QString & bcc ) { mBcc.push_back( bcc ); } + + QString heloHostname() const { return mHeloHostname; } + QCString heloHostnameCString() const; + void setHeloHostname( const QString & hostname ) { mHeloHostname = hostname; } + + bool emitHeaders() const { return mEmitHeaders; } + void setEmitHeaders( bool emitHeaders ) { mEmitHeaders = emitHeaders; } + + bool is8BitBody() const { return m8Bit; } + void set8BitBody( bool a8Bit ) { m8Bit = a8Bit; } + + unsigned int size() const { return mSize; } + void setSize( unsigned int size ) { mSize = size; } + + /** If @ref #emitHeaders() is true, returns the rfc2822 + serialization of the header fields "To", "Cc", "Subject" and + "From", as determined by the respective settings. If @ref + #emitHeaders() is false, returns a null string. */ + QCString headerFields( const QString & fromRealName=QString::null ) const; + + private: + QStringList mTo, mCc, mBcc; + QString mProfileName, mSubject, mFromAddress, mHeloHostname; + bool mEmitHeaders; + bool m8Bit; + unsigned int mSize; + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_REQUEST_H__ diff --git a/kioslave/smtp/response.cc b/kioslave/smtp/response.cc new file mode 100644 index 000000000..b1745865a --- /dev/null +++ b/kioslave/smtp/response.cc @@ -0,0 +1,160 @@ +/* -*- c++ -*- + response.cc + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <config.h> + +#include "response.h" + +#include <klocale.h> +#include <kio/global.h> + +#include <qstring.h> + +namespace KioSMTP { + + void Response::parseLine( const char * line, int len ) { + + if ( !isWellFormed() ) return; // don't bother + + if ( isComplete() ) + // if the response is already complete, there can't be another line + mValid = false; + + if ( len > 1 && line[len-1] == '\n' && line[len-2] == '\r' ) + len -= 2; + + if ( len < 3 ) { + // can't be valid - too short + mValid = false; + mWellFormed = false; + return; + } + + bool ok = false; + unsigned int code = QCString( line, 3+1 ).toUInt( &ok ); + if ( !ok || code < 100 || code > 559 ) { + // not a number or number out of range + mValid = false; + if ( !ok || code < 100 ) + mWellFormed = false; + return; + } + if ( mCode && code != mCode ) { + // different codes in one response are not allowed. + mValid = false; + return; + } + mCode = code; + + if ( len == 3 || line[3] == ' ' ) + mSawLastLine = true; + else if ( line[3] != '-' ) { + // code must be followed by either SP or hyphen (len == 3 is + // also accepted since broken servers exist); all else is + // invalid + mValid = false; + mWellFormed = false; + return; + } + + mLines.push_back( len > 4 ? QCString( line+4, len-4+1 ).stripWhiteSpace() : QCString() ); + } + + + // hackishly fixing QCStringList flaws... + static QCString join( char sep, const QCStringList & list ) { + if ( list.empty() ) + return QCString(); + QCString result = list.front(); + for ( QCStringList::const_iterator it = ++list.begin() ; it != list.end() ; ++it ) + result += sep + *it; + return result; + } + + QString Response::errorMessage() const { + QString msg; + if ( lines().count() > 1 ) + msg = i18n("The server responded:\n%1") + .arg( join( '\n', lines() ) ); + else + msg = i18n("The server responded: \"%1\"") + .arg( lines().front() ); + if ( first() == 4 ) + msg += '\n' + i18n("This is a temporary failure. " + "You may try again later."); + return msg; + } + + int Response::errorCode() const { + switch ( code() ) { + case 421: // Service not available, closing transmission channel + case 454: // TLS not available due to temporary reason + // Temporary authentication failure + case 554: // Transaction failed / No SMTP service here / No valid recipients + return KIO::ERR_SERVICE_NOT_AVAILABLE; + + case 451: // Requested action aborted: local error in processing + return KIO::ERR_INTERNAL_SERVER; + + case 452: // Requested action not taken: insufficient system storage + case 552: // Requested mail action aborted: exceeded storage allocation + return KIO::ERR_DISK_FULL; + + case 500: // Syntax error, command unrecognized + case 501: // Syntax error in parameters or arguments + case 502: // Command not implemented + case 503: // Bad sequence of commands + case 504: // Command parameter not implemented + return KIO::ERR_INTERNAL; + + case 450: // Requested mail action not taken: mailbox unavailable + case 550: // Requested action not taken: mailbox unavailable + case 551: // User not local; please try <forward-path> + case 553: // Requested action not taken: mailbox name not allowed + return KIO::ERR_DOES_NOT_EXIST; + + case 530: // {STARTTLS,Authentication} required + case 538: // Encryption required for requested authentication mechanism + case 534: // Authentication mechanism is too weak + return KIO::ERR_UPGRADE_REQUIRED; + + case 432: // A password transition is needed + return KIO::ERR_COULD_NOT_AUTHENTICATE; + + default: + if ( isPositive() ) + return 0; + else + return KIO::ERR_UNKNOWN; + } + } + +} // namespace KioSMTP diff --git a/kioslave/smtp/response.h b/kioslave/smtp/response.h new file mode 100644 index 000000000..22835690a --- /dev/null +++ b/kioslave/smtp/response.h @@ -0,0 +1,125 @@ +/* -*- c++ -*- + response.h + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KIOSMTP_RESPONSE_H__ +#define __KIOSMTP_RESPONSE_H__ + +#include <qcstring.h> +#include <qvaluelist.h> +typedef QValueList<QCString> QCStringList; + +class QString; + +namespace KioSMTP { + + class Response { + public: + Response() + : mCode(0), + mValid(true), + mSawLastLine(false), + mWellFormed(true) {} + + void parseLine( const char * line ) { + parseLine( line, qstrlen( line ) ); + } + void parseLine( const char * line, int len ); + + /** Return an internationalized error message according to the + response's code. */ + QString errorMessage() const; + /** Translate the SMTP error code into a KIO one */ + int errorCode() const; + + enum Reply { + UnknownReply = -1, + PositivePreliminary = 1, + PositiveCompletion = 2, + PositiveIntermediate = 3, + TransientNegative = 4, + PermanentNegative = 5 + }; + + enum Category { + UnknownCategory = -1, + SyntaxError = 0, + Information = 1, + Connections = 2, + MailSystem = 5 + }; + + unsigned int code() const { return mCode; } + unsigned int first() const { return code() / 100 ; } + unsigned int second() const { return ( code() % 100 ) / 10 ; } + unsigned int third() const { return code() % 10 ; } + + bool isPositive() const { return first() <= 3 && first() >= 1 ; } + bool isNegative() const { return first() == 4 || first() == 5 ; } + bool isUnknown() const { return !isPositive() && !isNegative() ; } + + QCStringList lines() const { return mLines; } + + bool isValid() const { return mValid; } + bool isComplete() const { return mSawLastLine; } + + /** Shortcut method. + @return true iff the response is valid, complete and positive */ + bool isOk() const { return isValid() && isComplete() && isPositive() ; } + /** Indicates whether the response was well-formed, meaning it + obeyed the syntax of smtp responses. That the response + nevertheless is not valid may be caused by e.g. different + response codes in a multilie response. A non-well-formed + response is never valid. */ + bool isWellFormed() const { return mWellFormed; } + + void clear() { *this = Response(); } + +#ifdef KIOSMTP_COMPARATORS + bool operator==( const Response & other ) const { + return mCode == other.mCode && + mValid == other.mValid && + mSawLastLine == other.mSawLastLine && + mWellFormed == other.mWellFormed && + mLines == other.mLines; + } +#endif + + private: + unsigned int mCode; + QCStringList mLines; + bool mValid; + bool mSawLastLine; + bool mWellFormed; + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_RESPONSE_H__ diff --git a/kioslave/smtp/smtp.cc b/kioslave/smtp/smtp.cc new file mode 100644 index 000000000..dc621f533 --- /dev/null +++ b/kioslave/smtp/smtp.cc @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2000, 2001 Alex Zepeda <zipzippy@sonic.net> + * Copyright (c) 2001 Michael Häckel <Michael@Haeckel.Net> + * Copyright (c) 2002 Aaron J. Seigo <aseigo@olympusproject.org> + * Copyright (c) 2003 Marc Mutz <mutz@kde.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include <config.h> + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include <sasl/sasl.h> +} +#endif + +#include "smtp.h" +#include "request.h" +#include "response.h" +#include "transactionstate.h" +#include "command.h" +using KioSMTP::Capabilities; +using KioSMTP::Command; +using KioSMTP::EHLOCommand; +using KioSMTP::AuthCommand; +using KioSMTP::MailFromCommand; +using KioSMTP::RcptToCommand; +using KioSMTP::DataCommand; +using KioSMTP::TransferCommand; +using KioSMTP::Request; +using KioSMTP::Response; +using KioSMTP::TransactionState; + +#include <kemailsettings.h> +#include <ksock.h> +#include <kdebug.h> +#include <kinstance.h> +#include <kio/connection.h> +#include <kio/slaveinterface.h> +#include <klocale.h> + +#include <qstring.h> +#include <qstringlist.h> +#include <qcstring.h> + +#include <memory> +using std::auto_ptr; + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> + +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif +#include <netdb.h> + +#ifndef NI_NAMEREQD +// FIXME for KDE 3.3: fake defintion +// API design flaw in KExtendedSocket::resolve +# define NI_NAMEREQD 0 +#endif + + +extern "C" { + KDE_EXPORT int kdemain(int argc, char **argv); +} + +int kdemain(int argc, char **argv) +{ + KInstance instance("kio_smtp"); + + if (argc != 4) { + fprintf(stderr, + "Usage: kio_smtp protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + +#ifdef HAVE_LIBSASL2 + if ( sasl_client_init( NULL ) != SASL_OK ) { + fprintf(stderr, "SASL library initialization failed!\n"); + exit(-1); + } +#endif + SMTPProtocol slave( argv[2], argv[3], qstricmp( argv[1], "smtps" ) == 0 ); + slave.dispatchLoop(); +#ifdef HAVE_LIBSASL2 + sasl_done(); +#endif + return 0; +} + +SMTPProtocol::SMTPProtocol(const QCString & pool, const QCString & app, + bool useSSL) +: TCPSlaveBase(useSSL ? 465 : 25, + useSSL ? "smtps" : "smtp", + pool, app, useSSL), + m_iOldPort(0), + m_opened(false) +{ + //kdDebug(7112) << "SMTPProtocol::SMTPProtocol" << endl; + mPendingCommandQueue.setAutoDelete( true ); + mSentCommandQueue.setAutoDelete( true ); +} + +unsigned int SMTPProtocol::sendBufferSize() const { + // ### how much is eaten by SSL/TLS overhead? + const int fd = fileno( fp ); + int value = -1; + kde_socklen_t len = sizeof(value); + if ( fd < 0 || ::getsockopt( fd, SOL_SOCKET, SO_SNDBUF, (char*)&value, &len ) ) + value = 1024; // let's be conservative + kdDebug(7112) << "send buffer size seems to be " << value << " octets." << endl; + return value > 0 ? value : 1024 ; +} + +SMTPProtocol::~SMTPProtocol() { + //kdDebug(7112) << "SMTPProtocol::~SMTPProtocol" << endl; + smtp_close(); +} + +void SMTPProtocol::openConnection() { + if ( smtp_open() ) + connected(); + else + closeConnection(); +} + +void SMTPProtocol::closeConnection() { + smtp_close(); +} + +void SMTPProtocol::special( const QByteArray & aData ) { + QDataStream s( aData, IO_ReadOnly ); + int what; + s >> what; + if ( what == 'c' ) { + infoMessage( createSpecialResponse() ); +#ifndef NDEBUG + kdDebug(7112) << "special('c') returns \"" << createSpecialResponse() << "\"" << endl; +#endif + } else if ( what == 'N' ) { + if ( !execute( Command::NOOP ) ) + return; + } else { + error( KIO::ERR_INTERNAL, + i18n("The application sent an invalid request.") ); + return; + } + finished(); +} + + +// Usage: smtp://smtphost:port/send?to=user@host.com&subject=blah +// If smtphost is the name of a profile, it'll use the information +// provided by that profile. If it's not a profile name, it'll use it as +// nature intended. +// One can also specify in the query: +// headers=0 (turns off header generation) +// to=emailaddress +// cc=emailaddress +// bcc=emailaddress +// subject=text +// profile=text (this will override the "host" setting) +// hostname=text (used in the HELO) +// body={7bit,8bit} (default: 7bit; 8bit activates the use of the 8BITMIME SMTP extension) +void SMTPProtocol::put(const KURL & url, int /*permissions */ , + bool /*overwrite */ , bool /*resume */ ) +{ + Request request = Request::fromURL( url ); // parse settings from URL's query + + KEMailSettings mset; + KURL open_url = url; + if ( !request.hasProfile() ) { + //kdDebug(7112) << "kio_smtp: Profile is null" << endl; + bool hasProfile = mset.profiles().contains( open_url.host() ); + if ( hasProfile ) { + mset.setProfile(open_url.host()); + open_url.setHost(mset.getSetting(KEMailSettings::OutServer)); + m_sUser = mset.getSetting(KEMailSettings::OutServerLogin); + m_sPass = mset.getSetting(KEMailSettings::OutServerPass); + + if (m_sUser.isEmpty()) + m_sUser = QString::null; + if (m_sPass.isEmpty()) + m_sPass = QString::null; + open_url.setUser(m_sUser); + open_url.setPass(m_sPass); + m_sServer = open_url.host(); + m_iPort = open_url.port(); + } + else { + mset.setProfile(mset.defaultProfileName()); + } + } + else { + mset.setProfile( request.profileName() ); + } + + // Check KEMailSettings to see if we've specified an E-Mail address + // if that worked, check to see if we've specified a real name + // and then format accordingly (either: emailaddress@host.com or + // Real Name <emailaddress@host.com>) + if ( !request.hasFromAddress() ) { + const QString from = mset.getSetting( KEMailSettings::EmailAddress ); + if ( !from.isNull() ) + request.setFromAddress( from ); + else if ( request.emitHeaders() ) { + error(KIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); + return; + } + } + + if ( !smtp_open( request.heloHostname() ) ) + { + error(KIO::ERR_SERVICE_NOT_AVAILABLE, + i18n("SMTPProtocol::smtp_open failed (%1)") // ### better error message? + .arg(open_url.path())); + return; + } + + if ( request.is8BitBody() + && !haveCapability("8BITMIME") && metaData("8bitmime") != "on" ) { + error( KIO::ERR_SERVICE_NOT_AVAILABLE, + i18n("Your server does not support sending of 8-bit messages.\n" + "Please use base64 or quoted-printable encoding.") ); + return; + } + + queueCommand( new MailFromCommand( this, request.fromAddress().latin1(), + request.is8BitBody(), request.size() ) ); + + // Loop through our To and CC recipients, and send the proper + // SMTP commands, for the benefit of the server. + QStringList recipients = request.recipients(); + for ( QStringList::const_iterator it = recipients.begin() ; it != recipients.end() ; ++it ) + queueCommand( new RcptToCommand( this, (*it).latin1() ) ); + + queueCommand( Command::DATA ); + queueCommand( new TransferCommand( this, request.headerFields( mset.getSetting( KEMailSettings::RealName ) ) ) ); + + TransactionState ts; + if ( !executeQueuedCommands( &ts ) ) { + if ( ts.errorCode() ) + error( ts.errorCode(), ts.errorMessage() ); + } else + finished(); +} + + +void SMTPProtocol::setHost(const QString & host, int port, + const QString & user, const QString & pass) +{ + m_sServer = host; + m_iPort = port; + m_sUser = user; + m_sPass = pass; +} + +bool SMTPProtocol::sendCommandLine( const QCString & cmdline ) { + //kdDebug( cmdline.length() < 4096, 7112) << "C: " << cmdline.data(); + //kdDebug( cmdline.length() >= 4096, 7112) << "C: <" << cmdline.length() << " bytes>" << endl; + kdDebug( 7112) << "C: <" << cmdline.length() << " bytes>" << endl; + ssize_t cmdline_len = cmdline.length(); + if ( write( cmdline.data(), cmdline_len ) != cmdline_len ) { + error( KIO::ERR_COULD_NOT_WRITE, m_sServer ); + return false; + } + return true; +} + +Response SMTPProtocol::getResponse( bool * ok ) { + + if ( ok ) + *ok = false; + + Response response; + char buf[2048]; + + int recv_len = 0; + do { + // wait for data... + if ( !waitForResponse( 600 ) ) { + error( KIO::ERR_SERVER_TIMEOUT, m_sServer ); + return response; + } + + // ...read data... + recv_len = readLine( buf, sizeof(buf) - 1 ); + if ( recv_len < 1 && !isConnectionValid() ) { + error( KIO::ERR_CONNECTION_BROKEN, m_sServer ); + return response; + } + + kdDebug(7112) << "S: " << QCString( buf, recv_len + 1 ).data(); + // ...and parse lines... + response.parseLine( buf, recv_len ); + + // ...until the response is complete or the parser is so confused + // that it doesn't think a RSET would help anymore: + } while ( !response.isComplete() && response.isWellFormed() ); + + if ( !response.isValid() ) { + error( KIO::ERR_NO_CONTENT, i18n("Invalid SMTP response (%1) received.").arg(response.code()) ); + return response; + } + + if ( ok ) + *ok = true; + + return response; +} + +bool SMTPProtocol::executeQueuedCommands( TransactionState * ts ) { + assert( ts ); + + kdDebug( canPipelineCommands(), 7112 ) << "using pipelining" << endl; + + while( !mPendingCommandQueue.isEmpty() ) { + QCString cmdline = collectPipelineCommands( ts ); + if ( ts->failedFatally() ) { + smtp_close( false ); // _hard_ shutdown + return false; + } + if ( ts->failed() ) + break; + if ( cmdline.isEmpty() ) + continue; + if ( !sendCommandLine( cmdline ) || + !batchProcessResponses( ts ) || + ts->failedFatally() ) { + smtp_close( false ); // _hard_ shutdown + return false; + } + } + + if ( ts->failed() ) { + if ( !execute( Command::RSET ) ) + smtp_close( false ); + return false; + } + return true; +} + +QCString SMTPProtocol::collectPipelineCommands( TransactionState * ts ) { + assert( ts ); + + QCString cmdLine; + unsigned int cmdLine_len = 0; + + while ( mPendingCommandQueue.head() ) { + + Command * cmd = mPendingCommandQueue.head(); + + if ( cmd->doNotExecute( ts ) ) { + delete mPendingCommandQueue.dequeue(); + if ( cmdLine_len ) + break; + else + continue; + } + + if ( cmdLine_len && cmd->mustBeFirstInPipeline() ) + break; + + if ( cmdLine_len && !canPipelineCommands() ) + break; + + while ( !cmd->isComplete() && !cmd->needsResponse() ) { + const QCString currentCmdLine = cmd->nextCommandLine( ts ); + if ( ts->failedFatally() ) + return cmdLine; + const unsigned int currentCmdLine_len = currentCmdLine.length(); + + if ( cmdLine_len && cmdLine_len + currentCmdLine_len > sendBufferSize() ) { + // must all fit into the send buffer, else connection deadlocks, + // but we need to have at least _one_ command to send + cmd->ungetCommandLine( currentCmdLine, ts ); + return cmdLine; + } + cmdLine_len += currentCmdLine_len; + cmdLine += currentCmdLine; + } + + mSentCommandQueue.enqueue( mPendingCommandQueue.dequeue() ); + + if ( cmd->mustBeLastInPipeline() ) + break; + } + + return cmdLine; +} + +bool SMTPProtocol::batchProcessResponses( TransactionState * ts ) { + assert( ts ); + + while ( !mSentCommandQueue.isEmpty() ) { + + Command * cmd = mSentCommandQueue.head(); + assert( cmd->isComplete() ); + + bool ok = false; + Response r = getResponse( &ok ); + if ( !ok ) + return false; + cmd->processResponse( r, ts ); + if ( ts->failedFatally() ) + return false; + + mSentCommandQueue.remove(); + } + + return true; +} + +void SMTPProtocol::queueCommand( int type ) { + queueCommand( Command::createSimpleCommand( type, this ) ); +} + +bool SMTPProtocol::execute( int type, TransactionState * ts ) { + auto_ptr<Command> cmd( Command::createSimpleCommand( type, this ) ); + kdFatal( !cmd.get(), 7112 ) << "Command::createSimpleCommand( " << type << " ) returned null!" << endl; + return execute( cmd.get(), ts ); +} + +// ### fold into pipelining engine? How? (execute() is often called +// ### when command queues are _not_ empty!) +bool SMTPProtocol::execute( Command * cmd, TransactionState * ts ) +{ + kdFatal( !cmd, 7112 ) << "SMTPProtocol::execute() called with no command to run!" << endl; + + if (!cmd) + return false; + + if ( cmd->doNotExecute( ts ) ) + return true; + + do { + while ( !cmd->isComplete() && !cmd->needsResponse() ) { + const QCString cmdLine = cmd->nextCommandLine( ts ); + if ( ts && ts->failedFatally() ) { + smtp_close( false ); + return false; + } + if ( cmdLine.isEmpty() ) + continue; + if ( !sendCommandLine( cmdLine ) ) { + smtp_close( false ); + return false; + } + } + + bool ok = false; + Response r = getResponse( &ok ); + if ( !ok ) { + smtp_close( false ); + return false; + } + if ( !cmd->processResponse( r, ts ) ) { + if ( ts && ts->failedFatally() || + cmd->closeConnectionOnError() || + !execute( Command::RSET ) ) + smtp_close( false ); + return false; + } + } while ( !cmd->isComplete() ); + + return true; +} + +bool SMTPProtocol::smtp_open(const QString& fakeHostname) +{ + if (m_opened && + m_iOldPort == port(m_iPort) && + m_sOldServer == m_sServer && + m_sOldUser == m_sUser && + (fakeHostname.isNull() || m_hostname == fakeHostname)) + return true; + + smtp_close(); + if (!connectToHost(m_sServer, m_iPort)) + return false; // connectToHost has already send an error message. + m_opened = true; + + bool ok = false; + Response greeting = getResponse( &ok ); + if ( !ok || !greeting.isOk() ) + { + if ( ok ) + error( KIO::ERR_COULD_NOT_LOGIN, + i18n("The server did not accept the connection.\n" + "%1").arg( greeting.errorMessage() ) ); + smtp_close(); + return false; + } + + if (!fakeHostname.isNull()) + { + m_hostname = fakeHostname; + } + else + { + QString tmpPort; + KSocketAddress* addr = KExtendedSocket::localAddress(m_iSock); + // perform name lookup. NI_NAMEREQD means: don't return a numeric + // value (we need to know when we get have the IP address, so we + // can enclose it in sqaure brackets (domain-literal). Failure to + // do so is normally harmless with IPv4, but fails for IPv6: + if (KExtendedSocket::resolve(addr, m_hostname, tmpPort, NI_NAMEREQD) != 0) + // FQDN resolution failed + // use the IP address as domain-literal + m_hostname = '[' + addr->nodeName() + ']'; + delete addr; + + if(m_hostname.isEmpty()) + { + m_hostname = "localhost.invalid"; + } + } + + EHLOCommand ehloCmdPreTLS( this, m_hostname ); + if ( !execute( &ehloCmdPreTLS ) ) { + smtp_close(); + return false; + } + + if ( ( haveCapability("STARTTLS") && canUseTLS() && metaData("tls") != "off" ) + || metaData("tls") == "on" ) { + // For now we're gonna force it on. + + if ( execute( Command::STARTTLS ) ) { + + // re-issue EHLO to refresh the capability list (could be have + // been faked before TLS was enabled): + EHLOCommand ehloCmdPostTLS( this, m_hostname ); + if ( !execute( &ehloCmdPostTLS ) ) { + smtp_close(); + return false; + } + } + } + // Now we try and login + if (!authenticate()) { + smtp_close(); + return false; + } + + m_iOldPort = m_iPort; + m_sOldServer = m_sServer; + m_sOldUser = m_sUser; + m_sOldPass = m_sPass; + + return true; +} + +bool SMTPProtocol::authenticate() +{ + // return with success if the server doesn't support SMTP-AUTH or an user + // name is not specified and metadata doesn't tell us to force it. + if ( (m_sUser.isEmpty() || !haveCapability( "AUTH" )) && + metaData( "sasl" ).isEmpty() ) return true; + + KIO::AuthInfo authInfo; + authInfo.username = m_sUser; + authInfo.password = m_sPass; + authInfo.prompt = i18n("Username and password for your SMTP account:"); + + QStringList strList; + + if (!metaData("sasl").isEmpty()) + strList.append(metaData("sasl").latin1()); + else + strList = mCapabilities.saslMethodsQSL(); + + AuthCommand authCmd( this, strList.join(" ").latin1(), m_sServer, authInfo ); + bool ret = execute( &authCmd ); + m_sUser = authInfo.username; + m_sPass = authInfo.password; + return ret; +} + +void SMTPProtocol::parseFeatures( const Response & ehloResponse ) { + mCapabilities = Capabilities::fromResponse( ehloResponse ); + + QString category = usingTLS() ? "TLS" : usingSSL() ? "SSL" : "PLAIN" ; + setMetaData( category + " AUTH METHODS", mCapabilities.authMethodMetaData() ); + setMetaData( category + " CAPABILITIES", mCapabilities.asMetaDataString() ); +#ifndef NDEBUG + kdDebug(7112) << "parseFeatures() " << category << " AUTH METHODS:" + << '\n' + mCapabilities.authMethodMetaData() << endl + << "parseFeatures() " << category << " CAPABILITIES:" + << '\n' + mCapabilities.asMetaDataString() << endl; +#endif +} + +void SMTPProtocol::smtp_close( bool nice ) { + if (!m_opened) // We're already closed + return; + + if ( nice ) + execute( Command::QUIT ); + kdDebug( 7112 ) << "closing connection" << endl; + closeDescriptor(); + m_sOldServer = QString::null; + m_sOldUser = QString::null; + m_sOldPass = QString::null; + + mCapabilities.clear(); + mPendingCommandQueue.clear(); + mSentCommandQueue.clear(); + + m_opened = false; +} + +void SMTPProtocol::stat(const KURL & url) +{ + QString path = url.path(); + error(KIO::ERR_DOES_NOT_EXIST, url.path()); +} + diff --git a/kioslave/smtp/smtp.h b/kioslave/smtp/smtp.h new file mode 100644 index 000000000..571375bd4 --- /dev/null +++ b/kioslave/smtp/smtp.h @@ -0,0 +1,146 @@ +/* -*- c++ -*- + * Copyright (c) 2000, 2001 Alex Zepeda <zipzippy@sonic.net> + * Copyright (c) 2001 Michael Häckel <Michael@Haeckel.Net> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#ifndef _SMTP_H +#define _SMTP_H + +#include <kio/tcpslavebase.h> + +#include "capabilities.h" + +#include <qstring.h> +#include <qptrqueue.h> + +class KURL; +class QCString; +template <typename T> class QMemArray; +typedef QMemArray<char> QByteArray; + +namespace KioSMTP { + class Response; + class TransactionState; + class Command; +} + +class SMTPProtocol : public KIO::TCPSlaveBase { + friend class KioSMTP::Command; +public: + SMTPProtocol(const QCString & pool, const QCString & app, bool useSSL); + virtual ~ SMTPProtocol(); + + virtual void setHost(const QString & host, int port, + const QString & user, const QString & pass); + + virtual void special(const QByteArray & aData); + virtual void put(const KURL & url, int permissions, bool overwrite, + bool resume); + virtual void stat(const KURL & url); + virtual void openConnection(); + virtual void closeConnection(); + +protected: + + bool smtp_open(const QString& fakeHostname = QString::null); + + /** Closes the connection. If @p nice is true (default), then QUIT + is sent and it's reponse waited for. */ + void smtp_close( bool nice=true ); + + /** Execute command @p cmd */ + bool execute( KioSMTP::Command * cmd, KioSMTP::TransactionState * ts=0 ); + /** Execute a command of type @p type */ + bool execute( int type, KioSMTP::TransactionState * ts=0 ); + /** Execute the queued commands. If something goes horribly wrong + (sending command oline fails, getting response fails or some + command raises the failedFatally() flag in @p ts, shuts down the + connection with <code>smtp_close( false )</code>. If The + transaction fails gracefully (<code>ts->failed()</code> is + true), issues a RSET command. + + @return true if transaction succeeded, false otherwise. + **/ + bool executeQueuedCommands( KioSMTP::TransactionState * ts ); + + /** Parse a single response from the server. Single- vs. multiline + responses are correctly detected. + + @param ok if not 0, returns whether response parsing was + successful. Don't confuse this with negative responses + (e.g. 5xx), which you can check for using + @ref Response::isNegative() + @return the @ref Response object representing the server response. + **/ + KioSMTP::Response getResponse( bool * ok ); + + bool authenticate(); + void parseFeatures( const KioSMTP::Response & ehloResponse ); + + bool sendCommandLine( const QCString & cmd ); + QCString collectPipelineCommands( KioSMTP::TransactionState * ts ); + bool batchProcessResponses( KioSMTP::TransactionState * ts ); + + /** This is a pure convenience wrapper around + @ref KioSMTP::Capabilities::have() */ + bool haveCapability( const char * cap ) const { + return mCapabilities.have( cap ); + } + + /** @return true is pipelining is available and allowed by metadata */ + bool canPipelineCommands() const { + return haveCapability("PIPELINING") && metaData("pipelining") != "off" ; + } + + /** Wrapper around getsockopt(..., SO_SNDBUF,...) */ + unsigned int sendBufferSize() const; + + /** This is a pure convenience wrapper around + @ref KioSMTP::Capabilities::createSpecialResponse */ + QString createSpecialResponse() const { + return mCapabilities.createSpecialResponse( usingTLS() || haveCapability( "STARTTLS" ) ); + } + + void queueCommand( KioSMTP::Command * command ) { + mPendingCommandQueue.enqueue( command ); + } + void queueCommand( int type ); + + unsigned short m_iOldPort; + bool m_opened; + QString m_sServer, m_sOldServer; + QString m_sUser, m_sOldUser; + QString m_sPass, m_sOldPass; + QString m_hostname; + + KioSMTP::Capabilities mCapabilities; + + typedef QPtrQueue<KioSMTP::Command> CommandQueue; + CommandQueue mPendingCommandQueue; + CommandQueue mSentCommandQueue; +}; + +#endif // _SMTP_H diff --git a/kioslave/smtp/smtp.protocol b/kioslave/smtp/smtp.protocol new file mode 100644 index 000000000..e01be9c7f --- /dev/null +++ b/kioslave/smtp/smtp.protocol @@ -0,0 +1,16 @@ +[Protocol] +exec=kio_smtp +protocol=smtp +Capabilities=SASL +input=none +output=filesystem +listing=Name,Type,Size +reading=false +writing=true +deleting=false +source=true +makedir=false +linking=false +moving=false +DocPath=kioslave/smtp.html +Icon=folder_outbox diff --git a/kioslave/smtp/smtps.protocol b/kioslave/smtp/smtps.protocol new file mode 100644 index 000000000..2087845f2 --- /dev/null +++ b/kioslave/smtp/smtps.protocol @@ -0,0 +1,15 @@ +[Protocol] +exec=kio_smtp +protocol=smtps +Capabilities=SASL +input=none +output=filesystem +listing=Name,Type,Size +reading=false +writing=true +deleting=false +source=true +makedir=false +linking=false +moving=false +Icon=folder_outbox diff --git a/kioslave/smtp/test_commands.cc b/kioslave/smtp/test_commands.cc new file mode 100644 index 000000000..a6969490f --- /dev/null +++ b/kioslave/smtp/test_commands.cc @@ -0,0 +1,728 @@ +#include <kio/global.h> +#include <kdebug.h> + +#include <qstring.h> +#include <qcstring.h> +#include <qstringlist.h> + +//#include <iostream> +//using std::cout; +//using std::endl; + +namespace KioSMTP { + class Response; +}; + +// fake +class SMTPProtocol { +public: + SMTPProtocol() { clear(); } + + // + // public members to control the API emulation below: + // + int startTLSReturnCode; + bool usesSSL; + bool usesTLS; + int lastErrorCode; + QString lastErrorMessage; + int lastMessageBoxCode; + QString lastMessageBoxText; + QByteArray nextData; + int nextDataReturnCode; + QStringList caps; + KIO::MetaData metadata; + + void clear() { + startTLSReturnCode = 1; + usesSSL = usesTLS = false; + lastErrorCode = lastMessageBoxCode = 0; + lastErrorMessage = lastMessageBoxText = QString::null; + nextData.resize( 0 ); + nextDataReturnCode = -1; + caps.clear(); + metadata.clear(); + } + + // + // emulated API: + // + void parseFeatures( const KioSMTP::Response & ) { /* noop */ } + int startTLS() { + if ( startTLSReturnCode == 1 ) + usesTLS = true; + return startTLSReturnCode; + } + bool usingSSL() const { return usesSSL; } + bool usingTLS() const { return usesTLS; } + bool haveCapability( const char * cap ) const { return caps.contains( cap ); } + void error( int id, const QString & msg ) { + lastErrorCode = id; + lastErrorMessage = msg; + } + void messageBox( int id, const QString & msg, const QString & ) { + lastMessageBoxCode = id; + lastMessageBoxText = msg; + } + void dataReq() { /* noop */ } + int readData( QByteArray & ba ) { ba = nextData; return nextDataReturnCode; } + QString metaData( const QString & key ) const { return metadata[key]; } + +}; + +#define _SMTP_H + +#define KIOSMTP_COMPARATORS // for TransactionState::operator== +#include "command.h" +#include "response.h" +#include "transactionstate.h" + +#include <assert.h> + +using namespace KioSMTP; + +static const char * foobarbaz = ".Foo bar baz"; +static const unsigned int foobarbaz_len = qstrlen( foobarbaz ); + +static const char * foobarbaz_dotstuffed = "..Foo bar baz"; +static const unsigned int foobarbaz_dotstuffed_len = qstrlen( foobarbaz_dotstuffed ); + +static const char * foobarbaz_lf = ".Foo bar baz\n"; +static const unsigned int foobarbaz_lf_len = qstrlen( foobarbaz_lf ); + +static const char * foobarbaz_crlf = "..Foo bar baz\r\n"; +static const unsigned int foobarbaz_crlf_len = qstrlen( foobarbaz_crlf ); + +static void checkSuccessfulTransferCommand( bool, bool, bool, bool, bool ); + +int main( int, char** ) { + + // FIXME: Port this to new API. +#if 0 + SMTPProtocol smtp; + Response r; + TransactionState ts, ts2; + + // + // EHLO / HELO + // + + smtp.clear(); + EHLOCommand ehlo( &smtp, "mail.example.com" ); + // flags + assert( ehlo.closeConnectionOnError() ); + assert( ehlo.mustBeLastInPipeline() ); + assert( !ehlo.mustBeFirstInPipeline() ); + + // initial state + assert( !ehlo.isComplete() ); + assert( !ehlo.doNotExecute( 0 ) ); + assert( !ehlo.needsResponse() ); + + // dynamics 1: EHLO succeeds + assert( ehlo.nextCommandLine( 0 ) == "EHLO mail.example.com\r\n" ); + assert( !ehlo.isComplete() ); // EHLO may fail and we then try HELO + assert( ehlo.needsResponse() ); + r.clear(); + r.parseLine( "250-mail.example.net\r\n" ); + r.parseLine( "250-PIPELINING\r\n" ); + r.parseLine( "250 8BITMIME\r\n" ); + assert( ehlo.processResponse( r, 0 ) == true ); + assert( ehlo.isComplete() ); + assert( !ehlo.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // dynamics 2: EHLO fails with "unknown command" + smtp.clear(); + EHLOCommand ehlo2( &smtp, "mail.example.com" ); + ehlo2.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command\r\n" ); + assert( ehlo2.processResponse( r, 0 ) == true ); + assert( !ehlo2.isComplete() ); + assert( !ehlo2.needsResponse() ); + assert( ehlo2.nextCommandLine( 0 ) == "HELO mail.example.com\r\n" ); + assert( ehlo2.isComplete() ); + assert( ehlo2.needsResponse() ); + r.clear(); + r.parseLine( "250 mail.example.net\r\n" ); + assert( ehlo2.processResponse( r, 0 ) == true ); + assert( !ehlo2.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // dynamics 3: EHLO fails with unknown response code + smtp.clear(); + EHLOCommand ehlo3( &smtp, "mail.example.com" ); + ehlo3.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "545 you don't know me\r\n" ); + assert( ehlo3.processResponse( r, 0 ) == false ); + assert( ehlo3.isComplete() ); + assert( !ehlo3.needsResponse() ); + assert( smtp.lastErrorCode == KIO::ERR_UNKNOWN ); + + // dynamics 4: EHLO _and_ HELO fail with "command unknown" + smtp.clear(); + EHLOCommand ehlo4( &smtp, "mail.example.com" ); + ehlo4.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command\r\n" ); + ehlo4.processResponse( r, 0 ); + ehlo4.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command\r\n" ); + assert( ehlo4.processResponse( r, 0 ) == false ); + assert( ehlo4.isComplete() ); + assert( !ehlo4.needsResponse() ); + assert( smtp.lastErrorCode == KIO::ERR_INTERNAL_SERVER ); + + // + // STARTTLS + // + + smtp.clear(); + StartTLSCommand tls( &smtp ); + // flags + assert( tls.closeConnectionOnError() ); + assert( tls.mustBeLastInPipeline() ); + assert( !tls.mustBeFirstInPipeline() ); + + // initial state + assert( !tls.isComplete() ); + assert( !tls.doNotExecute( 0 ) ); + assert( !tls.needsResponse() ); + + // dynamics 1: ok from server, TLS negotiation successful + ts.clear(); + ts2 = ts; + assert( tls.nextCommandLine( &ts ) == "STARTTLS\r\n" ); + assert( ts == ts2 ); + assert( tls.isComplete() ); + assert( tls.needsResponse() ); + r.clear(); + r.parseLine( "220 Go ahead" ); + smtp.startTLSReturnCode = 1; + assert( tls.processResponse( r, &ts ) == true ); + assert( !tls.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics 2: NAK from server + smtp.clear(); + StartTLSCommand tls2( &smtp ); + ts.clear(); + tls2.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "454 TLS temporarily disabled" ); + smtp.startTLSReturnCode = 1; + assert( tls2.processResponse( r, &ts ) == false ); + assert( !tls2.needsResponse() ); + assert( smtp.lastErrorCode == KIO::ERR_SERVICE_NOT_AVAILABLE ); + + // dynamics 3: ok from server, TLS negotiation unsuccessful + smtp.clear(); + StartTLSCommand tls3( &smtp ); + ts.clear(); + tls3.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "220 Go ahead" ); + smtp.startTLSReturnCode = -1; + assert( tls.processResponse( r, &ts ) == false ); + assert( !tls.needsResponse() ); + + // + // AUTH + // + + smtp.clear(); + QStrIList mechs; + mechs.append( "PLAIN" ); + smtp.metadata["sasl"] = "PLAIN"; + AuthCommand auth( &smtp, mechs, "user", "pass" ); + // flags + assert( auth.closeConnectionOnError() ); + assert( auth.mustBeLastInPipeline() ); + assert( !auth.mustBeFirstInPipeline() ); + + // initial state + assert( !auth.isComplete() ); + assert( !auth.doNotExecute( 0 ) ); + assert( !auth.needsResponse() ); + + // dynamics 1: TLS, so AUTH should include initial-response: + smtp.usesTLS = true; + ts.clear(); + ts2 = ts; + assert( auth.nextCommandLine( &ts ) == "AUTH PLAIN dXNlcgB1c2VyAHBhc3M=\r\n" ); + assert( auth.isComplete() ); + assert( auth.needsResponse() ); + assert( ts == ts2 ); + r.clear(); + r.parseLine( "250 OK" ); + + // dynamics 2: No TLS, so AUTH should not include initial-response: + smtp.clear(); + smtp.metadata["sasl"] = "PLAIN"; + smtp.usesTLS = false; + AuthCommand auth2( &smtp, mechs, "user", "pass" ); + ts.clear(); + assert( auth2.nextCommandLine( &ts ) == "AUTH PLAIN\r\n" ); + assert( !auth2.isComplete() ); + assert( auth2.needsResponse() ); + r.clear(); + r.parseLine( "334 Go on" ); + assert( auth2.processResponse( r, &ts ) == true ); + assert( auth2.nextCommandLine( &ts ) == "dXNlcgB1c2VyAHBhc3M=\r\n" ); + assert( auth2.isComplete() ); + assert( auth2.needsResponse() ); + + // dynamics 3: LOGIN + smtp.clear(); + smtp.metadata["sasl"] = "LOGIN"; + mechs.clear(); + mechs.append( "LOGIN" ); + AuthCommand auth3( &smtp, mechs, "user", "pass" ); + ts.clear(); + ts2 = ts; + assert( auth3.nextCommandLine( &ts ) == "AUTH LOGIN\r\n" ); + assert( !auth3.isComplete() ); + assert( auth3.needsResponse() ); + r.clear(); + r.parseLine( "334 VXNlcm5hbWU6" ); + assert( auth3.processResponse( r, &ts ) == true ); + assert( !auth3.needsResponse() ); + assert( auth3.nextCommandLine( &ts ) == "dXNlcg==\r\n" ); + assert( !auth3.isComplete() ); + assert( auth3.needsResponse() ); + r.clear(); + r.parseLine( "334 go on" ); + assert( auth3.processResponse( r, &ts ) == true ); + assert( !auth3.needsResponse() ); + assert( auth3.nextCommandLine( &ts ) == "cGFzcw==\r\n" ); + assert( auth3.isComplete() ); + assert( auth3.needsResponse() ); + r.clear(); + r.parseLine( "250 OK" ); + assert( auth3.processResponse( r, &ts ) == true ); + assert( !auth3.needsResponse() ); + assert( !smtp.lastErrorCode ); + assert( ts == ts2 ); + + // + // MAIL FROM: + // + + smtp.clear(); + MailFromCommand mail( &smtp, "joe@user.org" ); + // flags + assert( !mail.closeConnectionOnError() ); + assert( !mail.mustBeLastInPipeline() ); + assert( !mail.mustBeFirstInPipeline() ); + + // initial state + assert( !mail.isComplete() ); + assert( !mail.doNotExecute( 0 ) ); + assert( !mail.needsResponse() ); + + // dynamics: success, no size, no 8bit + ts.clear(); + ts2 = ts; + assert( mail.nextCommandLine( &ts ) == "MAIL FROM:<joe@user.org>\r\n" ); + assert( ts2 == ts ); + assert( mail.isComplete() ); + assert( mail.needsResponse() ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( mail.processResponse( r, &ts ) == true ); + assert( !mail.needsResponse() ); + assert( ts == ts2 ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: success, size, 8bit, but no SIZE, 8BITMIME caps + smtp.clear(); + MailFromCommand mail2( &smtp, "joe@user.org", true, 500 ); + ts.clear(); + ts2 = ts; + assert( mail2.nextCommandLine( &ts ) == "MAIL FROM:<joe@user.org>\r\n" ); + assert( ts == ts2 ); + + // dynamics: success, size, 8bit, SIZE, 8BITMIME caps + smtp.clear(); + MailFromCommand mail3( &smtp, "joe@user.org", true, 500 ); + ts.clear(); + ts2 = ts; + smtp.caps << "SIZE" << "8BITMIME" ; + assert( mail3.nextCommandLine( &ts ) == "MAIL FROM:<joe@user.org> BODY=8BITMIME SIZE=500\r\n" ); + assert( ts == ts2 ); + + // dynamics: failure + smtp.clear(); + MailFromCommand mail4( &smtp, "joe@user.org" ); + ts.clear(); + mail4.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "503 Bad sequence of commands" ); + assert( mail4.processResponse( r, &ts ) == false ); + assert( mail4.isComplete() ); + assert( !mail4.needsResponse() ); + assert( ts.failed() ); + assert( !ts.failedFatally() ); + assert( smtp.lastErrorCode == 0 ); + + // + // RCPT TO: + // + + smtp.clear(); + RcptToCommand rcpt( &smtp, "joe@user.org" ); + // flags + assert( !rcpt.closeConnectionOnError() ); + assert( !rcpt.mustBeLastInPipeline() ); + assert( !rcpt.mustBeFirstInPipeline() ); + + // initial state + assert( !rcpt.isComplete() ); + assert( !rcpt.doNotExecute( 0 ) ); + assert( !rcpt.needsResponse() ); + + // dynamics: success + ts.clear(); + ts2 = ts; + assert( rcpt.nextCommandLine( &ts ) == "RCPT TO:<joe@user.org>\r\n" ); + assert( ts == ts2 ); + assert( rcpt.isComplete() ); + assert( rcpt.needsResponse() ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( rcpt.processResponse( r, &ts ) == true ); + assert( !rcpt.needsResponse() ); + assert( ts.atLeastOneRecipientWasAccepted() ); + assert( !ts.haveRejectedRecipients() ); + assert( !ts.failed() ); + assert( !ts.failedFatally() ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: failure + smtp.clear(); + RcptToCommand rcpt2( &smtp, "joe@user.org" ); + ts.clear(); + rcpt2.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "530 5.7.1 Relaying not allowed!" ); + assert( rcpt2.processResponse( r, &ts ) == false ); + assert( rcpt2.isComplete() ); + assert( !rcpt2.needsResponse() ); + assert( !ts.atLeastOneRecipientWasAccepted() ); + assert( ts.haveRejectedRecipients() ); + assert( ts.rejectedRecipients().count() == 1 ); + assert( ts.rejectedRecipients().front().recipient == "joe@user.org" ); + assert( ts.failed() ); + assert( !ts.failedFatally() ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: success and failure combined + smtp.clear(); + RcptToCommand rcpt3( &smtp, "info@example.com" ); + RcptToCommand rcpt4( &smtp, "halloween@microsoft.com" ); + RcptToCommand rcpt5( &smtp, "joe@user.org" ); + ts.clear(); + rcpt3.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "530 5.7.1 Relaying not allowed!" ); + rcpt3.processResponse( r, &ts ); + + rcpt4.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "250 Ok" ); + rcpt4.processResponse( r, &ts ); + + rcpt5.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( ts.failed() ); + assert( !ts.failedFatally() ); + assert( ts.haveRejectedRecipients() ); + assert( ts.atLeastOneRecipientWasAccepted() ); + assert( smtp.lastErrorCode == 0 ); + + // + // DATA (init) + // + + smtp.clear(); + DataCommand data( &smtp ); + // flags + assert( !data.closeConnectionOnError() ); + assert( data.mustBeLastInPipeline() ); + assert( !data.mustBeFirstInPipeline() ); + + // initial state + assert( !data.isComplete() ); + assert( !data.doNotExecute( 0 ) ); + assert( !data.needsResponse() ); + + // dynamics: success + ts.clear(); + assert( data.nextCommandLine( &ts ) == "DATA\r\n" ); + assert( data.isComplete() ); + assert( data.needsResponse() ); + assert( ts.dataCommandIssued() ); + assert( !ts.dataCommandSucceeded() ); + r.clear(); + r.parseLine( "354 Send data, end in <CR><LF>.<CR><LF>" ); + assert( data.processResponse( r, &ts ) == true ); + assert( !data.needsResponse() ); + assert( ts.dataCommandSucceeded() ); + assert( ts.dataResponse() == r ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: failure + smtp.clear(); + DataCommand data2( &smtp ); + ts.clear(); + data2.nextCommandLine( &ts ); + r.clear(); + r.parseLine( "551 No valid recipients" ); + assert( data2.processResponse( r, &ts ) == false ); + assert( !data2.needsResponse() ); + assert( !ts.dataCommandSucceeded() ); + assert( ts.dataResponse() == r ); + assert( smtp.lastErrorCode == 0 ); + + // + // DATA (transfer) + // + + TransferCommand xfer( &smtp, 0 ); + // flags + assert( !xfer.closeConnectionOnError() ); + assert( !xfer.mustBeLastInPipeline() ); + assert( xfer.mustBeFirstInPipeline() ); + + // initial state + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + + // dynamics 1: DATA command failed + ts.clear(); + r.clear(); + r.parseLine( "551 no valid recipients" ); + ts.setDataCommandIssued( true ); + ts.setDataCommandSucceeded( false, r ); + assert( xfer.doNotExecute( &ts ) ); + + // dynamics 2: some recipients rejected, but not all + smtp.clear(); + TransferCommand xfer2( &smtp, 0 ); + ts.clear(); + ts.setRecipientAccepted(); + ts.addRejectedRecipient( "joe@user.org", "No relaying allowed" ); + ts.setDataCommandIssued( true ); + r.clear(); + r.parseLine( "354 go on" ); + ts.setDataCommandSucceeded( true, r ); + // ### will change with allow-partial-delivery option: + assert( xfer.doNotExecute( &ts ) ); + + // successful dynamics with all combinations of: + enum { + EndInLF = 1, + PerformDotStuff = 2, + UngetLast = 4, + Preloading = 8, + Error = 16, + EndOfOptions = 32 + }; + for ( unsigned int i = 0 ; i < EndOfOptions ; ++i ) + checkSuccessfulTransferCommand( i & Error, i & Preloading, i & UngetLast, + i & PerformDotStuff, i & EndInLF ); + + // + // NOOP + // + + smtp.clear(); + NoopCommand noop( &smtp ); + // flags + assert( !noop.closeConnectionOnError() ); + assert( noop.mustBeLastInPipeline() ); + assert( !noop.mustBeFirstInPipeline() ); + + // initial state + assert( !noop.isComplete() ); + assert( !noop.doNotExecute( &ts ) ); + assert( !noop.needsResponse() ); + + // dynamics: success (failure is tested with RSET) + assert( noop.nextCommandLine( 0 ) == "NOOP\r\n" ); + assert( noop.isComplete() ); + assert( noop.needsResponse() ); + r.clear(); + r.parseLine( "250 Ok" ); + assert( noop.processResponse( r, 0 ) == true ); + assert( noop.isComplete() ); + assert( !noop.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // + // RSET + // + + smtp.clear(); + RsetCommand rset( &smtp ); + // flags + assert( rset.closeConnectionOnError() ); + assert( !rset.mustBeLastInPipeline() ); + assert( !rset.mustBeFirstInPipeline() ); + + // initial state + assert( !rset.isComplete() ); + assert( !rset.doNotExecute( &ts ) ); + assert( !rset.needsResponse() ); + + // dynamics: failure (success is tested with NOOP/QUIT) + assert( rset.nextCommandLine( 0 ) == "RSET\r\n" ); + assert( rset.isComplete() ); + assert( rset.needsResponse() ); + r.clear(); + r.parseLine( "502 command not implemented" ); + assert( rset.processResponse( r, 0 ) == false ); + assert( rset.isComplete() ); + assert( !rset.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); // an RSET failure isn't worth it, is it? + assert( smtp.lastErrorMessage.isNull() ); + + // + // QUIT + // + + smtp.clear(); + QuitCommand quit( &smtp ); + // flags + assert( quit.closeConnectionOnError() ); + assert( quit.mustBeLastInPipeline() ); + assert( !quit.mustBeFirstInPipeline() ); + + // initial state + assert( !quit.isComplete() ); + assert( !quit.doNotExecute( 0 ) ); + assert( !quit.needsResponse() ); + + // dynamics 1: success + assert( quit.nextCommandLine( 0 ) == "QUIT\r\n" ); + assert( quit.isComplete() ); + assert( quit.needsResponse() ); + r.clear(); + r.parseLine( "221 Goodbye" ); + assert( quit.processResponse( r, 0 ) == true ); + assert( quit.isComplete() ); + assert( !quit.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); + assert( smtp.lastErrorMessage.isNull() ); + + // dynamics 2: success + smtp.clear(); + QuitCommand quit2( &smtp ); + quit2.nextCommandLine( 0 ); + r.clear(); + r.parseLine( "500 unknown command" ); + assert( quit2.processResponse( r, 0 ) == false ); + assert( quit2.isComplete() ); + assert( !quit2.needsResponse() ); + assert( smtp.lastErrorCode == 0 ); // an QUIT failure isn't worth it, is it? + assert( smtp.lastErrorMessage.isNull() ); +#endif + + return 0; +} + +void checkSuccessfulTransferCommand( bool error, bool preload, bool ungetLast, + bool slaveDotStuff, bool mailEndsInNewline ) { + kdDebug() << " ===== checkTransferCommand( " + << error << ", " + << preload << ", " + << ungetLast << ", " + << slaveDotStuff << ", " + << mailEndsInNewline << " ) =====" << endl; + + SMTPProtocol smtp; + if ( slaveDotStuff ) + smtp.metadata["lf2crlf+dotstuff"] = "slave"; + + Response r; + + const char * s_pre = slaveDotStuff ? + mailEndsInNewline ? foobarbaz_lf : foobarbaz + : + mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ; + const unsigned int s_pre_len = qstrlen( s_pre ); + + const char * s_post = mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ; + //const unsigned int s_post_len = qstrlen( s_post ); + + TransferCommand xfer( &smtp, preload ? s_post : 0 ); + + TransactionState ts; + ts.setRecipientAccepted(); + ts.setDataCommandIssued( true ); + r.clear(); + r.parseLine( "354 ok" ); + ts.setDataCommandSucceeded( true, r ); + assert( !xfer.doNotExecute( &ts ) ); + if ( preload ) { + assert( xfer.nextCommandLine( &ts ) == s_post ); + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + } + smtp.nextData.duplicate( s_pre, s_pre_len ); + smtp.nextDataReturnCode = s_pre_len; + assert( xfer.nextCommandLine( &ts ) == s_post ); + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + smtp.nextData.resize( 0 ); + smtp.nextDataReturnCode = 0; + if ( ungetLast ) { + xfer.ungetCommandLine( xfer.nextCommandLine( &ts ), &ts ); + assert( !xfer.isComplete() ); + assert( !xfer.needsResponse() ); + assert( !ts.complete() ); + smtp.nextDataReturnCode = -1; // double read -> error + } + if ( mailEndsInNewline ) + assert( xfer.nextCommandLine( &ts ) == ".\r\n" ); + else + assert( xfer.nextCommandLine( &ts ) == "\r\n.\r\n" ); + assert( xfer.isComplete() ); + assert( xfer.needsResponse() ); + assert( !ts.complete() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + r.clear(); + if ( error ) { + r.parseLine( "552 Exceeded storage allocation" ); + assert( xfer.processResponse( r, &ts ) == false ); + assert( !xfer.needsResponse() ); + assert( ts.complete() ); + assert( ts.failed() ); + assert( smtp.lastErrorCode == KIO::ERR_DISK_FULL ); + } else { + r.parseLine( "250 Message accepted" ); + assert( xfer.processResponse( r, &ts ) == true ); + assert( !xfer.needsResponse() ); + assert( ts.complete() ); + assert( !ts.failed() ); + assert( smtp.lastErrorCode == 0 ); + } +}; + +#define NDEBUG + +#include "command.cc" +#include "response.cc" +#include "transactionstate.cc" diff --git a/kioslave/smtp/test_headergeneration.cc b/kioslave/smtp/test_headergeneration.cc new file mode 100644 index 000000000..bdf8b251f --- /dev/null +++ b/kioslave/smtp/test_headergeneration.cc @@ -0,0 +1,86 @@ +#include "request.h" + +//#include <iostream> + +//using std::cout; +//using std::endl; + +int main( int , char ** ) { + static QCString expected = + "From: mutz@kde.org\r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: Marc Mutz <mutz@kde.org>\r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: \"Mutz, Marc\" <mutz@kde.org>\r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= <mutz@kde.org>\r\n" + "Subject: missing subject\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: mutz@kde.org\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: Marc Mutz <mutz@kde.org>\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: \"Mutz, Marc\" <mutz@kde.org>\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n" + "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= <mutz@kde.org>\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: joe@user.org,\r\n" + "\tvalentine@14th.february.org\r\n" + "Cc: boss@example.com\r\n" + "\n"; + + KioSMTP::Request request; + QCString result; + + request.setEmitHeaders( true ); + request.setFromAddress( "mutz@kde.org" ); + request.addTo( "joe@user.org" ); + request.addTo( "valentine@14th.february.org" ); + request.addCc( "boss@example.com" ); + + result += request.headerFields() + '\n'; + result += request.headerFields( "Marc Mutz" ) + '\n'; + result += request.headerFields( "Mutz, Marc" ) + '\n'; + result += request.headerFields( "Marc Mötz" ) + '\n'; + + request.setSubject( "Blödes Subject" ); + + result += request.headerFields() + '\n'; + result += request.headerFields( "Marc Mutz" ) + '\n'; + result += request.headerFields( "Mutz, Marc" ) + '\n'; + result += request.headerFields( "Marc Mötz" ) + '\n'; + + //cout << "Result:\n" << result.data() << endl; + + return result == expected ? 0 : 1 ; +} + +#include "request.cc" + diff --git a/kioslave/smtp/test_responseparser.cc b/kioslave/smtp/test_responseparser.cc new file mode 100644 index 000000000..5daa2fb3b --- /dev/null +++ b/kioslave/smtp/test_responseparser.cc @@ -0,0 +1,107 @@ +#include "response.h" +#include <assert.h> + +static const QCString singleLineResponseCRLF = "250 OK\r\n"; +static const QCString singleLineResponse = "250 OK"; + +static const QCString multiLineResponse[] = { + "250-ktown.kde.org\r\n", + "250-STARTTLS\r\n", + "250-AUTH PLAIN DIGEST-MD5\r\n", + "250 PIPELINING\r\n" +}; +static const unsigned int numMultiLineLines = sizeof multiLineResponse / sizeof *multiLineResponse ; + +int main ( int, char** ) { + + KioSMTP::Response r; + assert( r.isValid() ); + assert( r.lines().empty() ); + assert( r.isWellFormed() ); + assert( r.code() == 0 ); + assert( r.isUnknown() ); + assert( !r.isComplete() ); + assert( !r.isOk() ); + r.parseLine( singleLineResponseCRLF.data(), + singleLineResponseCRLF.length() ); + assert( r.isWellFormed() ); + assert( r.isComplete() ); + assert( r.isValid() ); + assert( r.isPositive() ); + assert( r.isOk() ); + assert( r.code() == 250 ); + assert( r.errorCode() == 0 ); + assert( r.first() == 2 ); + assert( r.second() == 5 ); + assert( r.third() == 0 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front() == "OK" ); + r.parseLine( singleLineResponse.data(), + singleLineResponse.length() ); + assert( !r.isValid() ); + r.clear(); + assert( r.isValid() ); + assert( r.lines().empty() ); + + r.parseLine( singleLineResponse.data(), + singleLineResponse.length() ); + assert( r.isWellFormed() ); + assert( r.isComplete() ); + assert( r.isValid() ); + assert( r.isPositive() ); + assert( r.isOk() ); + assert( r.code() == 250 ); + assert( r.first() == 2 ); + assert( r.second() == 5 ); + assert( r.third() == 0 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front() == "OK" ); + r.parseLine( singleLineResponse.data(), + singleLineResponse.length() ); + assert( !r.isValid() ); + r.clear(); + assert( r.isValid() ); + + for ( unsigned int i = 0 ; i < numMultiLineLines ; ++i ) { + r.parseLine( multiLineResponse[i].data(), + multiLineResponse[i].length() ); + assert( r.isWellFormed() ); + if ( i < numMultiLineLines-1 ) + assert( !r.isComplete() ); + else + assert( r.isComplete() ); + assert( r.isValid() ); + assert( r.isPositive() ); + assert( r.code() == 250 ); + assert( r.first() == 2 ); + assert( r.second() == 5 ); + assert( r.third() == 0 ); + assert( r.lines().count() == i + 1 ); + } + assert( r.lines().back() == "PIPELINING" ); + + r.clear(); + r.parseLine( "230", 3 ); + assert( r.isValid() ); + assert( r.isWellFormed() ); // even though it isn't ;-) + assert( r.code() == 230 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front().isNull() ); + + r.clear(); + r.parseLine( "230\r\n", 5 ); + assert( r.isValid() ); + assert( r.isWellFormed() ); // even though it isn't ;-) + assert( r.code() == 230 ); + assert( r.lines().count() == 1 ); + assert( r.lines().front().isNull() ); + + r.clear(); + r.parseLine( " 23 ok", 6 ); + assert( !r.isValid() ); + assert( !r.isWellFormed() ); + + return 0; +} + +#include "response.cc" diff --git a/kioslave/smtp/transactionstate.cc b/kioslave/smtp/transactionstate.cc new file mode 100644 index 000000000..3bf3c7614 --- /dev/null +++ b/kioslave/smtp/transactionstate.cc @@ -0,0 +1,114 @@ +/* -*- c++ -*- + transactionstate.cc + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include <config.h> + +#include "transactionstate.h" + +#include <kio/global.h> +#include <klocale.h> + +#include <qstringlist.h> + +namespace KioSMTP { + + void TransactionState::setFailedFatally( int code, const QString & msg ) { + mFailed = mFailedFatally = true; + mErrorCode = code; + mErrorMessage = msg; + } + + void TransactionState::setMailFromFailed( const QString & addr, const Response & r ) { + setFailed(); + mErrorCode = KIO::ERR_NO_CONTENT; + if ( addr.isEmpty() ) + mErrorMessage = i18n("The server did not accept a blank sender address.\n" + "%1").arg( r.errorMessage() ); + else + mErrorMessage = i18n("The server did not accept the sender address \"%1\".\n" + "%2").arg( addr ).arg( r.errorMessage() ); + } + + void TransactionState::addRejectedRecipient( const RecipientRejection & r ) { + mRejectedRecipients.push_back( r ); + if ( mRcptToDenyIsFailure ) + setFailed(); + } + + void TransactionState::setDataCommandSucceeded( bool succeeded, const Response & r ) { + mDataCommandSucceeded = succeeded; + mDataResponse = r; + if ( !succeeded ) + setFailed(); + else if ( failed() ) + // can happen with pipelining: the server accepts the DATA, but + // we don't want to send the data, so force a connection + // shutdown: + setFailedFatally(); + } + + int TransactionState::errorCode() const { + if ( !failed() ) + return 0; + if ( mErrorCode ) + return mErrorCode; + if ( haveRejectedRecipients() || !dataCommandSucceeded() ) + return KIO::ERR_NO_CONTENT; + // ### what else? + return KIO::ERR_INTERNAL; + } + + QString TransactionState::errorMessage() const { + if ( !failed() ) + return QString::null; + + if ( !mErrorMessage.isEmpty() ) + return mErrorMessage; + + if ( haveRejectedRecipients() ) { + QString msg = i18n("Message sending failed since the following recipients were rejected by the server:\n" + "%1"); + QStringList recip; + for ( RejectedRecipientList::const_iterator it = mRejectedRecipients.begin() ; + it != mRejectedRecipients.end() ; ++it ) + recip.push_back( (*it).recipient + " (" + (*it).reason + ')' ); + return msg.arg( recip.join("\n") ); + } + + if ( !dataCommandSucceeded() ) + return i18n("The attempt to start sending the message content failed.\n" + "%1").arg( mDataResponse.errorMessage() ); + + // ### what else? + return i18n("Unhandled error condition. Please send a bug report."); + } + +} diff --git a/kioslave/smtp/transactionstate.h b/kioslave/smtp/transactionstate.h new file mode 100644 index 000000000..96376718e --- /dev/null +++ b/kioslave/smtp/transactionstate.h @@ -0,0 +1,185 @@ +/* -*- c++ -*- + transactionstate.h + + This file is part of kio_smtp, the KDE SMTP kioslave. + Copyright (c) 2003 Marc Mutz <mutz@kde.org> + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + This program 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 + 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 Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), 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 + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KIOSMTP_TRANSACTIONSTATE_H__ +#define __KIOSMTP_TRANSACTIONSTATE_H__ + +#include "response.h" + +#include <qstring.h> +#include <qvaluelist.h> + +namespace KioSMTP { + + /** + @short A class modelling an SMTP transaction's state + + This class models SMTP transaction state, ie. the collective + result of the MAIL FROM:, RCPT TO: and DATA commands. This is + needed since e.g. a single failed RCPT TO: command does not + neccessarily fail the whole transaction (servers are free to + accept delivery for some recipients, but not for others). + + The class can operate in two modes, which differ in the way + failed recipients are handled. If @p rcptToDenyIsFailure is true + (the default), then any failing RCPT TO: will cause the + transaction to fail. Since at the point of RCPT TO: failure + detection, the DATA command may have already been sent + (pipelining), the only way to cancel the transaction is to take + down the connection hard (ie. without proper quit). + + Since that is not very nice behaviour, a second mode that is more + to the spirit of SMTP is provided that can cope with partially + failed RCPT TO: commands. + */ + class TransactionState { + public: + struct RecipientRejection { + RecipientRejection( const QString & who=QString::null, + const QString & why=QString::null ) + : recipient( who ), reason( why ) {} + QString recipient; + QString reason; +#ifdef KIOSMTP_COMPARATORS + bool operator==( const RecipientRejection & other ) const { + return recipient == other.recipient && reason == other.reason; + } +#endif + }; + typedef QValueList<RecipientRejection> RejectedRecipientList; + + TransactionState( bool rcptToDenyIsFailure=true ) + : mErrorCode( 0 ), + mRcptToDenyIsFailure( rcptToDenyIsFailure ), + mAtLeastOneRecipientWasAccepted( false ), + mDataCommandIssued( false ), + mDataCommandSucceeded( false ), + mFailed( false ), + mFailedFatally( false ), + mComplete( false ) {} + + /** @return whether the transaction failed (e.g. the server + rejected all recipients. Graceful failure is handled after + transaction ends. */ + bool failed() const { return mFailed || mFailedFatally; } + void setFailed() { mFailed = true; } + + /** @return whether the failure was so grave that an immediate + untidy connection shutdown is in order (ie. @ref + smtp_close(false)). Fatal failure is handled immediately */ + bool failedFatally() const { return mFailedFatally; } + void setFailedFatally( int code=0, const QString & msg=QString::null ); + + /** @return whether the transaction was completed successfully */ + bool complete() const { return mComplete; } + void setComplete() { mComplete = true; } + + /** @return an appropriate KIO error code in case the transaction + failed, or 0 otherwise */ + int errorCode() const; + /** @return an appropriate error message in case the transaction + failed or QString::null otherwise */ + QString errorMessage() const; + + void setMailFromFailed( const QString & addr, const Response & r ); + + bool dataCommandIssued() const { return mDataCommandIssued; } + void setDataCommandIssued( bool issued ) { mDataCommandIssued = issued; } + + bool dataCommandSucceeded() const { + return mDataCommandIssued && mDataCommandSucceeded; + } + void setDataCommandSucceeded( bool succeeded, const Response & r ); + + Response dataResponse() const { + return mDataResponse; + } + + bool atLeastOneRecipientWasAccepted() const { + return mAtLeastOneRecipientWasAccepted; + } + void setRecipientAccepted() { + mAtLeastOneRecipientWasAccepted = true; + } + + bool haveRejectedRecipients() const { + return !mRejectedRecipients.empty(); + } + RejectedRecipientList rejectedRecipients() const { + return mRejectedRecipients; + } + void addRejectedRecipient( const RecipientRejection & r ); + void addRejectedRecipient( const QString & who, const QString & why ) { + addRejectedRecipient( RecipientRejection( who, why ) ); + } + + void clear() { + mRejectedRecipients.clear(); + mDataResponse.clear(); + mAtLeastOneRecipientWasAccepted + = mDataCommandIssued + = mDataCommandSucceeded + = mFailed = mFailedFatally + = mComplete = false; + } + +#ifdef KIOSMTP_COMPARATORS + bool operator==( const TransactionState & other ) const { + return + mAtLeastOneRecipientWasAccepted == other.mAtLeastOneRecipientWasAccepted && + mDataCommandIssued == other.mDataCommandIssued && + mDataCommandSucceeded == other.mDataCommandSucceeded && + mFailed == other.mFailed && + mFailedFatally == other.mFailedFatally && + mComplete == other.mComplete && + mDataResponse.code() == other.mDataResponse.code() && + mRejectedRecipients == other.mRejectedRecipients; + } +#endif + + + private: + RejectedRecipientList mRejectedRecipients; + Response mDataResponse; + QString mErrorMessage; + int mErrorCode; + bool mRcptToDenyIsFailure; + bool mAtLeastOneRecipientWasAccepted; + bool mDataCommandIssued; + bool mDataCommandSucceeded; + bool mFailed; + bool mFailedFatally; + bool mComplete; + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_TRANSACTIONSTATE_H__ diff --git a/kioslave/system/Makefile.am b/kioslave/system/Makefile.am new file mode 100644 index 000000000..fb71211dc --- /dev/null +++ b/kioslave/system/Makefile.am @@ -0,0 +1,31 @@ +SUBDIRS= . kdedmodule entries mimetypes + +INCLUDES = $(all_includes) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kio_system.la + +kio_system_la_SOURCES = dummy.cpp +kio_system_la_LIBADD = libkiosystem.la $(LIB_KIO) +kio_system_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined + +dummy.cpp: + echo > dummy.cpp + +kde_services_DATA = system.protocol + +noinst_LTLIBRARIES = libkiosystem.la +libkiosystem_la_SOURCES = kio_system.cpp systemimpl.cpp + +check_PROGRAMS = testsystem +testsystem_SOURCES = testsystem.cpp +testsystem_LDADD = libkiosystem.la $(LIB_KIO) +testsystem_LDFLAGS = $(all_libraries) + +## TODO in unsermake: TESTS = testsystem +check: testsystem + ./testsystem + +messages: + $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/kio_system.pot + diff --git a/kioslave/system/dummy.cpp b/kioslave/system/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/kioslave/system/dummy.cpp @@ -0,0 +1 @@ + diff --git a/kioslave/system/entries/Makefile.am b/kioslave/system/entries/Makefile.am new file mode 100644 index 000000000..1e356badb --- /dev/null +++ b/kioslave/system/entries/Makefile.am @@ -0,0 +1,5 @@ +systemviewdir = $(kde_datadir)/systemview +systemview_DATA = home.desktop documents.desktop users.desktop media.desktop remote.desktop trash.desktop + +bin_SCRIPTS = kio_system_documenthelper + diff --git a/kioslave/system/entries/documents.desktop b/kioslave/system/entries/documents.desktop new file mode 100644 index 000000000..16016e0c5 --- /dev/null +++ b/kioslave/system/entries/documents.desktop @@ -0,0 +1,68 @@ +[Desktop Entry] +Type=Link +Path[$e]=$( kio_system_documenthelper ) +Icon=folder_important +Name=Documents Folder +Name[af]=Dokument Gids +Name[ar]=مستنداتي +Name[be]=ТÑчка Ð´Ð»Ñ Ð´Ð°ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñž +Name[bg]=Ð”Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð¸ +Name[bn]=ডকà§à¦®à§‡à¦¨à§à¦Ÿ ফোলà§à¦¡à¦¾à¦° +Name[br]=Renkell an teulioù +Name[bs]=Direktorij dokumenata +Name[ca]=Carpeta de documents +Name[cs]=Složka s dokumenty +Name[csb]=Katalog dokùmentów +Name[da]=Dokumentmappe +Name[de]=Dokumente +Name[el]=Φάκελος εγγÏάφων +Name[eo]=Dokumenta dosierujo +Name[es]=Carpeta de documentos +Name[et]=Dokumentide kataloog +Name[eu]=Dokumentuen karpeta +Name[fa]=پوشۀ مستندات +Name[fi]=Asiakirjakansio +Name[fr]=Dossier des documents +Name[fy]=Ofkeppele kamera +Name[ga]=Fillteán na gCáipéisà +Name[gl]=Cartafol de Documentos +Name[he]=תיקיית ×ž×¡×ž×›×™× +Name[hr]=Mapa dokumenata +Name[hu]=Dokumentumok könyvtár +Name[is]=Skjalamappa +Name[it]=Cartella documenti +Name[ja]=ドã‚ュメントフォルダ +Name[ka]=დáƒáƒ™áƒ£áƒ›áƒ”ნტების დáƒáƒ¡áƒ¢áƒ +Name[kk]=Құжаттар қапшығы +Name[km]=ážážâ€‹áž¯áž€ážŸáž¶ážš +Name[ko]=문서 íƒìƒ‰ê¸° +Name[lt]=Dokumentų aplankai +Name[mk]=Папка Ñо документи +Name[nb]=Dokumentmappe +Name[nds]=Dokmenten-Orner +Name[ne]=कागजात फोलà¥à¤¡à¤° +Name[nl]=Documenten +Name[nn]=Dokumentmappe +Name[pa]=ਦਸਤਾਵੇਜ਼ ਫੋਲਡਰ +Name[pl]=Katalog dokumentów +Name[pt]=Pasta de Documentos +Name[pt_BR]=Pasta de Documentos +Name[ro]=Folder documente +Name[ru]=Документы +Name[se]=Dokumeantamáhppa +Name[sk]=PrieÄinok dokumentov +Name[sl]=Mapa z dokumenti +Name[sr]=ФаÑцикла Ñа документима +Name[sr@Latn]=Fascikla sa dokumentima +Name[sv]=Dokumentkatalog +Name[te]=పతà±à°°à°¾à°² ఫొలà±à°¡à°°à± +Name[tg]=Ҳуҷҷатҳо +Name[th]=โฟลเดà¸à¸£à¹Œà¹€à¸à¸à¸ªà¸²à¸£ +Name[tr]=Belgeler Dizini +Name[uk]=Тека документів +Name[uz]=Hujjatlar jildi +Name[uz@cyrillic]=Ҳужжатлар жилди +Name[vi]=ThÆ° mục Tà i liệu +Name[wa]=Ridant documints +Name[zh_CN]=文档文件夹 +Name[zh_TW]=文件目錄 diff --git a/kioslave/system/entries/home.desktop b/kioslave/system/entries/home.desktop new file mode 100644 index 000000000..56c07117a --- /dev/null +++ b/kioslave/system/entries/home.desktop @@ -0,0 +1,78 @@ +[Desktop Entry] +Type=Link +Path=$HOME +Icon=folder_home +Name=Home Folder +Name[af]=Tuis Gids +Name[ar]=منزلي +Name[az]=Ev QovluÄŸu +Name[be]=ХатнÑÑ Ñ‚Ñчка +Name[bg]=Домашна Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ +Name[bn]=বà§à¦¯à¦•à§à¦¤à¦¿à¦—ত ফোলà§à¦¡à¦¾à¦° +Name[br]=Renkell ar gêr +Name[bs]=Home direktorij +Name[ca]=Carpeta inici +Name[cs]=Domovská složka +Name[csb]=Domôcy katalog +Name[cy]=Plygell Cartref +Name[da]=Hjemmemappe +Name[de]=Persönlicher Ordner +Name[el]=Î Ïοσωπικός φάκελος +Name[eo]=Hejma dosierujo +Name[es]=Carpeta personal +Name[et]=Kodukataloog +Name[eu]=Etxeko karpeta +Name[fa]=پوشۀ آغازه +Name[fi]=Kotikansio +Name[fr]=Dossier personnel +Name[fy]=Persoanlike map +Name[ga]=Fillteán Baile +Name[gl]=Cartafol Persoal +Name[he]=תיקיית בית +Name[hi]=मà¥à¤– फ़ोलà¥à¤¡à¤° +Name[hr]=PoÄetna mapa korisnika +Name[hu]=Saját könyvtár +Name[is]=Heimasvæði +Name[it]=Cartella Home +Name[ja]=ホームフォルダ +Name[ka]=სáƒáƒ¬áƒ§áƒ˜áƒ¡áƒ˜ სáƒáƒ¥áƒáƒ¦áƒáƒšáƒ“ე +Name[kk]=Мекен қапшығы +Name[km]=ážážâ€‹áž•áŸ’ទះ +Name[ko]=홈 í´ë” +Name[lt]=Namų aplankas +Name[lv]=MÄjas mape +Name[mk]=Домашна папка +Name[mn]=Хувийн лавлах +Name[ms]=Folder Laman Utama +Name[mt]=Direttorju Personali +Name[nb]=Hjemmemappe +Name[nds]=Tohuus-Orner +Name[ne]=गृह फोलà¥à¤¡à¤° +Name[nl]=Persoonlijke map +Name[nn]=Heimemappe +Name[pa]=ਘਰ ਫੋਲਡਰ +Name[pl]=Katalog domowy +Name[pt]=Pasta Pessoal +Name[pt_BR]=Pasta do Usuário +Name[ro]=Folder personal +Name[ru]=ДомашнÑÑ Ð¿Ð°Ð¿ÐºÐ° +Name[rw]=Ububiko Urugo +Name[se]=Ruoktomáhppa +Name[sk]=Domovský prieÄinok +Name[sl]=DomaÄa mapa +Name[sr]=Домаћа фаÑцикла +Name[sr@Latn]=Domaća fascikla +Name[sv]=Hemkatalog +Name[ta]=வீடà¯à®Ÿà¯ அடைவ௠+Name[te]=ఇంటి ఫొలà±à°¡à°°à± +Name[tg]=ФеҳриÑти хонагӣ +Name[th]=โฟลเดà¸à¸£à¹Œà¸ªà¹ˆà¸§à¸™à¸•à¸±à¸§ +Name[tr]=BaÅŸlangıç Dizini +Name[tt]=Ana Törgäk +Name[uk]=Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñ‚ÐµÐºÐ° +Name[uz]=Uy jildi +Name[uz@cyrillic]=Уй жилди +Name[vi]=ThÆ° mục Nhà +Name[wa]=Ridant mÃ¥jhon +Name[zh_CN]=主文件夹 +Name[zh_TW]=家目錄 diff --git a/kioslave/system/entries/kio_system_documenthelper b/kioslave/system/entries/kio_system_documenthelper new file mode 100755 index 000000000..7cc2c2b2d --- /dev/null +++ b/kioslave/system/entries/kio_system_documenthelper @@ -0,0 +1,14 @@ +#!/bin/sh +# Script used by by the document.desktop entry to determine if it must be +# shown or not +# +# Copyright GPL v2 by Kevin Ottens <ervin ipsquad net> +# + +document_path=`kde-config --userpath document | sed "s/\/\$//"` +home_path=`echo $HOME | sed "s/\/\$//"` + +if test "$document_path" != "$home_path" ; then + echo $document_path +fi + diff --git a/kioslave/system/entries/media.desktop b/kioslave/system/entries/media.desktop new file mode 100644 index 000000000..8ad83923a --- /dev/null +++ b/kioslave/system/entries/media.desktop @@ -0,0 +1,71 @@ +[Desktop Entry] +Type=Link +URL=media:/ +Icon=system +Name=Storage Media +Name[af]=Stoor Media +Name[ar]=وسائط التخزين +Name[be]=ÐоÑьбіты +Name[bg]=СъхранÑващи уÑтройÑтва +Name[bn]=সà§à¦Ÿà§‹à¦°à§‡à¦œ মিডিয়া +Name[bs]=UreÄ‘aji za smjeÅ¡taj podataka +Name[ca]=Suports d'emmagatzematge +Name[cs]=Úložná zaÅ™Ãzenà +Name[csb]=Zôpisowné media +Name[da]=Opbevaringsmedie +Name[de]=Speichermedien +Name[el]=ΣυσκευÎÏ‚ αποθήκευσης +Name[eo]=Enmemoriga Medio +Name[es]=Dispositivos de almacenamiento +Name[et]=Andmekandjad +Name[eu]=Biltegiratze-euskarria +Name[fa]=رسانۀ ذخیره‌گاه +Name[fi]=Tallennusmedia +Name[fr]=Support de stockage +Name[fy]=Opslachapparaten +Name[ga]=Meán Stórais +Name[gl]=Medios de armacenaxe +Name[he]=×”×ª×§× ×™× +Name[hi]=à¤à¤‚डार मीडिया +Name[hr]=Mediji za pohranjivanje +Name[hu]=Tárolóeszközök +Name[is]=Geymslumiðlar +Name[it]=Dispositivi di archiviazione +Name[ja]=記憶メディア +Name[ka]=მáƒáƒœáƒáƒªáƒ”მთრშენáƒáƒ®áƒ•áƒ˜áƒ¡ მáƒáƒ¬áƒ§áƒáƒ‘ილáƒáƒ‘ები +Name[kk]=Жинақтаушы құрылғылар +Name[km]=ឧបករណáŸâ€‹áž•áŸ’ទុក +Name[lt]=Saugojimo įrenginiai +Name[lv]=Datu nesÄ“js +Name[mk]=Медиуми за податоци +Name[ms]=Media Storan +Name[nb]=Lagringsenheter +Name[nds]=Spiekermedien +Name[ne]=à¤à¤£à¥à¤¡à¤¾à¤°à¤£ मिडिया +Name[nl]=Opslagapparaten +Name[nn]=Lagringsmedium +Name[pa]=ਸਟੋਰੇਜ਼ ਮੀਡਿਆ +Name[pl]=UrzÄ…dzenia przechowywania danych +Name[pt]=Dispositivos de Armazenamento +Name[pt_BR]=MÃdia de Armazenamento +Name[ro]=Mediu de stocare +Name[ru]=УÑтройÑтва Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ñ‹Ñ… +Name[rw]=Uburyo bwo Kubika +Name[se]=Vurkenmedia +Name[sk]=Zálohovacie médiá +Name[sl]=Nosilci za shranjevanje +Name[sr]=Складишни медијуми +Name[sr@Latn]=SkladiÅ¡ni medijumi +Name[sv]=Lagringsmedia +Name[ta]=சேகரிபà¯à®ªà¯ ஊடகம௠+Name[tg]=Захирагоҳи маълумот +Name[th]=สื่à¸à¹€à¸à¹‡à¸šà¸‚้à¸à¸¡à¸¹à¸¥ +Name[tr]=Depolama Ortamı +Name[tt]=Saqlawlı Media +Name[uk]=ПриÑтрої Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— +Name[uz]=Saqlash uskunalari +Name[uz@cyrillic]=Сақлаш уÑкуналари +Name[vi]=á»” chứa Dữ liệu +Name[wa]=Sopoirts di wÃ¥rdaedje +Name[zh_CN]=å˜å‚¨ä»‹è´¨ +Name[zh_TW]=儲å˜åª’é«” diff --git a/kioslave/system/entries/remote.desktop b/kioslave/system/entries/remote.desktop new file mode 100644 index 000000000..8f064a83b --- /dev/null +++ b/kioslave/system/entries/remote.desktop @@ -0,0 +1,73 @@ +[Desktop Entry] +Type=Link +URL=remote:/ +Icon=network +Name=Remote Places +Name[af]=Afgeleë Plekke +Name[ar]=أماكن بعيدة +Name[be]=ÐÐ´Ð´Ð°Ð»ÐµÐ½Ñ‹Ñ Ð¼ÐµÑцы +Name[bg]=Отдалечени директории +Name[bn]=দূরবরà§à¦¤à§€ অবসà§à¦¥à¦¾à¦¨ +Name[br]=Lec'hiadurioù a-bell +Name[bs]=Udaljena mjesta +Name[ca]=Llocs remots +Name[cs]=Vzdálená umÃstÄ›nà +Name[csb]=Daleczi place +Name[da]=Eksterne steder +Name[de]=Netzwerk +Name[el]=ΑπομακÏυσμÎνες τοποθεσίες +Name[eo]=Foraj Lokoj +Name[es]=Lugares remotos +Name[et]=Võrgukohad +Name[eu]=Urruneko lekuak +Name[fa]=جاهای دور +Name[fi]=Etäkohteet +Name[fr]=Emplacements distants +Name[fy]=Eksterne plakken +Name[ga]=Ãiteanna Cianda +Name[gl]=Comparticións Remotas +Name[he]=מקומות ×ž×¨×•×—×§×™× +Name[hi]=रिमोट सà¥à¤¥à¤¾à¤¨ +Name[hr]=Udaljene lokacije +Name[hu]=Hálózati helyek +Name[is]=Fjarlægir staðir +Name[it]=Indirizzi remoti +Name[ja]=リモートã®å ´æ‰€ +Name[ka]=დáƒáƒ¨áƒáƒ ებული áƒáƒ“გილები +Name[kk]=Қашықтағы орындар +Name[km]=កន្លែង​ពី​ចម្ងាយ +Name[ko]=ì›ê²© ì ‘ì† +Name[lt]=Nutolusios vietos +Name[lv]=AttÄlinÄtÄs vietas +Name[mk]=Оддалечени меÑта +Name[ms]=Tempat Jauh +Name[nb]=Eksterne steder +Name[nds]=Feern Steden +Name[ne]=टाढाको सà¥à¤¥à¤¾à¤¨ +Name[nl]=Externe plekken +Name[nn]=Eksterne plassar +Name[pa]=ਰਿਮੋਟ ਥਾਂ +Name[pl]=Zdalne miejsca +Name[pt]=Locais Remotos +Name[pt_BR]=Locais Remotos +Name[ro]=LocaÈ›ii distante +Name[ru]=Сетевые реÑурÑÑ‹ +Name[rw]=Imyanya ya Kure +Name[se]=Gáiddus báikkit +Name[sk]=Vzdialené miesta +Name[sl]=Oddaljena mesta +Name[sr]=Удаљена меÑта +Name[sr@Latn]=Udaljena mesta +Name[sv]=Fjärrplatser +Name[ta]=தொலைதூர இடஙà¯à®•à®³à¯ +Name[te]=à°¸à±à°¦à±‚రపౠపà±à°°à°¦à±‡à°¶à°¾à°²à± +Name[th]=ทางไà¸à¸¥ +Name[tr]=Uzak Yerler +Name[tt]=Çittäge Urınnar +Name[uk]=Віддалені міÑÑ†Ñ +Name[uz]=Masofadagi joylar +Name[uz@cyrillic]=МаÑофадаги жойлар +Name[vi]=Äịa chỉ trên mạng +Name[wa]=Plaeces dÃ¥ lon +Name[zh_CN]=远程ä½ç½® +Name[zh_TW]=é 端空間 diff --git a/kioslave/system/entries/trash.desktop b/kioslave/system/entries/trash.desktop new file mode 100644 index 000000000..7de5f21b6 --- /dev/null +++ b/kioslave/system/entries/trash.desktop @@ -0,0 +1,84 @@ +[Desktop Entry] +Type=Link +URL=trash:/ +Icon=trashcan_full +EmptyIcon=trashcan_empty +Name=Trash +Name[af]=Gemors +Name[ar]=سلة النÙايات +Name[az]=Zibil +Name[be]=Сметніца +Name[bg]=Кошче +Name[bn]=আবরà§à¦œà¦¨à¦¾ +Name[br]=Pod-lastez +Name[bs]=Smeće +Name[ca]=Paperera +Name[cs]=KoÅ¡ +Name[csb]=Kòsz +Name[cy]=Sbwriel +Name[da]=Affald +Name[de]=Mülleimer +Name[el]=Κάδος αποÏÏιμμάτων +Name[en_GB]=Wastebin +Name[eo]=Rubujo +Name[es]=Papelera +Name[et]=Prügikast +Name[eu]=Zaborra +Name[fa]=زباله +Name[fi]=Roskakori +Name[fr]=Corbeille +Name[fy]=Jiskefet +Name[ga]=Bruscar +Name[gl]=Lixo +Name[he]=×שפה +Name[hi]=रदà¥à¤¦à¥€ +Name[hr]=Otpad +Name[hsb]=Papjernik +Name[hu]=Szemétkosár +Name[is]=Rusl +Name[it]=Cestino +Name[ja]=ã”ã¿ç®± +Name[ka]=ურნრ+Name[kk]=Өшірілгендер +Name[km]=ធុងសំរាម +Name[lo]=ຖັງຂີ້ເຫàºàº·à»ˆàº +Name[lt]=Å iukÅ¡liadėžė +Name[lv]=Miskaste +Name[mk]=Корпа +Name[mn]=Хогийн Ñав +Name[ms]=Sampah +Name[mt]=Skart +Name[nb]=Papirkurv +Name[nds]=Affalltünn +Name[ne]=रदà¥à¤¦à¥€à¤Ÿà¥‹à¤•à¤°à¥€ +Name[nl]=Prullenbak +Name[nn]=Papirkorg +Name[nso]=Seswaraditlakala +Name[pa]=ਰੱਦੀ +Name[pl]=Kosz +Name[pt]=Lixo +Name[pt_BR]=Lixo +Name[ro]=Gunoi +Name[ru]=Корзина +Name[se]=Ruskalihtti +Name[sk]=Kôš +Name[sl]=Smeti +Name[sr]=Смеће +Name[sr@Latn]=Smeće +Name[sv]=Skräp +Name[ta]=கà¯à®ªà¯à®ªà¯ˆ +Name[te]=చెతà±à°¤ à°¬à±à°Ÿà±à°Ÿ +Name[tg]=Ðхлотдон +Name[th]=ถังขยะ +Name[tr]=Çöp +Name[tt]=Çüplek +Name[uk]=Смітник +Name[uz]=Chiqindilar qutisi +Name[uz@cyrillic]=Чиқиндилар қутиÑи +Name[ven]=Tshikha +Name[vi]=Thùng rác +Name[wa]=Batch +Name[xh]=Inkukumo +Name[zh_CN]=回收站 +Name[zh_TW]=資æºå›žæ”¶æ¡¶ +Name[zu]=Izibi diff --git a/kioslave/system/entries/users.desktop b/kioslave/system/entries/users.desktop new file mode 100644 index 000000000..3ae601b0b --- /dev/null +++ b/kioslave/system/entries/users.desktop @@ -0,0 +1,67 @@ +[Desktop Entry] +Type=Link +URL=home:/ +Icon=folder_home2 +Name=Users Folders +Name[af]=Gebruiker Gidse +Name[ar]=مجلّدات المستخدمين +Name[be]=ТÑчкі карыÑтальнікаў +Name[bg]=ПотребителÑки директории +Name[bn]=বà§à¦¯à¦¬à¦¹à¦¾à¦°à¦•à¦¾à¦°à§€ ফোলà§à¦¡à¦¾à¦° +Name[br]=Renkellioù an arveriaded +Name[bs]=KorisniÄki direktoriji +Name[ca]=Carpeta d'usuaris +Name[cs]=Složka uživatelů +Name[csb]=Katalodżi brëkòwników +Name[da]=Brugermapper +Name[de]=Alle Persönlichen Ordner +Name[el]=Φάκελος χÏηστών +Name[eo]=Uzantaj dosierujoj +Name[es]=Carpetas de usuarios +Name[et]=Kasutajate kataloogid +Name[eu]=Erabiltzaileen karpetak +Name[fa]=پوشه‌های‌ کاربران +Name[fi]=Käyttäjäkansiot +Name[fr]=Dossiers utilisateurs +Name[fy]=Brûkersmappen +Name[ga]=Fillteáin Úsáideora +Name[gl]=Cartafoles dos Usuarios +Name[he]=תיקיות ×ž×©×ª×ž×©×™× +Name[hr]=KorisniÄke mape +Name[hu]=Felhasználói könyvtárak +Name[is]=Notandamöppur +Name[it]=Cartelle utenti +Name[ja]=ユーザフォルダ +Name[ka]=მáƒáƒ›áƒ®áƒ›áƒáƒ ებლის დáƒáƒ¡áƒ¢áƒ”ბი +Name[kk]=Пайдаланушылардың қапшығы +Name[km]=ážážâ€‹ážšáž”ស់អ្នកប្រើ +Name[ko]=홈 í´ë” +Name[lt]=Naudotojų aplankai +Name[mk]=КориÑнички папки +Name[nb]=Brukermapper +Name[nds]=All Tohuusornern +Name[ne]=पà¥à¤°à¤¯à¥‹à¤—करà¥à¤¤à¤¾ फोलà¥à¤¡à¤° +Name[nl]=Gebruikersmappen +Name[nn]=Brukarmapper +Name[pa]=ਉਪà¨à©‹à¨—à©€ ਫੋਲਡਰ +Name[pl]=Katalogi użytkowników +Name[pt]=Pastas dos Utilizadores +Name[pt_BR]=Pastas do Usuários +Name[ro]=Foldere utilizatori +Name[ru]=ПользовательÑкие папки +Name[se]=Geavaheaddji máhpat +Name[sk]=PrieÄinky použÃvateľov +Name[sl]=Mapa z uporabniki +Name[sr]=ФаÑцикле кориÑникâ +Name[sr@Latn]=Fascikle korisnikâ +Name[sv]=Användares kataloger +Name[te]=à°¯à±à°œà°°à±à°² ఫొలà±à°¡à°°à±à°²à± +Name[th]=โฟลเดà¸à¸£à¹Œà¸‚à¸à¸‡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ +Name[tr]=Kullanıcı Dizinleri +Name[uk]=Теки кориÑтувачів +Name[uz]=Foydalanuvchilarning jildlari +Name[uz@cyrillic]=Фойдаланувчиларнинг жилдлари +Name[vi]=ThÆ° mục NgÆ°á»i dùng +Name[wa]=Ridant uzeu +Name[zh_CN]=用户文件夹 +Name[zh_TW]=使用者目錄 diff --git a/kioslave/system/kdedmodule/Makefile.am b/kioslave/system/kdedmodule/Makefile.am new file mode 100644 index 000000000..bc368f9b1 --- /dev/null +++ b/kioslave/system/kdedmodule/Makefile.am @@ -0,0 +1,13 @@ +kde_module_LTLIBRARIES = kded_systemdirnotify.la + +METASOURCES = AUTO +INCLUDES = $(all_includes) + +kded_systemdirnotify_la_SOURCES = systemdirnotify.cpp systemdirnotify.skel systemdirnotifymodule.cpp systemdirnotifymodule.skel +kded_systemdirnotify_la_LDFLAGS = $(all_libraries) -module -avoid-version +kded_systemdirnotify_la_LIBADD = $(LIB_KSYCOCA) + + +servicesdir = $(kde_servicesdir)/kded +services_DATA = systemdirnotify.desktop + diff --git a/kioslave/system/kdedmodule/systemdirnotify.cpp b/kioslave/system/kdedmodule/systemdirnotify.cpp new file mode 100644 index 000000000..e0ec57992 --- /dev/null +++ b/kioslave/system/kdedmodule/systemdirnotify.cpp @@ -0,0 +1,184 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "systemdirnotify.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kdesktopfile.h> + +#include <kdirnotify_stub.h> + +#include <qdir.h> + +SystemDirNotify::SystemDirNotify() +: mInited( false ) +{ +} + +void SystemDirNotify::init() +{ + if( mInited ) + return; + mInited = true; + KGlobal::dirs()->addResourceType("system_entries", + KStandardDirs::kde_default("data") + "systemview"); + + QStringList names_found; + QStringList dirList = KGlobal::dirs()->resourceDirs("system_entries"); + + QStringList::ConstIterator dirpath = dirList.begin(); + QStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + QDir dir = *dirpath; + if (!dir.exists()) continue; + + QStringList filenames + = dir.entryList( QDir::Files | QDir::Readable ); + + QStringList::ConstIterator name = filenames.begin(); + QStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (!names_found.contains(*name)) + { + KDesktopFile desktop(*dirpath+*name, true); + + QString system_name = *name; + system_name.truncate(system_name.length()-8); + + KURL system_url("system:/"+system_name); + + if ( !desktop.readURL().isEmpty() ) + { + m_urlMap[desktop.readURL()] = system_url; + names_found.append( *name ); + } + else if ( !desktop.readPath().isEmpty() ) + { + KURL url; + url.setPath( desktop.readPath() ); + m_urlMap[url] = system_url; + names_found.append( *name ); + } + } + } + } +} + +KURL SystemDirNotify::toSystemURL(const KURL &url) +{ + kdDebug() << "SystemDirNotify::toSystemURL(" << url << ")" << endl; + + init(); + QMap<KURL,KURL>::const_iterator it = m_urlMap.begin(); + QMap<KURL,KURL>::const_iterator end = m_urlMap.end(); + + for (; it!=end; ++it) + { + KURL base = it.key(); + + if ( base.isParentOf(url) ) + { + QString path = KURL::relativePath(base.path(), + url.path()); + KURL result = it.data(); + result.addPath(path); + result.cleanPath(); + kdDebug() << result << endl; + return result; + } + } + + kdDebug() << "KURL()" << endl; + return KURL(); +} + +KURL::List SystemDirNotify::toSystemURLList(const KURL::List &list) +{ + init(); + KURL::List new_list; + + KURL::List::const_iterator it = list.begin(); + KURL::List::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + KURL url = toSystemURL(*it); + + if (url.isValid()) + { + new_list.append(url); + } + } + + return new_list; +} + +ASYNC SystemDirNotify::FilesAdded(const KURL &directory) +{ + KURL new_dir = toSystemURL(directory); + + if (new_dir.isValid()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesAdded( new_dir ); + if (new_dir.upURL().upURL()==KURL("system:/")) + { + notifier.FilesChanged( new_dir.upURL() ); + } + } +} + +ASYNC SystemDirNotify::FilesRemoved(const KURL::List &fileList) +{ + KURL::List new_list = toSystemURLList(fileList); + + if (!new_list.isEmpty()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesRemoved( new_list ); + + KURL::List::const_iterator it = new_list.begin(); + KURL::List::const_iterator end = new_list.end(); + + for (; it!=end; ++it) + { + if ((*it).upURL().upURL()==KURL("system:/")) + { + notifier.FilesChanged( (*it).upURL() ); + } + } + } +} + +ASYNC SystemDirNotify::FilesChanged(const KURL::List &fileList) +{ + KURL::List new_list = toSystemURLList(fileList); + + if (!new_list.isEmpty()) + { + KDirNotify_stub notifier("*", "*"); + notifier.FilesChanged( new_list ); + } +} + diff --git a/kioslave/system/kdedmodule/systemdirnotify.desktop b/kioslave/system/kdedmodule/systemdirnotify.desktop new file mode 100644 index 000000000..315cb5bf8 --- /dev/null +++ b/kioslave/system/kdedmodule/systemdirnotify.desktop @@ -0,0 +1,64 @@ +[Desktop Entry] +Type=Service +Name=KDED System Base URL Notifier +Name[af]=KDED Stelse URL inkennissteller +Name[be]=Праверка змены ÑÑ–ÑÑ‚Ñмных файлаў KDED +Name[bs]=KDED sistemsko obavjeÅ¡tenje o baznom URLu +Name[ca]=Notificador d'URL de base al sistema KDED +Name[cs]=Démon upozorňovánà na systémová URL +Name[csb]=Dôwanié wiédzë ù systemòwëch URL-ach dlô KDED +Name[da]=KDED Systembais-url pÃ¥mindelser +Name[de]=Ãœberwachung für Systemordner +Name[el]=Ειδοποιητής KDED για URL του συστήματος +Name[eo]=KDED Sistemo Bazo URL Atentigilo +Name[es]=Notificador de URL de base sistema de KDED +Name[et]=KDED süsteemsete URL-ide teadustaja +Name[eu]=KDEren sistema oinarri URL iragarlea +Name[fa]=اخطاردهندۀ نشانی وب پایۀ سیستم KDED +Name[fi]=KDED:in järjestelmä-osoitteiden ilmoittaja +Name[fr]=Notificateur d'URL système KDED +Name[fy]=KDED systeem basis URL-adres melding +Name[gl]=KDED Notificador de Base de URL Remota +Name[hi]=केडीईडी तंतà¥à¤° आधार यूआरà¤à¤² नोटिफ़ॉयर +Name[hr]=KDED sistemsko URL obavjeÅ¡tavanje +Name[hu]=AlapcÃm-értesÃtÅ‘ +Name[is]=KDED kerfis grunnslóðar tilkynnari +Name[it]=Notifica KDED System Base URL +Name[ja]=KDED システムベース URL 通知 +Name[kk]=Жергілікті диÑкідегі Ó©Ð·Ð³ÐµÑ€Ñ–Ñ Ñ‚ÑƒÑ€Ð°Ð»Ñ‹ қулақтандыру +Name[ko]=KDED ì›ê²© 기반 URL 알리미 +Name[lt]=KDED sistemos pagrindinio URL priminiklis +Name[lv]=KDED sistÄ“mas bÄzes URL atgÄdinÄtÄjs +Name[ms]=Pemberitahu URL Pangkalan Sistem KDED +Name[nb]=KDED-Systembase URL-varsler +Name[nds]=KDED-Narichten för Systeem-Basis-URLs +Name[ne]=KDED पà¥à¤°à¤£à¤¾à¤²à¥€ आधारित यूआरà¤à¤² सूचक +Name[nl]=KDED systeem basis URL-adres notificatie +Name[nn]=KDED-Systembase URL-varslar +Name[pa]=KDE ਸਿਸਟਮ ਆਧਾਰ URL ਸੂਚਨਾ +Name[pl]=Powiadamianie o systemowych URL-ach dla KDED +Name[pt]=Notificador URLs de Base de Sistema do KDED +Name[pt_BR]=Serviço de Notificação da URL da Base do Sistema +Name[ro]=Notificare KDED pentru URL sistem de bază +Name[ru]=Проверка Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ñ‹Ñ… файлов +Name[rw]=Mumenyekanisha wa URL KDED Sisitemu Shingiro +Name[sk]=KDED notifikátor systémovej URL +Name[sl]=Obvestilnik KDED sistemskega osnovnega URL-ja +Name[sr]=Обавештавач о ÑиÑтемÑком базном URL-у, KDED +Name[sr@Latn]=ObaveÅ¡tavaÄ o sistemskom baznom URL-u, KDED +Name[sv]=KDED-meddelande om systembaswebbadresser +Name[ta]=KDED அமைபà¯à®ªà¯ சாரà¯à®¨à¯à®¤ வலைமனை கà¯à®±à®¿à®ªà¯à®ªà®¾à®©à¯ +Name[th]=ตัวà¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸à¸™à¸žà¸·à¹‰à¸™à¸à¸²à¸™à¸‚à¸à¸‡à¸£à¸°à¸šà¸š URL KDED +Name[tr]=KDED Sistem Tabanlı URL Hatırlatıcı +Name[tt]=Cirle URL Ãœzgärelü Beldergeçe +Name[uk]=Сповіщувач про ÑиÑтемну оÑновну адреÑу (URL) Ð´Ð»Ñ KDED +Name[vi]=Trình thông báo URL hệ thống KDED +Name[wa]=Notifiaedje KDED d' URL sistinme di bÃ¥ze +Name[zh_CN]=KDED 系统基 URL 通知器 +Name[zh_TW]=KDED 系統基礎 URL é€šçŸ¥ç¨‹å¼ +ServiceTypes=KDEDModule +X-KDE-ModuleType=Library +X-KDE-Library=systemdirnotify +X-KDE-FactoryName=systemdirnotify +X-KDE-Kded-load-on-demand=true +X-KDE-Kded-autoload=true diff --git a/kioslave/system/kdedmodule/systemdirnotify.h b/kioslave/system/kdedmodule/systemdirnotify.h new file mode 100644 index 000000000..34da65b82 --- /dev/null +++ b/kioslave/system/kdedmodule/systemdirnotify.h @@ -0,0 +1,47 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _SYSTEMDIRNOTIFY_H_ +#define _SYSTEMDIRNOTIFY_H_ + +#include <kurl.h> +#include <kdirnotify.h> +#include <qmap.h> + +class SystemDirNotify : public KDirNotify +{ +K_DCOP + +public: + SystemDirNotify(); + +k_dcop: + virtual ASYNC FilesAdded (const KURL &directory); + virtual ASYNC FilesRemoved (const KURL::List &fileList); + virtual ASYNC FilesChanged (const KURL::List &fileList); + +private: + void init(); + KURL toSystemURL(const KURL &url); + KURL::List toSystemURLList(const KURL::List &list); + + QMap<KURL,KURL> m_urlMap; + bool mInited; +}; + +#endif diff --git a/kioslave/system/kdedmodule/systemdirnotifymodule.cpp b/kioslave/system/kdedmodule/systemdirnotifymodule.cpp new file mode 100644 index 000000000..36c46fcd0 --- /dev/null +++ b/kioslave/system/kdedmodule/systemdirnotifymodule.cpp @@ -0,0 +1,37 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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 "systemdirnotifymodule.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> + +SystemDirNotifyModule::SystemDirNotifyModule(const QCString &obj) + : KDEDModule(obj) +{ +} + +extern "C" { + KDE_EXPORT KDEDModule *create_systemdirnotify(const QCString &obj) + { + KGlobal::locale()->insertCatalogue("kio_system"); + return new SystemDirNotifyModule(obj); + } +} + diff --git a/kioslave/system/kdedmodule/systemdirnotifymodule.h b/kioslave/system/kdedmodule/systemdirnotifymodule.h new file mode 100644 index 000000000..c75cd1a9e --- /dev/null +++ b/kioslave/system/kdedmodule/systemdirnotifymodule.h @@ -0,0 +1,36 @@ +/* This file is part of the KDE Project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + 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. +*/ + +#ifndef _SYSTEMDIRNOTIFYMODULE_H_ +#define _SYSTEMDIRNOTIFYMODULE_H_ + +#include <kdedmodule.h> + +#include "systemdirnotify.h" + +class SystemDirNotifyModule : public KDEDModule +{ +K_DCOP + +public: + SystemDirNotifyModule(const QCString &obj); +private: + SystemDirNotify notifier; +}; + +#endif diff --git a/kioslave/system/kio_system.cpp b/kioslave/system/kio_system.cpp new file mode 100644 index 000000000..c2683add8 --- /dev/null +++ b/kioslave/system/kio_system.cpp @@ -0,0 +1,189 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <stdlib.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <dcopclient.h> + +#include <qeventloop.h> + +#include "kio_system.h" + + +static const KCmdLineOptions options[] = +{ + { "+protocol", I18N_NOOP( "Protocol name" ), 0 }, + { "+pool", I18N_NOOP( "Socket name" ), 0 }, + { "+app", I18N_NOOP( "Socket name" ), 0 }, + KCmdLineLastOption +}; + +extern "C" { + KDE_EXPORT int kdemain( int argc, char **argv ) + { + // KApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + KCmdLineArgs::init(argc, argv, "kio_system", 0, 0, 0, 0); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + SystemProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +SystemProtocol::SystemProtocol(const QCString &protocol, + const QCString &pool, const QCString &app) + : ForwardingSlaveBase(protocol, pool, app) +{ +} + +SystemProtocol::~SystemProtocol() +{ +} + +bool SystemProtocol::rewriteURL(const KURL &url, KURL &newUrl) +{ + QString name, path; + + if ( !m_impl.parseURL(url, name, path) ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return false; + } + + if ( !m_impl.realURL(name, path, newUrl) ) + { + error( m_impl.lastErrorCode(), m_impl.lastErrorMessage() ); + return false; + } + + return true; +} + + +void SystemProtocol::stat(const KURL &url) +{ + kdDebug() << "SystemProtocol::stat: " << url << endl; + + QString path = url.path(); + if ( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + KIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + QString name; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if( path.isEmpty() ) + { + KIO::UDSEntry entry; + + if ( m_impl.statByName(name, entry) ) + { + statEntry(entry); + finished(); + } + else + { + error(KIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + } + else + { + ForwardingSlaveBase::stat(url); + } +} + +void SystemProtocol::listDir(const KURL &url) +{ + kdDebug() << "SystemProtocol::listDir: " << url << endl; + + if ( url.path().length() <= 1 ) + { + listRoot(); + return; + } + + QString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(KIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + ForwardingSlaveBase::listDir(url); +} + +void SystemProtocol::listRoot() +{ + KIO::UDSEntry entry; + + KIO::UDSEntryList system_entries; + bool ok = m_impl.listRoot(system_entries); + + if (!ok) + { + error( m_impl.lastErrorCode(), m_impl.lastErrorMessage() ); + return; + } + + totalSize(system_entries.count()+1); + + m_impl.createTopLevelEntry(entry); + listEntry(entry, false); + + KIO::UDSEntryListIterator it = system_entries.begin(); + KIO::UDSEntryListIterator end = system_entries.end(); + + for(; it!=end; ++it) + { + listEntry(*it, false); + } + + entry.clear(); + listEntry(entry, true); + + finished(); +} + + +//#include "kio_system.moc" diff --git a/kioslave/system/kio_system.h b/kioslave/system/kio_system.h new file mode 100644 index 000000000..8c7c4664c --- /dev/null +++ b/kioslave/system/kio_system.h @@ -0,0 +1,45 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _KIO_MEDIA_H_ +#define _KIO_MEDIA_H_ + +#include <kio/forwardingslavebase.h> + +#include "systemimpl.h" + +class SystemProtocol : public KIO::ForwardingSlaveBase +{ +public: + SystemProtocol(const QCString &protocol, const QCString &pool, + const QCString &app); + virtual ~SystemProtocol(); + + virtual bool rewriteURL(const KURL &url, KURL &newUrl); + + virtual void stat(const KURL &url); + virtual void listDir(const KURL &url); + +private: + void listRoot(); + + SystemImpl m_impl; +}; + +#endif diff --git a/kioslave/system/mimetypes/Makefile.am b/kioslave/system/mimetypes/Makefile.am new file mode 100644 index 000000000..4f58439a7 --- /dev/null +++ b/kioslave/system/mimetypes/Makefile.am @@ -0,0 +1,6 @@ +mimetypedir = $(kde_mimedir)/inode + +mimetype_DATA = system_directory.desktop + + +EXTRA_DIST = $(mimetype_DATA) diff --git a/kioslave/system/mimetypes/system_directory.desktop b/kioslave/system/mimetypes/system_directory.desktop new file mode 100644 index 000000000..f7be931d7 --- /dev/null +++ b/kioslave/system/mimetypes/system_directory.desktop @@ -0,0 +1,76 @@ +[Desktop Entry] +Icon=kfm +Type=MimeType +MimeType=inode/system_directory +Comment=System Folder +Comment[af]=Stelsel Gids +Comment[ar]=مجلد النظام +Comment[be]=СіÑÑ‚ÑÐ¼Ð½Ð°Ñ Ñ‚Ñчка +Comment[bg]=СиÑтемна Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ +Comment[bn]=সিসà§à¦Ÿà§‡à¦® ফোলà§à¦¡à¦¾à¦° +Comment[br]=Renkell reiziad +Comment[bs]=Sistemski direktorij +Comment[ca]=Carpeta del sistema +Comment[cs]=Systémová složka +Comment[csb]=Systemòwi katalog +Comment[da]=Systemmappe +Comment[de]=Systemordner +Comment[el]=Φάκελος συστήματος +Comment[eo]=Sistema dosierujo +Comment[es]=Carpeta del sistema +Comment[et]=Süsteemi kataloog +Comment[eu]=Sistemaren karpeta +Comment[fa]=پوشۀ سیستم +Comment[fi]=Järjestelmän kansio +Comment[fr]=Dossier système +Comment[fy]=Systeemmap +Comment[ga]=Comhadlann Chórais +Comment[gl]=Cartafol do Sistema +Comment[he]=תיקיית מערכת +Comment[hr]=Sistemska mapa +Comment[hu]=Rendszermappa +Comment[is]=Kerfismappa +Comment[it]=Cartella di sistema +Comment[ja]=システムフォルダ +Comment[ka]=სისტემური სáƒáƒ¥áƒáƒ¦áƒáƒšáƒ“ე +Comment[kk]=Жүйелік қапшық +Comment[km]=ážážâ€‹áž”្រពáŸáž“្ធ +Comment[ko]=홈 í´ë” +Comment[lt]=Sistemos aplankas +Comment[mk]=СиÑтемÑка папка +Comment[ms]=Folder Sistem +Comment[nb]=Systemmappe +Comment[nds]=Systeemorner +Comment[ne]=पà¥à¤°à¤£à¤¾à¤²à¥€ फोलà¥à¤¡à¤° +Comment[nl]=Systeemmap +Comment[nn]=Systemmappe +Comment[pa]=ਸਿਸਟਮ ਫੋਲਡਰ +Comment[pl]=Katalog systemowy +Comment[pt]=Pasta de Sistema +Comment[pt_BR]=Pasta do Sistema +Comment[ro]=Folder de sistem +Comment[ru]=СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° +Comment[rw]=Ububiko Sisitemu +Comment[se]=Vuogádatmáhpa +Comment[sk]=Systémový prieÄinok +Comment[sl]=Sistemska mapa +Comment[sr]=СиÑтемÑка фаÑцикла +Comment[sr@Latn]=Sistemska fascikla +Comment[sv]=Systemkatalog +Comment[ta]=அமைபà¯à®ªà¯ அடைவ௠+Comment[te]=à°µà±à°¯à°µà°¸à±à°¥ ఫొలà±à°¡à°°à± +Comment[tg]=ФеҳраÑти ÑиÑтема +Comment[th]=โฟลเดà¸à¸£à¹Œà¸£à¸°à¸šà¸š +Comment[tr]=Sistem Klasörü +Comment[tt]=Sistem Törgäge +Comment[uk]=СиÑтемна тека +Comment[uz]=Tizim jildi +Comment[uz@cyrillic]=Тизим жилди +Comment[vi]=ThÆ° mục Hệ thống +Comment[wa]=Ridant sistinme +Comment[zh_CN]=系统文件夹 +Comment[zh_TW]=系統資料夾 +Patterns= + +X-KDE-AutoEmbed=true +X-KDE-IsAlso=inode/directory diff --git a/kioslave/system/system.protocol b/kioslave/system/system.protocol new file mode 100644 index 000000000..1f5723e3e --- /dev/null +++ b/kioslave/system/system.protocol @@ -0,0 +1,19 @@ +[Protocol] +exec=kio_system +protocol=system +input=none +output=filesystem +listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link +reading=true +writing=true +makedir=true +deleting=true +linking=true +moving=true +Icon=system +maxInstances=4 +#TODO DocPath=kioslave/file.html +Class=:local +deleteRecursive=true +fileNameUsedForCopying=Name + diff --git a/kioslave/system/systemimpl.cpp b/kioslave/system/systemimpl.cpp new file mode 100644 index 000000000..f773dc20e --- /dev/null +++ b/kioslave/system/systemimpl.cpp @@ -0,0 +1,276 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "systemimpl.h" + +#include <kdebug.h> +#include <kglobalsettings.h> +#include <kstandarddirs.h> +#include <kdesktopfile.h> + +#include <qapplication.h> +#include <qeventloop.h> +#include <qdir.h> + +#include <sys/stat.h> + +SystemImpl::SystemImpl() : QObject() +{ + KGlobal::dirs()->addResourceType("system_entries", + KStandardDirs::kde_default("data") + "systemview"); +} + +bool SystemImpl::listRoot(QValueList<KIO::UDSEntry> &list) +{ + kdDebug() << "SystemImpl::listRoot" << endl; + + QStringList names_found; + QStringList dirList = KGlobal::dirs()->resourceDirs("system_entries"); + + QStringList::ConstIterator dirpath = dirList.begin(); + QStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + QDir dir = *dirpath; + if (!dir.exists()) continue; + + QStringList filenames + = dir.entryList( QDir::Files | QDir::Readable ); + + + KIO::UDSEntry entry; + + QStringList::ConstIterator filename = filenames.begin(); + QStringList::ConstIterator endf = filenames.end(); + + for(; filename!=endf; ++filename) + { + if (!names_found.contains(*filename)) + { + entry.clear(); + createEntry(entry, *dirpath, *filename); + if ( !entry.isEmpty() ) + { + list.append(entry); + names_found.append(*filename); + } + } + } + } + + return true; +} + +bool SystemImpl::parseURL(const KURL &url, QString &name, QString &path) const +{ + QString url_path = url.path(); + + int i = url_path.find('/', 1); + if (i > 0) + { + name = url_path.mid(1, i-1); + path = url_path.mid(i+1); + } + else + { + name = url_path.mid(1); + path = QString::null; + } + + return name != QString::null; +} + +bool SystemImpl::realURL(const QString &name, const QString &path, + KURL &url) const +{ + url = findBaseURL(name); + if (!url.isValid()) + { + return false; + } + + url.addPath(path); + return true; +} + +bool SystemImpl::statByName(const QString &filename, KIO::UDSEntry& entry) +{ + kdDebug() << "SystemImpl::statByName" << endl; + + QStringList dirList = KGlobal::dirs()->resourceDirs("system_entries"); + + QStringList::ConstIterator dirpath = dirList.begin(); + QStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + QDir dir = *dirpath; + if (!dir.exists()) continue; + + QStringList filenames + = dir.entryList( QDir::Files | QDir::Readable ); + + + QStringList::ConstIterator name = filenames.begin(); + QStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (*name==filename+".desktop") + { + createEntry(entry, *dirpath, *name); + return true; + } + } + } + + return false; +} + +KURL SystemImpl::findBaseURL(const QString &filename) const +{ + kdDebug() << "SystemImpl::findBaseURL" << endl; + + QStringList dirList = KGlobal::dirs()->resourceDirs("system_entries"); + + QStringList::ConstIterator dirpath = dirList.begin(); + QStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + QDir dir = *dirpath; + if (!dir.exists()) continue; + + QStringList filenames + = dir.entryList( QDir::Files | QDir::Readable ); + + + KIO::UDSEntry entry; + + QStringList::ConstIterator name = filenames.begin(); + QStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (*name==filename+".desktop") + { + KDesktopFile desktop(*dirpath+filename+".desktop", true); + if ( desktop.readURL().isEmpty() ) + { + KURL url; + url.setPath( desktop.readPath() ); + return url; + } + + return desktop.readURL(); + } + } + } + + return KURL(); +} + + +static void addAtom(KIO::UDSEntry &entry, unsigned int ID, long l, + const QString &s = QString::null) +{ + KIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void SystemImpl::createTopLevelEntry(KIO::UDSEntry &entry) const +{ + entry.clear(); + addAtom(entry, KIO::UDS_NAME, 0, "."); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_ACCESS, 0555); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/system_directory"); + addAtom(entry, KIO::UDS_ICON_NAME, 0, "system"); +} + +void SystemImpl::createEntry(KIO::UDSEntry &entry, + const QString &directory, + const QString &file) +{ + kdDebug() << "SystemImpl::createEntry" << endl; + + KDesktopFile desktop(directory+file, true); + + kdDebug() << "path = " << directory << file << endl; + + entry.clear(); + + // Ensure that we really want this entry to be displayed + if ( desktop.readURL().isEmpty() && desktop.readPath().isEmpty() ) + { + return; + } + + addAtom(entry, KIO::UDS_NAME, 0, desktop.readName()); + + QString new_filename = file; + new_filename.truncate(file.length()-8); + addAtom(entry, KIO::UDS_URL, 0, "system:/"+new_filename); + + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + + QString icon = desktop.readIcon(); + QString empty_icon = desktop.readEntry("EmptyIcon"); + + if (!empty_icon.isEmpty()) + { + KURL url = desktop.readURL(); + + m_lastListingEmpty = true; + + KIO::ListJob *job = KIO::listDir(url, false, false); + connect( job, SIGNAL( entries(KIO::Job *, + const KIO::UDSEntryList &) ), + this, SLOT( slotEntries(KIO::Job *, + const KIO::UDSEntryList &) ) ); + connect( job, SIGNAL( result(KIO::Job *) ), + this, SLOT( slotResult(KIO::Job *) ) ); + qApp->eventLoop()->enterLoop(); + + if (m_lastListingEmpty) icon = empty_icon; + } + + addAtom(entry, KIO::UDS_ICON_NAME, 0, icon); +} + +void SystemImpl::slotEntries(KIO::Job *job, const KIO::UDSEntryList &list) +{ + if (list.size()>0) + { + job->kill(true); + m_lastListingEmpty = false; + qApp->eventLoop()->exitLoop(); + } +} + +void SystemImpl::slotResult(KIO::Job *) +{ + qApp->eventLoop()->exitLoop(); +} + + +#include "systemimpl.moc" diff --git a/kioslave/system/systemimpl.h b/kioslave/system/systemimpl.h new file mode 100644 index 000000000..a06874465 --- /dev/null +++ b/kioslave/system/systemimpl.h @@ -0,0 +1,65 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kevin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef SYSTEMIMPL_H +#define SYSTEMIMPL_H + +#include <kio/global.h> +#include <kio/job.h> +#include <kurl.h> +#include <dcopobject.h> + +#include <qobject.h> +#include <qstring.h> + +class SystemImpl : public QObject +{ +Q_OBJECT +public: + SystemImpl(); + + void createTopLevelEntry(KIO::UDSEntry& entry) const; + bool statByName(const QString &filename, KIO::UDSEntry& entry); + + bool listRoot(QValueList<KIO::UDSEntry> &list); + + bool parseURL(const KURL &url, QString &name, QString &path) const; + bool realURL(const QString &name, const QString &path, KURL &url) const; + + int lastErrorCode() const { return m_lastErrorCode; } + QString lastErrorMessage() const { return m_lastErrorMessage; } + +private slots: + KURL findBaseURL(const QString &filename) const; + void slotEntries(KIO::Job *job, const KIO::UDSEntryList &list); + void slotResult(KIO::Job *job); + +private: + void createEntry(KIO::UDSEntry& entry, const QString &directory, + const QString &file); + + bool m_lastListingEmpty; + + /// Last error code stored in class to simplify API. + /// Note that this means almost no method can be const. + int m_lastErrorCode; + QString m_lastErrorMessage; +}; + +#endif diff --git a/kioslave/system/testsystem.cpp b/kioslave/system/testsystem.cpp new file mode 100644 index 000000000..95107680f --- /dev/null +++ b/kioslave/system/testsystem.cpp @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kio_system.h" +#include "testsystem.h" + +#include <config.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#include <stdlib.h> + +static bool check(const QString& txt, QString a, QString b) +{ + if (a.isEmpty()) + a = QString::null; + if (b.isEmpty()) + b = QString::null; + if (a == b) { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +int main(int argc, char *argv[]) +{ + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc,argv,"testsystem", 0, 0, 0, 0); + KApplication app; + + TestSystem test; + test.setup(); + test.runAll(); + kdDebug() << "All tests OK." << endl; + return 0; // success. The exit(1) in check() is what happens in case of failure. +} + +void TestSystem::setup() +{ + +} + +void TestSystem::runAll() +{ + +} + diff --git a/kioslave/system/testsystem.h b/kioslave/system/testsystem.h new file mode 100644 index 000000000..2418e66cb --- /dev/null +++ b/kioslave/system/testsystem.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE project + Copyright (c) 2004 Kévin Ottens <ervin ipsquad net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef TESTSYSTEM_H +#define TESTSYSTEM_H + +class TestSystem +{ +public: + TestSystem() {} + void setup(); + void runAll(); + + // tests + +}; + +#endif diff --git a/kioslave/tar/Makefile.am b/kioslave/tar/Makefile.am new file mode 100644 index 000000000..fb4f4713a --- /dev/null +++ b/kioslave/tar/Makefile.am @@ -0,0 +1,24 @@ +## Makefile.am of kdebase/kioslave/tar + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) +METASOURCES = AUTO + +kde_module_LTLIBRARIES = kio_tar.la + +kio_tar_la_SOURCES = tar.cc +kio_tar_la_LIBADD = $(LIB_KSYCOCA) +kio_tar_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +check_PROGRAMS = ktartest + +ktartest_SOURCES = ktartest.cpp +ktartest_LDADD = $(LIB_KSYCOCA) + +noinst_HEADERS = tar.h + +kdelnk_DATA = tar.protocol ar.protocol zip.protocol +kdelnkdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cc -o $(podir)/kio_tar.pot diff --git a/kioslave/tar/ar.protocol b/kioslave/tar/ar.protocol new file mode 100644 index 000000000..7bdc3e746 --- /dev/null +++ b/kioslave/tar/ar.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=kio_tar +protocol=ar +mimetype=application/x-archive +input=filesystem +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link +reading=true +source=true +DocPath=kioslave/ar.html +Icon=ar +Class=:local diff --git a/kioslave/tar/ktartest.cpp b/kioslave/tar/ktartest.cpp new file mode 100644 index 000000000..a82e6dbcd --- /dev/null +++ b/kioslave/tar/ktartest.cpp @@ -0,0 +1,201 @@ +#include "ktar.h" +#include <stdio.h> +#include <qfile.h> +#include <kinstance.h> +#include <kdebug.h> + +void recursive_print( const KTarDirectory * dir, const QString & path ) +{ + QStringList l = dir->entries(); + QStringList::Iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + const KTarEntry* entry = dir->entry( (*it) ); + printf("mode=%07o %s %s %s%s\n", entry->permissions(), entry->user().latin1(), entry->group().latin1(), path.latin1(), (*it).latin1()); + if (entry->isDirectory()) + recursive_print( (KTarDirectory *)entry, path+(*it)+"/" ); + } +} + +static void usage() +{ + printf("\n" + " Usage :\n" + " ./ktartest list /path/to/existing_file.tar.gz tests listing an existing tar.gz\n" + " ./ktartest get /path/to/existing_file.tar.gz filename tests extracting a file.\n" + " ./ktartest readwrite newfile.tar.gz will create the tar.gz, then close and reopen it.\n" + " ./ktartest maxlength newfile.tar.gz tests the maximum filename length allowed.\n" + " ./ktartest bytearray /path/to/existing_file.tar.gz tests KTarData\n"); +} + +int main( int argc, char** argv ) +{ + if (argc < 3) + { + usage(); + return 1; + } + KInstance instance("ktartest"); + + QString command = argv[1]; + kdDebug() << "main: command=" << command << endl; + if ( command == "list" ) + { + KTarGz tar( argv[2] ); + + if ( !tar.open( IO_ReadOnly ) ) + { + printf("Could not open %s for reading\n", argv[1] ); + return 1; + } + + const KTarDirectory* dir = tar.directory(); + + //printf("calling recursive_print\n"); + recursive_print( dir, "" ); + //printf("recursive_print called\n"); + + tar.close(); + + return 0; + } + else if ( command == "get" ) + { + if ( argc != 4 ) + { + usage(); + return 1; + } + + KTarGz tar( argv[2] ); + + if ( !tar.open( IO_ReadOnly ) ) + { + printf("Could not open %s for reading\n", argv[1] ); + return 1; + } + + const KTarDirectory* dir = tar.directory(); + + const KTarEntry* e = dir->entry( argv[3] ); + Q_ASSERT( e && e->isFile() ); + const KTarFile* f = (KTarFile*)e; + + QByteArray arr( f->data() ); + printf("SIZE=%i\n",arr.size() ); + QString str( arr ); + printf("DATA=%s\n", str.latin1()); + + /* + // This is what KGzipDev::readAll could do, if QIODevice::readAll was virtual.... + QByteArray array(1024); + int n; + while ( ( n = dev.readBlock( array.data(), array.size() ) ) ) + { + kdDebug() << "readBlock returned " << n << endl << endl; + QCString s(array,n+1); // Terminate with 0 before printing + printf("%s", s.data()); + } + dev.close(); + */ + + + tar.close(); + } + else if (command == "readwrite" ) + { + kdDebug() << " --- readwrite --- " << endl; + KTarGz tar( argv[2] ); + + if ( !tar.open( IO_WriteOnly ) ) + { + printf("Could not open %s for writing\n", argv[1]); + return 1; + } + + tar.writeFile( "empty", "weis", "users", 0, "" ); + tar.writeFile( "test1", "weis", "users", 5, "Hallo" ); + tar.writeFile( "test2", "weis", "users", 8, "Hallo Du" ); + tar.writeFile( "mydir/test3", "weis", "users", 13, "Noch so einer" ); + tar.writeFile( "my/dir/test3", "dfaure", "hackers", 29, "I don't speak German (David)" ); + +#define SIZE1 100 + // Now a medium file : 100 null bytes + char medium[ SIZE1 ]; + memset( medium, 0, SIZE1 ); + tar.writeFile( "mediumfile", "user", "group", SIZE1, medium ); + // Another one, with an absolute path + tar.writeFile( "/dir/subdir/mediumfile2", "user", "group", SIZE1, medium ); + + // Now a huge file : 20000 null bytes + int n = 20000; + char * huge = new char[ n ]; + memset( huge, 0, n ); + tar.writeFile( "hugefile", "user", "group", n, huge ); + delete [] huge; + + tar.close(); + + printf("-----------------------\n"); + + if ( !tar.open( IO_ReadOnly ) ) + { + printf("Could not open %s for reading\n", argv[1] ); + return 1; + } + + const KTarDirectory* dir = tar.directory(); + recursive_print(dir, ""); + + const KTarEntry* e = dir->entry( "mydir/test3" ); + Q_ASSERT( e && e->isFile() ); + const KTarFile* f = (KTarFile*)e; + + QByteArray arr( f->data() ); + printf("SIZE=%i\n",arr.size() ); + QString str( arr ); + printf("DATA=%s\n", str.latin1()); + + tar.close(); + + return 0; + } + else if ( command == "maxlength" ) + { + KTarGz tar( argv[2] ); + + if ( !tar.open( IO_WriteOnly ) ) + { + printf("Could not open %s for writing\n", argv[1]); + return 1; + } + // Generate long filenames of each possible length bigger than 98... + for (int i = 98; i < 500 ; i++ ) + { + QString str, num; + str.fill( 'a', i-10 ); + num.setNum( i ); + num = num.rightJustify( 10, '0' ); + tar.writeFile( str+num, "testu", "testg", 3, "hum" ); + } + // Result of this test : it fails at 482 (instead of 154 previously). + // Ok, I think we can do with that :) + tar.close(); + printf("Now run 'tar tvzf %s'\n", argv[2]); + return 0; + } + else if ( command == "bytearray" ) + { + QFile file( argv[2] ); + if ( !file.open( IO_ReadOnly ) ) + return 1; + KTarGz tar( &file ); + tar.open( IO_ReadOnly ); + const KTarDirectory* dir = tar.directory(); + recursive_print( dir, "" ); + return 0; + } + else + printf("Unknown command\n"); +} + diff --git a/kioslave/tar/tar.cc b/kioslave/tar/tar.cc new file mode 100644 index 000000000..bfe909367 --- /dev/null +++ b/kioslave/tar/tar.cc @@ -0,0 +1,623 @@ +#include <config.h> + +#include <sys/types.h> +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#include <stdlib.h> +#include <unistd.h> + +#include <qfile.h> + +#include <kglobal.h> +#include <kurl.h> +#include <kdebug.h> +#include <kinstance.h> +#include <ktar.h> +#include <kzip.h> +#include <kar.h> +#include <kmimemagic.h> +#include <klocale.h> +#include <kde_file.h> +#include <kio/global.h> +#include <kremoteencoding.h> + +#include <errno.h> // to be removed + +#include "tar.h" + +using namespace KIO; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + KInstance instance( "kio_tar" ); + + kdDebug(7109) << "Starting " << getpid() << endl; + + if (argc != 4) + { + fprintf(stderr, "Usage: kio_tar protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + + ArchiveProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + kdDebug(7109) << "Done" << endl; + return 0; +} + +ArchiveProtocol::ArchiveProtocol( const QCString &pool, const QCString &app ) : SlaveBase( "tar", pool, app ) +{ + kdDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol" << endl; + m_archiveFile = 0L; +} + +ArchiveProtocol::~ArchiveProtocol() +{ + delete m_archiveFile; +} + +bool ArchiveProtocol::checkNewFile( const KURL & url, QString & path, KIO::Error& errorNum ) +{ + QString fullPath = url.path(); + kdDebug(7109) << "ArchiveProtocol::checkNewFile " << fullPath << endl; + + + // Are we already looking at that file ? + if ( m_archiveFile && m_archiveName == fullPath.left(m_archiveName.length()) ) + { + // Has it changed ? + KDE_struct_stat statbuf; + if ( KDE_stat( QFile::encodeName( m_archiveName ), &statbuf ) == 0 ) + { + if ( m_mtime == statbuf.st_mtime ) + { + path = fullPath.mid( m_archiveName.length() ); + kdDebug(7109) << "ArchiveProtocol::checkNewFile returning " << path << endl; + return true; + } + } + } + kdDebug(7109) << "Need to open a new file" << endl; + + // Close previous file + if ( m_archiveFile ) + { + m_archiveFile->close(); + delete m_archiveFile; + m_archiveFile = 0L; + } + + // Find where the tar file is in the full path + int pos = 0; + QString archiveFile; + path = QString::null; + + int len = fullPath.length(); + if ( len != 0 && fullPath[ len - 1 ] != '/' ) + fullPath += '/'; + + kdDebug(7109) << "the full path is " << fullPath << endl; + KDE_struct_stat statbuf; + statbuf.st_mode = 0; // be sure to clear the directory bit + while ( (pos=fullPath.find( '/', pos+1 )) != -1 ) + { + QString tryPath = fullPath.left( pos ); + kdDebug(7109) << fullPath << " trying " << tryPath << endl; + if ( KDE_stat( QFile::encodeName(tryPath), &statbuf ) == -1 ) + { + // We are not in the file system anymore, either we have already enough data or we will never get any useful data anymore + break; + } + if ( !S_ISDIR(statbuf.st_mode) ) + { + archiveFile = tryPath; + m_mtime = statbuf.st_mtime; + path = fullPath.mid( pos + 1 ); + kdDebug(7109) << "fullPath=" << fullPath << " path=" << path << endl; + len = path.length(); + if ( len > 1 ) + { + if ( path[ len - 1 ] == '/' ) + path.truncate( len - 1 ); + } + else + path = QString::fromLatin1("/"); + kdDebug(7109) << "Found. archiveFile=" << archiveFile << " path=" << path << endl; + break; + } + } + if ( archiveFile.isEmpty() ) + { + kdDebug(7109) << "ArchiveProtocol::checkNewFile: not found" << endl; + if ( S_ISDIR(statbuf.st_mode) ) // Was the last stat about a directory? + { + // Too bad, it is a directory, not an archive. + kdDebug(7109) << "Path is a directory, not an archive." << endl; + errorNum = KIO::ERR_IS_DIRECTORY; + } + else + errorNum = KIO::ERR_DOES_NOT_EXIST; + return false; + } + + // Open new file + if ( url.protocol() == "tar" ) { + kdDebug(7109) << "Opening KTar on " << archiveFile << endl; + m_archiveFile = new KTar( archiveFile ); + } else if ( url.protocol() == "ar" ) { + kdDebug(7109) << "Opening KAr on " << archiveFile << endl; + m_archiveFile = new KAr( archiveFile ); + } else if ( url.protocol() == "zip" ) { + kdDebug(7109) << "Opening KZip on " << archiveFile << endl; + m_archiveFile = new KZip( archiveFile ); + } else { + kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave" << endl; + errorNum = KIO::ERR_UNSUPPORTED_PROTOCOL; + return false; + } + + if ( !m_archiveFile->open( IO_ReadOnly ) ) + { + kdDebug(7109) << "Opening " << archiveFile << "failed." << endl; + delete m_archiveFile; + m_archiveFile = 0L; + errorNum = KIO::ERR_CANNOT_OPEN_FOR_READING; + return false; + } + + m_archiveName = archiveFile; + return true; +} + + +void ArchiveProtocol::createUDSEntry( const KArchiveEntry * archiveEntry, UDSEntry & entry ) +{ + UDSAtom atom; + entry.clear(); + atom.m_uds = UDS_NAME; + atom.m_str = remoteEncoding()->decode(archiveEntry->name().local8Bit()); + entry.append(atom); + + atom.m_uds = UDS_FILE_TYPE; + atom.m_long = archiveEntry->permissions() & S_IFMT; // keep file type only + entry.append( atom ); + + atom.m_uds = UDS_SIZE; + atom.m_long = archiveEntry->isFile() ? ((KArchiveFile *)archiveEntry)->size() : 0L ; + entry.append( atom ); + + atom.m_uds = UDS_MODIFICATION_TIME; + atom.m_long = archiveEntry->date(); + entry.append( atom ); + + atom.m_uds = UDS_ACCESS; + atom.m_long = archiveEntry->permissions() & 07777; // keep permissions only + entry.append( atom ); + + atom.m_uds = UDS_USER; + atom.m_str = remoteEncoding()->decode(archiveEntry->user().local8Bit()); + entry.append( atom ); + + atom.m_uds = UDS_GROUP; + atom.m_str = remoteEncoding()->decode(archiveEntry->group().local8Bit()); + entry.append( atom ); + + atom.m_uds = UDS_LINK_DEST; + atom.m_str = remoteEncoding()->decode(archiveEntry->symlink().local8Bit()); + entry.append( atom ); +} + +void ArchiveProtocol::listDir( const KURL & url ) +{ + kdDebug( 7109 ) << "ArchiveProtocol::listDir " << url << endl; + + QString path; + KIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING ) + { + // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) + // Therefore give a more specific error message + error( KIO::ERR_SLAVE_DEFINED, + i18n( "Could not open the file, probably due to an unsupported file format.\n%1") + .arg( url.prettyURL() ) ); + return; + } + else if ( errorNum != ERR_IS_DIRECTORY ) + { + // We have any other error + error( errorNum, url.prettyURL() ); + return; + } + // It's a real dir -> redirect + KURL redir; + redir.setPath( url.path() ); + kdDebug( 7109 ) << "Ok, redirection to " << redir.url() << endl; + redirection( redir ); + finished(); + // And let go of the tar file - for people who want to unmount a cdrom after that + delete m_archiveFile; + m_archiveFile = 0L; + return; + } + + if ( path.isEmpty() ) + { + KURL redir( url.protocol() + QString::fromLatin1( ":/") ); + kdDebug( 7109 ) << "url.path()==" << url.path() << endl; + redir.setPath( url.path() + QString::fromLatin1("/") ); + kdDebug( 7109 ) << "ArchiveProtocol::listDir: redirection " << redir.url() << endl; + redirection( redir ); + finished(); + return; + } + + path = QString::fromLocal8Bit(remoteEncoding()->encode(path)); + + kdDebug( 7109 ) << "checkNewFile done" << endl; + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveDirectory* dir; + if (!path.isEmpty() && path != "/") + { + kdDebug(7109) << QString("Looking for entry %1").arg(path) << endl; + const KArchiveEntry* e = root->entry( path ); + if ( !e ) + { + error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + if ( ! e->isDirectory() ) + { + error( KIO::ERR_IS_FILE, url.prettyURL() ); + return; + } + dir = (KArchiveDirectory*)e; + } else { + dir = root; + } + + QStringList l = dir->entries(); + totalSize( l.count() ); + + UDSEntry entry; + QStringList::Iterator it = l.begin(); + for( ; it != l.end(); ++it ) + { + kdDebug(7109) << (*it) << endl; + const KArchiveEntry* archiveEntry = dir->entry( (*it) ); + + createUDSEntry( archiveEntry, entry ); + + listEntry( entry, false ); + } + + listEntry( entry, true ); // ready + + finished(); + + kdDebug( 7109 ) << "ArchiveProtocol::listDir done" << endl; +} + +void ArchiveProtocol::stat( const KURL & url ) +{ + QString path; + UDSEntry entry; + KIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + // We may be looking at a real directory - this happens + // when pressing up after being in the root of an archive + if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING ) + { + // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) + // Therefore give a more specific error message + error( KIO::ERR_SLAVE_DEFINED, + i18n( "Could not open the file, probably due to an unsupported file format.\n%1") + .arg( url.prettyURL() ) ); + return; + } + else if ( errorNum != ERR_IS_DIRECTORY ) + { + // We have any other error + error( errorNum, url.prettyURL() ); + return; + } + // Real directory. Return just enough information for KRun to work + UDSAtom atom; + atom.m_uds = KIO::UDS_NAME; + atom.m_str = url.fileName(); + entry.append( atom ); + kdDebug( 7109 ) << "ArchiveProtocol::stat returning name=" << url.fileName() << endl; + + KDE_struct_stat buff; + if ( KDE_stat( QFile::encodeName( url.path() ), &buff ) == -1 ) + { + // Should not happen, as the file was already stated by checkNewFile + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL() ); + return; + } + + atom.m_uds = KIO::UDS_FILE_TYPE; + atom.m_long = buff.st_mode & S_IFMT; + entry.append( atom ); + + statEntry( entry ); + + finished(); + + // And let go of the tar file - for people who want to unmount a cdrom after that + delete m_archiveFile; + m_archiveFile = 0L; + return; + } + + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveEntry* archiveEntry; + if ( path.isEmpty() ) + { + path = QString::fromLatin1( "/" ); + archiveEntry = root; + } else { + path = QString::fromLocal8Bit(remoteEncoding()->encode(path)); + archiveEntry = root->entry( path ); + } + if ( !archiveEntry ) + { + error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + + createUDSEntry( archiveEntry, entry ); + statEntry( entry ); + + finished(); +} + +void ArchiveProtocol::get( const KURL & url ) +{ + kdDebug( 7109 ) << "ArchiveProtocol::get " << url << endl; + + QString path; + KIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + if ( errorNum == KIO::ERR_CANNOT_OPEN_FOR_READING ) + { + // If we cannot open, it might be a problem with the archive header (e.g. unsupported format) + // Therefore give a more specific error message + error( KIO::ERR_SLAVE_DEFINED, + i18n( "Could not open the file, probably due to an unsupported file format.\n%1") + .arg( url.prettyURL() ) ); + return; + } + else + { + // We have any other error + error( errorNum, url.prettyURL() ); + return; + } + } + + path = QString::fromLocal8Bit(remoteEncoding()->encode(path)); + + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveEntry* archiveEntry = root->entry( path ); + + if ( !archiveEntry ) + { + error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + if ( archiveEntry->isDirectory() ) + { + error( KIO::ERR_IS_DIRECTORY, url.prettyURL() ); + return; + } + const KArchiveFile* archiveFileEntry = static_cast<const KArchiveFile *>(archiveEntry); + if ( !archiveEntry->symlink().isEmpty() ) + { + kdDebug(7109) << "Redirection to " << archiveEntry->symlink() << endl; + KURL realURL; + if (archiveEntry->symlink().startsWith("/")) { // absolute path + realURL.setPath(archiveEntry->symlink() ); // goes out of tar:/, back into file: + } else { + realURL = KURL( url, archiveEntry->symlink() ); + } + kdDebug(7109) << "realURL= " << realURL << endl; + redirection( realURL ); + finished(); + return; + } + + //kdDebug(7109) << "Preparing to get the archive data" << endl; + + /* + * The easy way would be to get the data by calling archiveFileEntry->data() + * However this has drawbacks: + * - the complete file must be read into the memory + * - errors are skipped, resulting in an empty file + */ + + QIODevice* io = 0; + // Getting the device is hard, as archiveFileEntry->device() is not virtual! + if ( url.protocol() == "tar" ) + { + io = archiveFileEntry->device(); + } + else if ( url.protocol() == "ar" ) + { + io = archiveFileEntry->device(); + } + else if ( url.protocol() == "zip" ) + { + io = ((KZipFileEntry*) archiveFileEntry)->device(); + } + else + { + // Wrong protocol? Why was this not catched by checkNewFile? + kdWarning(7109) << "Protocol " << url.protocol() << " not supported by this IOSlave; " << k_funcinfo << endl; + error( KIO::ERR_UNSUPPORTED_PROTOCOL, url.protocol() ); + return; + } + + if (!io) + { + error( KIO::ERR_SLAVE_DEFINED, + i18n( "The archive file could not be opened, perhaps because the format is unsupported.\n%1" ) + .arg( url.prettyURL() ) ); + return; + } + + if ( !io->open( IO_ReadOnly ) ) + { + error( KIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL() ); + return; + } + + totalSize( archiveFileEntry->size() ); + + // Size of a QIODevice read. It must be large enough so that the mime type check will not fail + const int maxSize = 0x100000; // 1MB + + int bufferSize = kMin( maxSize, archiveFileEntry->size() ); + QByteArray buffer ( bufferSize ); + if ( buffer.isEmpty() && bufferSize > 0 ) + { + // Something went wrong + error( KIO::ERR_OUT_OF_MEMORY, url.prettyURL() ); + return; + } + + bool firstRead = true; + + // How much file do we still have to process? + int fileSize = archiveFileEntry->size(); + KIO::filesize_t processed = 0; + + while ( !io->atEnd() && fileSize > 0 ) + { + if ( !firstRead ) + { + bufferSize = kMin( maxSize, fileSize ); + buffer.resize( bufferSize, QGArray::SpeedOptim ); + } + const Q_LONG read = io->readBlock( buffer.data(), buffer.size() ); // Avoid to use bufferSize here, in case something went wrong. + if ( read != bufferSize ) + { + kdWarning(7109) << "Read " << read << " bytes but expected " << bufferSize << endl; + error( KIO::ERR_COULD_NOT_READ, url.prettyURL() ); + return; + } + if ( firstRead ) + { + // We use the magic one the first data read + // (As magic detection is about fixed positions, we can be sure that it is enough data.) + KMimeMagicResult * result = KMimeMagic::self()->findBufferFileType( buffer, path ); + kdDebug(7109) << "Emitting mimetype " << result->mimeType() << endl; + mimeType( result->mimeType() ); + firstRead = false; + } + data( buffer ); + processed += read; + processedSize( processed ); + fileSize -= bufferSize; + } + io->close(); + delete io; + + data( QByteArray() ); + + finished(); +} + +/* + In case someone wonders how the old filter stuff looked like : :) +void TARProtocol::slotData(void *_p, int _len) +{ + switch (m_cmd) { + case CMD_PUT: + assert(m_pFilter); + m_pFilter->send(_p, _len); + break; + default: + abort(); + break; + } +} + +void TARProtocol::slotDataEnd() +{ + switch (m_cmd) { + case CMD_PUT: + assert(m_pFilter && m_pJob); + m_pFilter->finish(); + m_pJob->dataEnd(); + m_cmd = CMD_NONE; + break; + default: + abort(); + break; + } +} + +void TARProtocol::jobData(void *_p, int _len) +{ + switch (m_cmd) { + case CMD_GET: + assert(m_pFilter); + m_pFilter->send(_p, _len); + break; + case CMD_COPY: + assert(m_pFilter); + m_pFilter->send(_p, _len); + break; + default: + abort(); + } +} + +void TARProtocol::jobDataEnd() +{ + switch (m_cmd) { + case CMD_GET: + assert(m_pFilter); + m_pFilter->finish(); + dataEnd(); + break; + case CMD_COPY: + assert(m_pFilter); + m_pFilter->finish(); + m_pJob->dataEnd(); + break; + default: + abort(); + } +} + +void TARProtocol::filterData(void *_p, int _len) +{ +debug("void TARProtocol::filterData"); + switch (m_cmd) { + case CMD_GET: + data(_p, _len); + break; + case CMD_PUT: + assert (m_pJob); + m_pJob->data(_p, _len); + break; + case CMD_COPY: + assert(m_pJob); + m_pJob->data(_p, _len); + break; + default: + abort(); + } +} +*/ + +// kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/kioslave/tar/tar.h b/kioslave/tar/tar.h new file mode 100644 index 000000000..644a8b100 --- /dev/null +++ b/kioslave/tar/tar.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _TAR_H +#define _TAR_H + +#include <sys/types.h> + +#include <kio/global.h> +#include <kio/slavebase.h> + +class ArchiveProtocol : public KIO::SlaveBase +{ +public: + ArchiveProtocol( const QCString &pool, const QCString &app ); + virtual ~ArchiveProtocol(); + + virtual void listDir( const KURL & url ); + virtual void stat( const KURL & url ); + virtual void get( const KURL & url ); + +protected: + void createUDSEntry( const KArchiveEntry * tarEntry, KIO::UDSEntry & entry ); + + /** + * \brief find, check and open the archive file + * \param url The URL of the archive + * \param path Path where the archive really is (returned value) + * \param errNum KIO error number (undefined if the function returns true) + * \return true if file was found, false if there was an error + */ + bool checkNewFile( const KURL & url, QString & path, KIO::Error& errorNum ); + + KArchive * m_archiveFile; + QString m_archiveName; + time_t m_mtime; +}; + +#endif diff --git a/kioslave/tar/tar.protocol b/kioslave/tar/tar.protocol new file mode 100644 index 000000000..096ed249a --- /dev/null +++ b/kioslave/tar/tar.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=kio_tar +protocol=tar +mimetype=application/x-tar +input=filesystem +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link +reading=true +source=true +DocPath=kioslave/tar.html +Icon=tar +Class=:local diff --git a/kioslave/tar/zip.protocol b/kioslave/tar/zip.protocol new file mode 100644 index 000000000..a7dffe7ad --- /dev/null +++ b/kioslave/tar/zip.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=kio_tar +protocol=zip +mimetype=application/x-zip +input=filesystem +output=filesystem +listing=Name,Type,Size,Date,Access,Owner,Group,Link +reading=true +source=true +DocPath=kioslave/zip.html +Icon=zip +Class=:local diff --git a/kioslave/thumbnail/Makefile.am b/kioslave/thumbnail/Makefile.am new file mode 100644 index 000000000..2b76cc4e0 --- /dev/null +++ b/kioslave/thumbnail/Makefile.am @@ -0,0 +1,69 @@ +## Makefile.am of kdebase/kioslave/thumbnail + +INCLUDES = $(all_includes) $(EXR_FLAGS) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) +METASOURCES = AUTO + +if have_xcursor +CURSOR_MODULE = cursorthumbnail.la +CURSOR_DATAFILE = cursorthumbnail.desktop +endif + +if include_EXR_MODULES +EXR_MODULE = exrthumbnail.la +EXR_DATAFILE = exrthumbnail.desktop +endif + +kde_module_LTLIBRARIES = kio_thumbnail.la imagethumbnail.la \ + textthumbnail.la htmlthumbnail.la \ + djvuthumbnail.la $(CURSOR_MODULE) \ + $(EXR_MODULE) + +kio_thumbnail_la_SOURCES = thumbnail.cpp +kio_thumbnail_la_LIBADD = $(LIB_KIO) +kio_thumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +imagethumbnail_la_SOURCES = imagecreator.cpp +imagethumbnail_la_LIBADD = $(LIB_KIO) # for kimageio +imagethumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +textthumbnail_la_SOURCES = textcreator.cpp +textthumbnail_la_LIBADD = $(LIB_KIO) # for kmimetype +textthumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +htmlthumbnail_la_SOURCES = htmlcreator.cpp +htmlthumbnail_la_LIBADD = $(LIB_KHTML) +htmlthumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +djvuthumbnail_la_SOURCES = djvucreator.cpp +djvuthumbnail_la_LIBADD = $(LIB_KDECORE) +djvuthumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +cursorthumbnail_la_SOURCES = cursorcreator.cpp +cursorthumbnail_la_LIBADD = $(LIB_KDECORE) $(LIB_XCURSOR) +cursorthumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +exrthumbnail_la_SOURCES = exrcreator.cpp +exrthumbnail_la_LIBADD = $(LIB_KDECORE) $(LIB_EXR) +exrthumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +noinst_HEADERS = thumbnail.h imagecreator.h textcreator.h htmlcreator.h \ + djvucreator.h cursorcreator.h \ + exrcreator.h + +kdelnk_DATA = thumbnail.protocol +kdelnkdir = $(kde_servicesdir) + +servicetypes_DATA = thumbcreator.desktop +servicetypesdir = $(kde_servicetypesdir) + +services_DATA = imagethumbnail.desktop textthumbnail.desktop \ + htmlthumbnail.desktop \ + djvuthumbnail.desktop \ + $(CURSOR_DATAFILE) $(EXR_DATAFILE) + +servicesdir = $(kde_servicesdir) + +messages: + $(XGETTEXT) *.cpp -o $(podir)/kio_thumbnail.pot + diff --git a/kioslave/thumbnail/configure.in.in b/kioslave/thumbnail/configure.in.in new file mode 100644 index 000000000..829727b69 --- /dev/null +++ b/kioslave/thumbnail/configure.in.in @@ -0,0 +1,14 @@ +AC_ARG_WITH(openexr, + [AC_HELP_STRING(--with-openexr, + [enable support for OpenEXR @<:@default=check@:>@])], + [], with_openexr=check) + +if test "x$with_openexr" != xno; then + KDE_FIND_LIBEXR + + if test "x$with_openexr" != xcheck && test -z "$LIB_EXR"; then + AC_MSG_ERROR([--with-openexr was given, but test for OpenEXR failed]) + fi +fi + +AM_CONDITIONAL(include_EXR_MODULES, test -n "$LIB_EXR") diff --git a/kioslave/thumbnail/cursorcreator.cpp b/kioslave/thumbnail/cursorcreator.cpp new file mode 100644 index 000000000..0b1470bd6 --- /dev/null +++ b/kioslave/thumbnail/cursorcreator.cpp @@ -0,0 +1,69 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Fredrik Höglund <fredrik@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <qimage.h> +#include <qfile.h> + +#include <kdemacros.h> + +#include "cursorcreator.h" + +#include <X11/Xlib.h> +#include <X11/Xcursor/Xcursor.h> + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new CursorCreator; + } +} + +bool CursorCreator::create( const QString &path, int width, int height, QImage &img ) +{ + XcursorImage *cursor = XcursorFilenameLoadImage( + QFile::encodeName( path ).data(), + width > height ? height : width ); + + if ( cursor ) { + img = QImage( reinterpret_cast<uchar *>( cursor->pixels ), + cursor->width, cursor->height, 32, + NULL, 0, QImage::BigEndian ); + img.setAlphaBuffer( true ); + + // Convert the image to non-premultiplied alpha + Q_UINT32 *pixels = reinterpret_cast<Q_UINT32 *>( img.bits() ); + for ( int i = 0; i < (img.width() * img.height()); i++ ) { + float alpha = qAlpha( pixels[i] ) / 255.0; + if ( alpha > 0.0 && alpha < 1.0 ) + pixels[i] = qRgba( int( qRed(pixels[i]) / alpha ), + int( qGreen(pixels[i]) / alpha ), + int( qBlue(pixels[i]) / alpha ), + qAlpha(pixels[i]) ); + } + + // Create a deep copy of the image so the image data is preserved + img = img.copy(); + XcursorImageDestroy( cursor ); + return true; + } + + return false; +} + diff --git a/kioslave/thumbnail/cursorcreator.h b/kioslave/thumbnail/cursorcreator.h new file mode 100644 index 000000000..7ef8ac2fd --- /dev/null +++ b/kioslave/thumbnail/cursorcreator.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Fredrik Höglund <fredrik@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _CURSORCREATOR_H_ +#define _CURSORCREATOR_H_ + +#include <kio/thumbcreator.h> + +class CursorCreator : public ThumbCreator +{ + public: + CursorCreator() {} + bool create( const QString &path, int, int, QImage &img ); +}; + +#endif + diff --git a/kioslave/thumbnail/cursorthumbnail.desktop b/kioslave/thumbnail/cursorthumbnail.desktop new file mode 100644 index 000000000..cd37ea5c4 --- /dev/null +++ b/kioslave/thumbnail/cursorthumbnail.desktop @@ -0,0 +1,80 @@ +[Desktop Entry] +Type=Service +Name=Cursor Files +Name[af]=Muis wyser Lêers +Name[ar]=ملÙات المؤشر +Name[az]=Kursor Faylları +Name[be]=Файлы курÑораў +Name[bg]=Файлове Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð»Ñ†Ð¸ на мишката +Name[bn]=কারà§à¦¸à¦° ফাইল +Name[br]=Restroù reti +Name[bs]=Datoteke sa kursorima +Name[ca]=Fitxers de cursor +Name[cs]=Soubory s kurzory +Name[csb]=Lopczi kùrsorów +Name[cy]=Ffeiliau Cyrchydd +Name[da]=Markørfiler +Name[de]=Cursor-Dateien +Name[el]=ΑÏχεία δÏομÎα +Name[eo]=Tipardosierojn +Name[es]=Archivos de cursores +Name[et]=Kursorifailid +Name[eu]=Kurtsore fitxategiak +Name[fa]=پرونده‌های مکان‌نما +Name[fi]=Osoitintiedostot +Name[fr]=Fichiers de curseurs +Name[fy]=Rinnerke-triemmen +Name[ga]=Comhaid Chursóra +Name[gl]=Ficheiros de Ponteiro +Name[he]=קבצי ×¡×ž× ×™× +Name[hi]=संकेतक फ़ाइल +Name[hr]=Datoteke pokazivaÄa +Name[hu]=Kurzorfájlok +Name[is]=Bendilsskrár +Name[it]=File dei cursori +Name[ja]=カーソルファイル +Name[ka]=კურსáƒáƒ თრფáƒáƒ˜áƒšáƒ”ბი +Name[kk]=Меңзер файлдары +Name[km]=ឯកសារ​ទស្សនáŸâ€‹áž‘្រនិច +Name[ko]=커서 íŒŒì¼ +Name[lt]=Kursorių bylos +Name[lv]=Kursoru faili +Name[mk]=Датотеки Ñо покажувачи +Name[mn]=ТүүчÑÑ Ñ„Ð°Ð¹Ð» +Name[ms]=Fail Kursor +Name[mt]=Fajls ta' kursur +Name[nb]=Pekerfiler +Name[nds]=Blinker-Dateien +Name[ne]=करà¥à¤¸à¤° फाइल +Name[nl]=Cursorbestanden +Name[nn]=Peikarfiler +Name[pa]=ਕਰਸਰ ਫਾਇਲਾਂ +Name[pl]=Pliki kursorów +Name[pt]=Ficheiros de Cursores +Name[pt_BR]=Arquivos de Cursor +Name[ro]=FiÈ™iere cursor +Name[ru]=Файлы курÑоров +Name[rw]=Amadosiye y'Inyoboranyandiko +Name[se]=Sievánfiillat +Name[sk]=Súbory s kurzormi +Name[sl]=Datoteke s kazalci +Name[sr]=Фајлови показивача +Name[sr@Latn]=Fajlovi pokazivaÄa +Name[sv]=Muspekarfiler +Name[ta]=நிலைகாடà¯à®Ÿà®¿ கோபà¯à®ªà¯à®•à®³à¯ +Name[te]=à°®à±à°²à±à°•à± దసà±à°¤à±à°°à°¾à°²à± +Name[tg]=Файлҳои курÑор +Name[th]=à¹à¸Ÿà¹‰à¸¡à¹€à¸„à¸à¸£à¹Œà¹€à¸‹à¸à¸£à¹Œ +Name[tr]=Ä°mleç Dosyaları +Name[tt]=Kürsär Biremnäre +Name[uk]=Файли курÑорів +Name[uz]=Kursor fayllari +Name[uz@cyrillic]=КурÑор файллари +Name[vi]=Táºp tin Con trá» +Name[wa]=Fitchîs cursoe +Name[zh_CN]=å…‰æ ‡æ–‡ä»¶ +Name[zh_TW]=游標檔案 +ServiceTypes=ThumbCreator +MimeTypes=image/x-xcursor +X-KDE-Library=cursorthumbnail +CacheThumbnail=false diff --git a/kioslave/thumbnail/djvucreator.cpp b/kioslave/thumbnail/djvucreator.cpp new file mode 100644 index 000000000..653fc4163 --- /dev/null +++ b/kioslave/thumbnail/djvucreator.cpp @@ -0,0 +1,139 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Malte Starostik <malte@kde.org> + Copyright (C) 2001 Leon Bottou <leon@bottou.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + + +#include <assert.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/time.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <errno.h> + +#include <qfile.h> +#include <qimage.h> + +#include <kdemacros.h> + +#include "djvucreator.h" + + + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new DjVuCreator; + } +} + +bool DjVuCreator::create(const QString &path, int width, int height, QImage &img) +{ + int output[2]; + QByteArray data(1024); + bool ok = false; + + if (pipe(output) == -1) + return false; + + const char* argv[8]; + QCString sizearg, fnamearg; + sizearg.sprintf("%dx%d", width, height); + fnamearg = QFile::encodeName( path ); + argv[0] = "ddjvu"; + argv[1] = "-page"; + argv[2] = "1"; + argv[3] = "-size"; + argv[4] = sizearg.data(); + argv[5] = fnamearg.data(); + argv[6] = 0; + + pid_t pid = fork(); + if (pid == 0) + { + close(output[0]); + dup2(output[1], STDOUT_FILENO); + execvp(argv[0], const_cast<char *const *>(argv)); + exit(1); + } + else if (pid >= 0) + { + close(output[1]); + int offset = 0; + while (!ok) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(output[0], &fds); + struct timeval tv; + tv.tv_sec = 20; + tv.tv_usec = 0; + if (select(output[0] + 1, &fds, 0, 0, &tv) <= 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + break; // error or timeout + } + if (FD_ISSET(output[0], &fds)) { + int count = read(output[0], data.data() + offset, 1024); + if (count == -1) + break; + if (count) // prepare for next block + { + offset += count; + data.resize(offset + 1024); + } + else // got all data + { + data.resize(offset); + ok = true; + } + } + } + if (!ok) + kill(pid, SIGTERM); + int status = 0; + if (waitpid(pid, &status, 0) != pid || (status != 0 && status != 256) ) + ok = false; + } + else + { + close(output[1]); + } + + close(output[0]); + int l = img.loadFromData( data ); + return ok && l; +} + + +ThumbCreator::Flags DjVuCreator::flags() const +{ + return static_cast<Flags>(DrawFrame); +} + + diff --git a/kioslave/thumbnail/djvucreator.h b/kioslave/thumbnail/djvucreator.h new file mode 100644 index 000000000..f6724c163 --- /dev/null +++ b/kioslave/thumbnail/djvucreator.h @@ -0,0 +1,35 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <malte@kde.org> + Copyright (C) 2003 Leon Bottou <leon@bottou.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _DJVUCREATOR_H_ +#define _DJVUCREATOR_H_ + +#include <kio/thumbcreator.h> + +class DjVuCreator : public ThumbCreator +{ +public: + DjVuCreator() {} + virtual bool create(const QString &path, int, int, QImage &img); + virtual Flags flags() const; + +}; + +#endif diff --git a/kioslave/thumbnail/djvuthumbnail.desktop b/kioslave/thumbnail/djvuthumbnail.desktop new file mode 100644 index 000000000..41ef0678f --- /dev/null +++ b/kioslave/thumbnail/djvuthumbnail.desktop @@ -0,0 +1,81 @@ +[Desktop Entry] +Type=Service +Name=DjVu Files +Name[af]=DjVu Lêers +Name[ar]=ملÙات DjVu +Name[az]=DjVu Faylları +Name[be]=DjVu файлы +Name[bg]=DjVu файлове +Name[bn]=DjVu ফাইল +Name[br]=Restr DjVu +Name[bs]=DjVu datoteke +Name[ca]=Fitxers DjVu +Name[cs]=DjVu soubory +Name[csb]=Lopczi DjVu +Name[cy]=Ffeiliau DjVu +Name[da]=DjVu-filer +Name[de]=DjVu-Dateien +Name[el]=ΑÏχεία DjVu +Name[eo]=DjVu-dosieroj +Name[es]=Archivos DjVu +Name[et]=DjVu failid +Name[eu]=DjVu fitxategiak +Name[fa]=پرونده‌های DjVu +Name[fi]=DjVu-tiedostot +Name[fr]=Fichiers DjVu +Name[fy]=DjVu-triemmen +Name[ga]=Comhaid DjVu +Name[gl]=Ficheiros DjVu +Name[he]=קבצי DjVu +Name[hi]=DjVu फ़ाइलें +Name[hr]=DjVu datoteke +Name[hu]=DjVu-fájlok +Name[is]=DjVu skrár +Name[it]=File DjVu +Name[ja]=DjVuファイル +Name[ka]=DjVu ფáƒáƒ˜áƒšáƒ”ბი +Name[kk]=DjVu файлдары +Name[km]=ឯកសារ DjVu +Name[ko]=DjVu íŒŒì¼ +Name[lt]=DjVu bylos +Name[lv]=DjVu faili +Name[mk]=DjVu-датотеки +Name[mn]=DjVu файлууд +Name[ms]=Fail DjVu +Name[mt]=Fajls DjVu +Name[nb]=DjVu-filer +Name[nds]=DjVu-Dateien +Name[ne]=DjVu फाइल +Name[nl]=DjVu-bestanden +Name[nn]=DjVu-filer +Name[pa]=DjVu ਫਾਇਲਾਂ +Name[pl]=Pliki DjVu +Name[pt]=Ficheiros DjVu +Name[pt_BR]=Arquivos DjVu +Name[ro]=FiÈ™iere DjVu +Name[ru]=Файлы DjVu +Name[rw]=Amadosiye DjVu +Name[se]=DjVu-fiilat +Name[sk]=Súbory DjVu +Name[sl]=Datoteke DjVu +Name[sr]=DjVu фајлови +Name[sr@Latn]=DjVu fajlovi +Name[sv]=DjVu-filer +Name[ta]=DjVu கோபà¯à®ªà¯à®•à®³à¯ +Name[te]=డెజావౠదసà±à°¤à±à°°à°¾à°²à± +Name[tg]=Файлҳои DjVu +Name[th]=à¹à¸Ÿà¹‰à¸¡ DjVu +Name[tr]=DjVu Dosyaları +Name[tt]=DjVu Bireme +Name[uk]=Файли DjVu +Name[uz]=DjVu-fayllari +Name[uz@cyrillic]=DjVu-файллари +Name[vi]=Táºp tin DjVu +Name[wa]=Fitchîs DjVu +Name[zh_CN]=DjVu 文件 +Name[zh_TW]=DjVu 檔案 +ServiceTypes=ThumbCreator +MimeTypes=image/x-djvu,image/x.djvu,image/vnd.djvu +X-KDE-Library=djvuthumbnail +CacheThumbnail=true +IgnoreMaximumSize=true diff --git a/kioslave/thumbnail/exrcreator.cpp b/kioslave/thumbnail/exrcreator.cpp new file mode 100644 index 000000000..ac2bc3a71 --- /dev/null +++ b/kioslave/thumbnail/exrcreator.cpp @@ -0,0 +1,85 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Brad Hards <bradh@frogmouth.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <qimage.h> + +#include <kimageio.h> +#include <kdebug.h> +#include <kconfig.h> +#include <kglobal.h> +#include <qfile.h> + +#include <ImfInputFile.h> +#include <ImfPreviewImage.h> + +#include "exrcreator.h" + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new EXRCreator; + } +} + +bool EXRCreator::create(const QString &path, int, int, QImage &img) +{ + Imf::InputFile in ( path.ascii() ); + const Imf::Header &h = in.header(); + + if ( h.hasPreviewImage() ) { + kdDebug() << "EXRcreator - using preview" << endl; + const Imf::PreviewImage &preview = in.header().previewImage(); + QImage qpreview(preview.width(), preview.height(), 32, 0, QImage::BigEndian); + for ( unsigned int y=0; y < preview.height(); y++ ) { + for ( unsigned int x=0; x < preview.width(); x++ ) { + const Imf::PreviewRgba &q = preview.pixels()[x+(y*preview.width())]; + qpreview.setPixel( x, y, qRgba(q.r, q.g, q.b, q.a) ); + } + } + img = qpreview; + return true; + } else { + // do it the hard way + // We ignore maximum size when just extracting the thumnail + // from the header, but it is very expensive to render large + // EXR images just to turn it into an icon, so we go back + // to honouring it in here. + kdDebug() << "EXRcreator - using original image" << endl; + KConfig * config = KGlobal::config(); + KConfigGroupSaver cgs( config, "PreviewSettings" ); + unsigned long long maxSize = config->readNumEntry( "MaximumSize", 1024*1024 /* 1MB */ ); + unsigned long long fileSize = QFile( path ).size(); + if ( (fileSize > 0) && (fileSize < maxSize) ) { + if (!img.load( path )) { + return false; + } + if (img.depth() != 32) + img = img.convertDepth( 32 ); + return true; + } else { + return false; + } + } +} + +ThumbCreator::Flags EXRCreator::flags() const +{ + return None; +} diff --git a/kioslave/thumbnail/exrcreator.h b/kioslave/thumbnail/exrcreator.h new file mode 100644 index 000000000..498bcfb1b --- /dev/null +++ b/kioslave/thumbnail/exrcreator.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Brad Hards <bradh@frogmouth.net> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _EXRCREATOR_H_ +#define _EXRCREATOR_H_ + +#include <kio/thumbcreator.h> + +class EXRCreator : public ThumbCreator +{ +public: + EXRCreator() {}; + virtual bool create(const QString &path, int, int, QImage &img); + virtual Flags flags() const; +}; + +#endif diff --git a/kioslave/thumbnail/exrthumbnail.desktop b/kioslave/thumbnail/exrthumbnail.desktop new file mode 100644 index 000000000..b532e31ed --- /dev/null +++ b/kioslave/thumbnail/exrthumbnail.desktop @@ -0,0 +1,79 @@ +[Desktop Entry] +Type=Service +Name=EXR Images +Name[af]=EXR Beelde +Name[ar]=صور EXR +Name[az]=EXR RÉ™smlÉ™ri +Name[be]=ВідарыÑÑ‹ EXR +Name[bg]=EXR образи +Name[bn]=ই-à¦à¦•à§à¦¸-আর ছবি +Name[br]=Skeudennoù EXR +Name[bs]=EXR slike +Name[ca]=Imatges EXR +Name[cs]=EXR obrázky +Name[csb]=Ã’brôzczi EXR +Name[da]=EXR-billeder +Name[de]=EXR-Bilder +Name[el]=Εικόνες EXR +Name[eo]=EXR-Bildoj +Name[es]=Imágenes EXR +Name[et]=EXR pildifailid +Name[eu]=EXR irudiak +Name[fa]=تصاویر EXR +Name[fi]=EXR-kuvat +Name[fr]=Images EXR +Name[fy]=EXR-ôfbyldings +Name[ga]=Ãomhánna EXR +Name[gl]=Imaxes EXR +Name[he]=×ª×ž×•× ×•×ª EXR +Name[hi]=ईà¤à¤•à¥à¤¸à¤†à¤° छवियाठ+Name[hr]=EXR slike +Name[hu]=EXR-képek +Name[is]=EXR myndir +Name[it]=Immagini EXR +Name[ja]=EXR イメージ +Name[ka]=EXR გáƒáƒ›áƒáƒ¡áƒáƒ®áƒ£áƒšáƒ”ბები +Name[kk]=EXR кеÑкіндері +Name[km]=រូបភាព EXR +Name[ko]=EXR 그림 +Name[lt]=EXR paveiksliukai +Name[lv]=EXR attÄ“li +Name[mk]=EXR-Ñлики +Name[ms]=Imej EXR +Name[mt]=Stampi EXR +Name[nb]=EXR-bilder +Name[nds]=EXR-Biller +Name[ne]=EXR छवि +Name[nl]=EXR-afbeeldingen +Name[nn]=EXR-bilete +Name[pa]=EXR ਚਿੱਤਰ +Name[pl]=Obrazki EXR +Name[pt]=Imagens EXR +Name[pt_BR]=Imagens EXR +Name[ro]=Imagini EXR +Name[ru]=РиÑунки EXR +Name[rw]=Amashusho EXR +Name[se]=EXR-govat +Name[sk]=EXR Obrázky +Name[sl]=Slike EXR +Name[sr]=EXR Ñлике +Name[sr@Latn]=EXR slike +Name[sv]=EXR-bilder +Name[ta]=இ எகà¯à®¸à¯ ஆர௠பிமà¯à®ªà®™à¯à®•à®³à¯ +Name[te]=ఈఎకà±à°¸à± ఆరౠచితà±à°°à°¾à°²à± +Name[tg]=ТаÑвирҳои EXR +Name[th]=à¹à¸Ÿà¹‰à¸¡à¸ าพ EXR +Name[tr]=EXR Görüntüsü +Name[tt]=EXR Bireme +Name[uk]=Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ EXR +Name[uz]=EXR-rasmlar +Name[uz@cyrillic]=EXR-раÑмлар +Name[vi]=Ảnh EXR +Name[wa]=ImÃ¥djes EXR +Name[zh_CN]=EXR Emacs +Name[zh_TW]=EXR å½±åƒ +ServiceTypes=ThumbCreator +MimeTypes=image/x-exr +X-KDE-Library=exrthumbnail +CacheThumbnail=true +IgnoreMaximumSize=true diff --git a/kioslave/thumbnail/htmlcreator.cpp b/kioslave/thumbnail/htmlcreator.cpp new file mode 100644 index 000000000..0431bcf6e --- /dev/null +++ b/kioslave/thumbnail/htmlcreator.cpp @@ -0,0 +1,118 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <malte@kde.org> + Copyright (C) 2006 Roberto Cappuccio <roberto.cappuccio@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <time.h> +#include <qpixmap.h> +#include <qimage.h> +#include <qpainter.h> +#include <kapplication.h> +#include <khtml_part.h> + +#include "htmlcreator.h" + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new HTMLCreator; + } +} + +HTMLCreator::HTMLCreator() + : m_html(0) +{ +} + +HTMLCreator::~HTMLCreator() +{ + delete m_html; +} + +bool HTMLCreator::create(const QString &path, int width, int height, QImage &img) +{ + if (!m_html) + { + m_html = new KHTMLPart; + connect(m_html, SIGNAL(completed()), SLOT(slotCompleted())); + m_html->setJScriptEnabled(false); + m_html->setJavaEnabled(false); + m_html->setPluginsEnabled(false); + m_html->setMetaRefreshEnabled(false); + m_html->setOnlyLocalReferences(true); + } + KURL url; + url.setPath(path); + m_html->openURL(url); + + int t = startTimer(5000); + + qApp->enter_loop(); + + killTimer(t); + + // render the HTML page on a bigger pixmap and use smoothScale, + // looks better than directly scaling with the QPainter (malte) + QPixmap pix; + if (width > 400 || height > 600) + { + if (height * 3 > width * 4) + pix.resize(width, width * 4 / 3); + else + pix.resize(height * 3 / 4, height); + } + else + pix.resize(400, 600); + + // light-grey background, in case loadind the page failed + pix.fill( QColor( 245, 245, 245 ) ); + + int borderX = pix.width() / width, borderY = pix.height() / height; + QRect rc(borderX, borderY, pix.width() - borderX * 2, + pix.height() - borderY * 2); + + QPainter p; + p.begin(&pix); + m_html->paint(&p, rc); + p.end(); + + img = pix.convertToImage(); + + m_html->closeURL(); + + return true; +} + +void HTMLCreator::timerEvent(QTimerEvent *) +{ + qApp->exit_loop(); +} + +void HTMLCreator::slotCompleted() +{ + qApp->exit_loop(); +} + +ThumbCreator::Flags HTMLCreator::flags() const +{ + return DrawFrame; +} + +#include "htmlcreator.moc" + diff --git a/kioslave/thumbnail/htmlcreator.h b/kioslave/thumbnail/htmlcreator.h new file mode 100644 index 000000000..6cb58b18f --- /dev/null +++ b/kioslave/thumbnail/htmlcreator.h @@ -0,0 +1,47 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <malte@kde.org> + Copyright (C) 2006 Roberto Cappuccio <roberto.cappuccio@gmail.com> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _HTMLCREATOR_H_ +#define _HTMLCREATOR_H_ + +#include <kio/thumbcreator.h> + +class KHTMLPart; + +class HTMLCreator : public QObject, public ThumbCreator +{ + Q_OBJECT +public: + HTMLCreator(); + virtual ~HTMLCreator(); + virtual bool create(const QString &path, int width, int height, QImage &img); + virtual Flags flags() const; + +protected: + virtual void timerEvent(QTimerEvent *); + +private slots: + void slotCompleted(); + +private: + KHTMLPart *m_html; +}; + +#endif diff --git a/kioslave/thumbnail/htmlthumbnail.desktop b/kioslave/thumbnail/htmlthumbnail.desktop new file mode 100644 index 000000000..30a057051 --- /dev/null +++ b/kioslave/thumbnail/htmlthumbnail.desktop @@ -0,0 +1,86 @@ +[Desktop Entry] +Type=Service +Name=HTML Files +Name[af]=HTML Lêers +Name[ar]=ملÙات HTML +Name[az]=HTML Faylları +Name[be]=Файлы HTML +Name[bg]=HTML файлове +Name[bn]=HTML ফাইল +Name[br]=Restroù HTML +Name[bs]=HTML datoteke +Name[ca]=Fitxers HTML +Name[cs]=HTML soubory +Name[csb]=Lopczi HTML +Name[cy]=Ffeiliau HTML +Name[da]=HTML-filer +Name[de]=HTML-Dateien +Name[el]=ΑÏχεία HTML +Name[eo]=HTML-dosieroj +Name[es]=Archivos HTML +Name[et]=HTML failid +Name[eu]=HTML fitxategiak +Name[fa]=پرونده‌های زنگام +Name[fi]=HTML-tiedostot +Name[fr]=Fichiers HTML +Name[fy]=HTML-triemmen +Name[ga]=Comhaid HTML +Name[gl]=Ficheiros HTML +Name[he]=קבצי HTML +Name[hi]=à¤à¤šà¤Ÿà¥€à¤à¤®à¤à¤² फ़ाइलें +Name[hr]=HTML datoteke +Name[hu]=HTML-fájlok +Name[id]=Berkas HTML +Name[is]=HTML skrár +Name[it]=File HTML +Name[ja]=HTML ファイル +Name[ka]=HTML ფáƒáƒ˜áƒšáƒ”ბი +Name[kk]=HTML файлдары +Name[km]=ឯកសារ HTML +Name[ko]=HTML íŒŒì¼ +Name[lo]=à»àºŸà»‰àº¡ HTML +Name[lt]=HTML bylos +Name[lv]=HTML Faili +Name[mk]=HTML-датотеки +Name[mn]=HTML-Файлууд +Name[ms]=Fail HTML +Name[mt]=Fajls HTML +Name[nb]=HTML-filer +Name[nds]=HTML-Dateien +Name[ne]=HTML फाइल +Name[nl]=HTML-bestanden +Name[nn]=HTML-filer +Name[nso]=Difaele tsa HTML +Name[pa]=HTML ਫਾਇਲ਼ਾਂ +Name[pl]=Strony HTML +Name[pt]=Ficheiros HTML +Name[pt_BR]=Arquivos HTML +Name[ro]=FiÈ™iere HTML +Name[ru]=Файлы HTML +Name[rw]=Amadosiye HTML +Name[se]=HTML-fiillat +Name[sk]=HTML súbory +Name[sl]=Datoteke HTML +Name[sr]=HTML фајлови +Name[sr@Latn]=HTML fajlovi +Name[sv]=HTML-filer +Name[ta]=HTML கோபà¯à®ªà¯à®•à®³à¯ +Name[te]=హెచౠటి à°Žà°‚ ఎలౠదసà±à°¤à±à°°à°¾à°²à± +Name[tg]=Файлҳои HTML +Name[th]=à¹à¸Ÿà¹‰à¸¡ HTML +Name[tr]=HTML Dosyaları +Name[tt]=HTML Bireme +Name[uk]=Файли HTML +Name[uz]=HTML-fayllari +Name[uz@cyrillic]=HTML-файллари +Name[ven]=Dzifaela dza HTML +Name[vi]=Táºp tin HTML +Name[wa]=Fitchîs HTML +Name[xh]=Iifayile ze HTML +Name[zh_CN]=HTML 文件 +Name[zh_TW]=HTML 檔案 +Name[zu]=Amafayela e-HTML +ServiceTypes=ThumbCreator +MimeTypes=text/html +X-KDE-Library=htmlthumbnail +CacheThumbnail=true diff --git a/kioslave/thumbnail/imagecreator.cpp b/kioslave/thumbnail/imagecreator.cpp new file mode 100644 index 000000000..24a75a51a --- /dev/null +++ b/kioslave/thumbnail/imagecreator.cpp @@ -0,0 +1,51 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Carsten Pfeiffer <pfeiffer@kde.org> + 2000 Malte Starostik <malte@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <assert.h> + +#include <qimage.h> + +#include <kimageio.h> + +#include "imagecreator.h" + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + KImageIO::registerFormats(); + return new ImageCreator; + } +} + +bool ImageCreator::create(const QString &path, int, int, QImage &img) +{ + // create image preview + if (!img.load( path )) + return false; + if (img.depth() != 32) + img = img.convertDepth( 32 ); + return true; +} + +ThumbCreator::Flags ImageCreator::flags() const +{ + return None; +} diff --git a/kioslave/thumbnail/imagecreator.h b/kioslave/thumbnail/imagecreator.h new file mode 100644 index 000000000..d06c9fa7d --- /dev/null +++ b/kioslave/thumbnail/imagecreator.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <malte@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _IMAGECREATOR_H_ +#define _IMAGECREATOR_H_ + +#include <kio/thumbcreator.h> + +class ImageCreator : public ThumbCreator +{ +public: + ImageCreator() {} + virtual bool create(const QString &path, int, int, QImage &img); + virtual Flags flags() const; +}; + +#endif diff --git a/kioslave/thumbnail/imagethumbnail.desktop b/kioslave/thumbnail/imagethumbnail.desktop new file mode 100644 index 000000000..6b53f5392 --- /dev/null +++ b/kioslave/thumbnail/imagethumbnail.desktop @@ -0,0 +1,85 @@ +[Desktop Entry] +Type=Service +Name=Images +Name[af]=Beelde +Name[ar]=الصور +Name[az]=RÉ™smlÉ™r +Name[be]=ВідарыÑÑ‹ +Name[bg]=Графични файлове +Name[bn]=ছবি +Name[br]=Skeudennoù +Name[bs]=Slike +Name[ca]=Imatges +Name[cs]=Obrázky +Name[csb]=Ã’brôzczi +Name[cy]=Delweddau +Name[da]=Billeder +Name[de]=Bilder +Name[el]=Εικόνες +Name[eo]=Bildoj +Name[es]=Imágenes +Name[et]=Pildid +Name[eu]=Irudiak +Name[fa]=تصاویر +Name[fi]=Kuvat +Name[fy]=Ofbyldings +Name[ga]=Ãomhánna +Name[gl]=Imaxes +Name[he]=×ª×ž×•× ×•×ª +Name[hi]=छवि +Name[hr]=Slike +Name[hu]=Képek +Name[id]=Gambar +Name[is]=Myndir +Name[it]=Immagini +Name[ja]=イメージ +Name[ka]=გáƒáƒ›áƒáƒ¡áƒáƒ®áƒ£áƒšáƒ”ბები +Name[kk]=КеÑкіндер +Name[km]=រូបភាព +Name[ko]=그림 +Name[lo]=à»àºŸà»‰àº¡àºžàº²àºš +Name[lt]=Paveiksliukai +Name[lv]=AttÄ“li +Name[mk]=Слики +Name[mn]=Зургууд +Name[ms]=Imej +Name[mt]=Stampi +Name[nb]=Bilder +Name[nds]=Biller +Name[ne]=छवि +Name[nl]=Afbeeldingen +Name[nn]=Bilete +Name[nso]=Diponagalo +Name[pa]=ਚਿੱਤਰ +Name[pl]=Obrazki +Name[pt]=Imagens +Name[pt_BR]=Imagens +Name[ro]=Imagini +Name[ru]=РиÑунки +Name[rw]=Amashusho +Name[se]=Govat +Name[sk]=Obrázky +Name[sl]=Slike +Name[sr]=Слике +Name[sr@Latn]=Slike +Name[sv]=Grafik +Name[ta]=பிமà¯à®ªà®™à¯à®•à®³à¯ +Name[te]=à°šà°¿à°¤à±à°°à°¾à°²à± +Name[tg]=ТаÑвир +Name[th]=à¹à¸Ÿà¹‰à¸¡à¸ าพ +Name[tr]=Resimler +Name[tt]=Sürätlär +Name[uk]=Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ +Name[uz]=Rasmlar +Name[uz@cyrillic]=РаÑмлар +Name[ven]=Zwifanyiso +Name[vi]=Ảnh +Name[wa]=ImÃ¥djes +Name[xh]=Imifanekiso +Name[zh_CN]=å›¾åƒ +Name[zh_TW]=å½±åƒ +Name[zu]=Izithombe +ServiceTypes=ThumbCreator +MimeTypes=image/cgm,image/fax-g3,image/gif,image/jp2,image/jpeg,image/pjpeg,image/png,image/tiff,image/x-bmp,image/x-dds,image/x-ico,image/x-jng,image/x-pcx,image/x-photo-cd,image/x-portable-bitmap,image/x-portable-greymap,image/x-portable-pixmap,image/x-rgb,image/x-targa,image/x-wmf,image/x-xbm,image/x-xcf-gimp,image/x-xfig,image/x-xpm +X-KDE-Library=imagethumbnail +CacheThumbnail=true diff --git a/kioslave/thumbnail/textcreator.cpp b/kioslave/thumbnail/textcreator.cpp new file mode 100644 index 000000000..b0424e228 --- /dev/null +++ b/kioslave/thumbnail/textcreator.cpp @@ -0,0 +1,195 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000,2002 Carsten Pfeiffer <pfeiffer@kde.org> + 2000 Malte Starostik <malte@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <qfile.h> +#include <qpixmap.h> +#include <qimage.h> + +#include <kstandarddirs.h> +#include <kpixmapsplitter.h> +#include <kmimetype.h> + +#include "textcreator.h" + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new TextCreator; + } +} + +TextCreator::TextCreator() + : m_splitter(0), + m_data(0), + m_dataSize(0) +{ +} + +TextCreator::~TextCreator() +{ + delete m_splitter; + delete [] m_data; +} + +bool TextCreator::create(const QString &path, int width, int height, QImage &img) +{ + // Filter out unwanted mimetypes + KMimeType::Ptr mimeType = KMimeType::findByPath( path ); + if ( mimeType->is( "text/rtf" ) ) + return false; + + if ( !m_splitter ) + { + m_splitter = new KPixmapSplitter; + QString pixmap = locate( "data", "konqueror/pics/thumbnailfont_7x4.png" ); + if ( !pixmap.isEmpty() ) + { + // FIXME: make font/glyphsize configurable... + m_splitter->setPixmap( QPixmap( pixmap )); + m_splitter->setItemSize( QSize( 4, 7 )); + } + } + + bool ok = false; + + // determine some sizes... + // example: width: 60, height: 64 + QSize pixmapSize( width, height ); + if (height * 3 > width * 4) + pixmapSize.setHeight( width * 4 / 3 ); + else + pixmapSize.setWidth( height * 3 / 4 ); + + if ( pixmapSize != m_pixmap.size() ) + m_pixmap.resize( pixmapSize ); + + // one pixel for the rectangle, the rest. whitespace + int xborder = 1 + pixmapSize.width()/16; // minimum x-border + int yborder = 1 + pixmapSize.height()/16; // minimum y-border + + QSize chSize = m_splitter->itemSize(); // the size of one char + int xOffset = chSize.width(); + int yOffset = chSize.height(); + + // calculate a better border so that the text is centered + int canvasWidth = pixmapSize.width() - 2*xborder; + int canvasHeight = pixmapSize.height() - 2*yborder; + int numCharsPerLine = (int) (canvasWidth / chSize.width()); + int numLines = (int) (canvasHeight / chSize.height()); + + // assumes an average line length of <= 120 chars + const int bytesToRead = 120 * numLines; + + // create text-preview + QFile file( path ); + if ( file.open( IO_ReadOnly )) + { + if ( !m_data || m_dataSize < bytesToRead + 1 ) + { + delete [] m_data; + m_data = new char[bytesToRead+1]; + m_dataSize = bytesToRead + 1; + } + + int read = file.readBlock( m_data, bytesToRead ); + if ( read > 0 ) + { + ok = true; + m_data[read] = '\0'; + QString text = QString::fromLocal8Bit( m_data ); + // FIXME: maybe strip whitespace and read more? + + m_pixmap.fill( QColor( 245, 245, 245 ) ); // light-grey background + + QRect rect; + + int rest = m_pixmap.width() - (numCharsPerLine * chSize.width()); + xborder = QMAX( xborder, rest/2); // center horizontally + rest = m_pixmap.height() - (numLines * chSize.height()); + yborder = QMAX( yborder, rest/2); // center vertically + // end centering + + int x = xborder, y = yborder; // where to paint the characters + int posNewLine = m_pixmap.width() - (chSize.width() + xborder); + int posLastLine = m_pixmap.height() - (chSize.height() + yborder); + bool newLine = false; + Q_ASSERT( posNewLine > 0 ); + const QPixmap *fontPixmap = &(m_splitter->pixmap()); + + for ( uint i = 0; i < text.length(); i++ ) + { + if ( x > posNewLine || newLine ) // start a new line? + { + x = xborder; + y += yOffset; + + if ( y > posLastLine ) // more text than space + break; + + // after starting a new line, we also jump to the next + // physical newline in the file if we don't come from one + if ( !newLine ) + { + int pos = text.find( '\n', i ); + if ( pos == -1 ) + break; + i = pos + 1; + } + + newLine = false; + } + + // check for newlines in the text (unix,dos) + QChar ch = text.at( i ); + if ( ch == '\n' ) + { + newLine = true; + continue; + } + else if ( ch == '\r' && text.at(i+1) == '\n' ) + { + newLine = true; + i++; // skip the next character (\n) as well + continue; + } + + rect = m_splitter->coordinates( ch ); + if ( !rect.isEmpty() ) + { + bitBlt( &m_pixmap, QPoint(x,y), fontPixmap, rect, Qt::CopyROP ); + } + + x += xOffset; // next character + } + if (ok) + img = m_pixmap.convertToImage(); + } + + file.close(); + } + return ok; +} + +ThumbCreator::Flags TextCreator::flags() const +{ + return (Flags)(DrawFrame | BlendIcon); +} + diff --git a/kioslave/thumbnail/textcreator.h b/kioslave/thumbnail/textcreator.h new file mode 100644 index 000000000..92461d0cd --- /dev/null +++ b/kioslave/thumbnail/textcreator.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <malte@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _TEXTCREATOR_H_ +#define _TEXTCREATOR_H_ + +#include <qpixmap.h> +#include <kio/thumbcreator.h> + +class KPixmapSplitter; + +class TextCreator : public ThumbCreator +{ +public: + TextCreator(); + virtual ~TextCreator(); + virtual bool create(const QString &path, int width, int height, QImage &img); + virtual Flags flags() const; + +private: + KPixmapSplitter *m_splitter; + char *m_data; + int m_dataSize; + QPixmap m_pixmap; +}; + +#endif diff --git a/kioslave/thumbnail/textthumbnail.desktop b/kioslave/thumbnail/textthumbnail.desktop new file mode 100644 index 000000000..bb13dbf38 --- /dev/null +++ b/kioslave/thumbnail/textthumbnail.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +Type=Service +Name=Text Files +Name[af]=Teks Lêers +Name[ar]=الملÙات النصية +Name[az]=MÉ™tn Faylları +Name[be]=ТÑкÑÑ‚Ð°Ð²Ñ‹Ñ Ñ„Ð°Ð¹Ð»Ñ‹ +Name[bg]=ТекÑтови файлове +Name[bn]=টেকà§à¦¸à¦Ÿ ফাইল +Name[br]=Restroù testenn +Name[bs]=Tekst datoteke +Name[ca]=Fitxers de text +Name[cs]=Textové soubory +Name[csb]=Tekstowé lopczi +Name[cy]=Ffeiliau Testun +Name[da]=Tekstfiler +Name[de]=Textdateien +Name[el]=ΑÏχεία κειμÎνου +Name[eo]=Tekstdosieroj +Name[es]=Archivos de texto +Name[et]=Tekstifailid +Name[eu]=Testu fitxategiak +Name[fa]=پرونده‌های متنی +Name[fi]=Tekstitiedostot +Name[fr]=Fichiers de texte +Name[fy]=Teksttriemmen +Name[ga]=Comhaid Téacs +Name[gl]=Ficheiros de Texto +Name[he]=קבצי טקסט +Name[hi]=पाठफ़ाइलें +Name[hr]=Tekstualne datoteke +Name[hsb]=Tekstowe dataje +Name[hu]=Szöveges fájlok +Name[id]=Berkas Teks +Name[is]=Textaskrár +Name[it]=File di testo +Name[ja]=テã‚ストファイル +Name[ka]=ტექსტური ფáƒáƒ˜áƒšáƒ”ბი +Name[kk]=Мәтін файлдары +Name[km]=ឯកសារ​អážáŸ’ážáž”áž‘ +Name[ko]=í…스트 íŒŒì¼ +Name[lo]=à»àºŸà»‰àº¡àº‚à»à»‰àº„ວາມ +Name[lt]=tekstinÄ—s bylos +Name[lv]=Teksta Faili +Name[mk]=ТекÑтуални датотеки +Name[mn]=ТекÑÑ‚ файлууд +Name[ms]=Fail Teks +Name[mt]=Fajls ta' Test +Name[nb]=Tekstfiler +Name[nds]=Textdateien +Name[ne]=पाठफाइल +Name[nl]=Tekstbestanden +Name[nn]=Tekstfiler +Name[nso]=Difaele tsa Sengwalwana +Name[pa]=ਪਾਠਫਾਇਲ਼ਾਂ +Name[pl]=Pliki tekstowe +Name[pt]=Ficheiros de Texto +Name[pt_BR]=Arquivos Texto +Name[ro]=FiÈ™iere text +Name[ru]=ТекÑтовые файлы +Name[rw]=Amadosiye Mwandiko +Name[se]=Teakstafiilat +Name[sk]=Textové súbory +Name[sl]=Besedilne datoteke +Name[sr]=ТекÑтуални фајлови +Name[sr@Latn]=Tekstualni fajlovi +Name[sv]=Textfiler +Name[ta]=உரை கோபà¯à®ªà¯à®•à®³à¯ +Name[te]=వచన దసà±à°¤à±à°°à°¾à°²à± +Name[tg]=Файлҳои матн +Name[th]=à¹à¸Ÿà¹‰à¸¡à¸‚้à¸à¸„วาม +Name[tr]=Metin Dosyaları +Name[tt]=Mäten Bireme +Name[uk]=ТекÑтові файли +Name[uz]=Matn fayllari +Name[uz@cyrillic]=Матн файллари +Name[ven]=Dzifaela dza manwalwa +Name[vi]=Táºp tin Văn bản +Name[wa]=Fitchîs tecse +Name[xh]=Iifayile Zombhalo +Name[zh_CN]=文本文件 +Name[zh_TW]=æ–‡å—檔案 +Name[zu]=Amafayela Ombhalo +ServiceTypes=ThumbCreator +MimeTypes=text/plain +X-KDE-Library=textthumbnail +CacheThumbnail=false diff --git a/kioslave/thumbnail/thumbcreator.desktop b/kioslave/thumbnail/thumbcreator.desktop new file mode 100644 index 000000000..e7c5951f8 --- /dev/null +++ b/kioslave/thumbnail/thumbcreator.desktop @@ -0,0 +1,86 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=ThumbCreator +Comment=Thumbnail Handler +Comment[af]=Duimnael Handteerder +Comment[ar]=معامل الأظاÙر +Comment[az]=Kiçik RÉ™sm Faylları Nümayişçisi +Comment[be]=Ðпрацоўшчык мініÑцюраў +Comment[bg]=Манипулатор на мини Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ +Comment[bn]=থামà§à¦¬à¦¨à§‡à¦‡à¦² হà§à¦¯à¦¾à¦£à§à¦¡à¦²à¦¾à¦° +Comment[bs]=Upravljanje thumbnail-ima +Comment[ca]=Gestor de miniatures +Comment[cs]=OvladaÄ miniatur +Comment[csb]=Programa òbsłùżënkù miniaturków +Comment[cy]=Trinydd Lluniau Cryno +Comment[da]=MiniaturehÃ¥ndtering +Comment[de]=Minibild-Verwaltung +Comment[el]=ΧειÏιστής εικόνας επισκόπησης +Comment[eo]=Miniatura traktilo +Comment[es]=Manejador de miniaturas +Comment[et]=Pisipiltide käsitlemine +Comment[eu]=Koadro txikien maneiatzailea +Comment[fa]=گردانندۀ ریزنقش +Comment[fi]=Esikatselukuvien hallinta +Comment[fr]=Gestionnaire d'aperçu des images +Comment[fy]=Miniatueren ôfhanneler +Comment[ga]=Láimhseálaà na Mionsamhlacha +Comment[gl]=Manexador de Miniaturas +Comment[he]=×ž× ×”×œ הדוגמיות +Comment[hi]=थमà¥à¤¬à¤¨à¥‡à¤² हैंडलर +Comment[hr]=Rukovatelj sliÄicama +Comment[hu]=A gyorsnézeti ikonok kezelÅ‘je +Comment[is]=Þumalmyndaforrit +Comment[it]=Gestore delle miniature +Comment[ja]=サムãƒã‚¤ãƒ«ãƒãƒ³ãƒ‰ãƒ© +Comment[ka]=მინი-გáƒáƒ›áƒáƒ¡áƒáƒ®áƒ£áƒšáƒ”ბის დáƒáƒ›áƒ›áƒ£áƒ¨áƒáƒ•áƒ”ბელი +Comment[kk]=Ðобай өңдеуші +Comment[km]=ឧបករណáŸâ€‹ážŠáŸ„ះស្រាយ​រូបភាព​ážáž¼áž…ៗ +Comment[ko]=미리 보기 처리기 +Comment[lt]=MiniatiÅ«rų tvarkiklis +Comment[lv]=SÄ«ktÄ“lu apkalpotÄjs +Comment[mk]=Ракувач Ñо Ñликички +Comment[mn]=БÑцхан зураг-Удирдлага +Comment[ms]=Pengendali Thumbnail +Comment[mt]=Handler għall-bolol +Comment[nb]=HÃ¥ndtering av forhÃ¥ndsvisning +Comment[nds]=Minibildmaker +Comment[ne]=थमà¥à¤¬à¤¨à¥‡à¤² हà¥à¤¯à¤¾à¤¨à¥à¤¡à¤²à¤° +Comment[nl]=Miniafbeeldingenmaker +Comment[nn]=Handtering av førehandsvising +Comment[nso]=Moswari wa Thumbnail +Comment[pa]=ਥੰਮਨੇਲ ਹੈਂਡਲਰ +Comment[pl]=Program obsÅ‚ugujÄ…cy miniaturki +Comment[pt]=Tratamento de Miniaturas +Comment[pt_BR]=Manipulador de Miniaturas +Comment[ro]=Generator previzualizări +Comment[ru]=Обработчик мини-изображений +Comment[rw]=Mugenga Igaragazaryihuse +Comment[se]=GieÄ‘ahala ovdaÄájehemiid +Comment[sk]=Podpora náhľadu obrázkov +Comment[sl]=Obdelovalnik sliÄic +Comment[sr]=Управљач Ñличицама +Comment[sr@Latn]=UpravljaÄ sliÄicama +Comment[sv]=Hanterar miniatyrbilder +Comment[ta]=சிறிய பிமà¯à®ª கையாளà¯à®¤à®²à¯ +Comment[tg]=Идоракунандаи таÑвироти хурдтарины +Comment[th]=ตัวรับมืà¸à¸ าพตัวà¸à¸¢à¹ˆà¸²à¸‡à¹à¸šà¸šà¸¢à¹ˆà¸ +Comment[tr]=Küçük Resim Dosyaları Görüntüleyici +Comment[tt]=Keçesürät EÅŸkärtkeç +Comment[uk]=МаніпулÑтор мініатюр +Comment[ven]=Tshaufara nga gunwe +Comment[vi]=Thao tác Hình nhá» +Comment[wa]=Manaedjeu d' prévoeyaedje +Comment[xh]=Umqheba Wozipho lobhontsi +Comment[zh_CN]=缩略图处ç†å™¨ +Comment[zh_TW]=縮圖處ç†å™¨ +Comment[zu]=Umphathi we-Thumbnail + +[PropertyDef::MimeTypes] +Type=QStringList + +[PropertyDef::CacheThumbnail] +Type=bool + +[PropertyDef::IgnoreMaximumSize] +Type=bool diff --git a/kioslave/thumbnail/thumbnail.cpp b/kioslave/thumbnail/thumbnail.cpp new file mode 100644 index 000000000..99cb3a89d --- /dev/null +++ b/kioslave/thumbnail/thumbnail.cpp @@ -0,0 +1,440 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <malte@kde.org> + 2000 Carsten Pfeiffer <pfeiffer@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <stdlib.h> +#include <unistd.h> +#ifdef __FreeBSD__ + #include <machine/param.h> +#endif +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/shm.h> + +#include <qfile.h> +#include <qbitmap.h> +#include <qpixmap.h> +#include <qpainter.h> +#include <qimage.h> +#include <qbuffer.h> + +#include <kdatastream.h> // Do not remove, needed for correct bool serialization +#include <kurl.h> +#include <kapplication.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kimageeffect.h> +#include <kmimetype.h> +#include <klibloader.h> +#include <kdebug.h> +#include <kservice.h> +#include <kservicetype.h> +#include <kuserprofile.h> +#include <kfilemetainfo.h> +#include <klocale.h> + +#include <config.h> // For HAVE_NICE +#include "thumbnail.h" +#include <kio/thumbcreator.h> + +// Use correctly KInstance instead of KApplication (but then no QPixmap) +#undef USE_KINSTANCE +// Fix thumbnail: protocol +#define THUMBNAIL_HACK (1) + +#ifdef THUMBNAIL_HACK +# include <qfileinfo.h> +# include <ktrader.h> +#endif + +// Recognized metadata entries: +// mimeType - the mime type of the file, used for the overlay icon if any +// width - maximum width for the thumbnail +// height - maximum height for the thumbnail +// iconSize - the size of the overlay icon to use if any +// iconAlpha - the transparency value used for icon overlays +// plugin - the name of the plugin library to be used for thumbnail creation. +// Provided by the application to save an addition KTrader +// query here. +// shmid - the shared memory segment id to write the image's data to. +// The segment is assumed to provide enough space for a 32-bit +// image sized width x height pixels. +// If this is given, the data returned by the slave will be: +// int width +// int height +// int depth +// Otherwise, the data returned is the image in PNG format. + +using namespace KIO; + +extern "C" +{ + KDE_EXPORT int kdemain(int argc, char **argv); +} + + +int kdemain(int argc, char **argv) +{ +#ifdef HAVE_NICE + nice( 5 ); +#endif + +#ifdef USE_KINSTANCE + KInstance instance("kio_thumbnail"); +#else + // creating KApplication in a slave in not a very good idea, + // as dispatchLoop() doesn't allow it to process its messages, + // so it for example wouldn't reply to ksmserver - on the other + // hand, this slave uses QPixmaps for some reason, and they + // need QApplication + // and HTML previews need even KApplication :( + putenv(strdup("SESSION_MANAGER=")); + KApplication::disableAutoDcopRegistration(); + + KApplication app(argc, argv, "kio_thumbnail", false, true); +#endif + + if (argc != 4) + { + kdError(7115) << "Usage: kio_thumbnail protocol domain-socket1 domain-socket2" << endl; + exit(-1); + } + + ThumbnailProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + + return 0; +} + +ThumbnailProtocol::ThumbnailProtocol(const QCString &pool, const QCString &app) + : SlaveBase("thumbnail", pool, app) +{ + m_creators.setAutoDelete(true); + m_iconDict.setAutoDelete(true); + m_iconSize = 0; +} + +ThumbnailProtocol::~ThumbnailProtocol() +{ +} + +void ThumbnailProtocol::get(const KURL &url) +{ + m_mimeType = metaData("mimeType"); + kdDebug(7115) << "Wanting MIME Type:" << m_mimeType << endl; +#ifdef THUMBNAIL_HACK + // ### HACK + bool direct=false; + if (m_mimeType.isEmpty()) + { + kdDebug(7115) << "PATH: " << url.path() << endl; + QFileInfo info(url.path()); + if (info.isDir()) + { + // We cannot process a directory + error(KIO::ERR_IS_DIRECTORY,url.path()); + return; + } + else if (!info.exists()) + { + // The file does not exist + error(KIO::ERR_DOES_NOT_EXIST,url.path()); + return; + } + else if (!info.isReadable()) + { + // The file is not readable! + error(KIO::ERR_COULD_NOT_READ,url.path()); + return; + } + m_mimeType = KMimeType::findByURL(url)->name(); + kdDebug(7115) << "Guessing MIME Type:" << m_mimeType << endl; + direct=true; // thumbnail: was probably called from Konqueror + } +#endif + + if (m_mimeType.isEmpty()) + { + error(KIO::ERR_INTERNAL, i18n("No MIME Type specified.")); + return; + } + + m_width = metaData("width").toInt(); + m_height = metaData("height").toInt(); + int iconSize = metaData("iconSize").toInt(); + + if (m_width < 0 || m_height < 0) + { + error(KIO::ERR_INTERNAL, i18n("No or invalid size specified.")); + return; + } +#ifdef THUMBNAIL_HACK + else if (!m_width || !m_height) + { + kdDebug(7115) << "Guessing height, width, icon sizre!" << endl; + m_width=128; + m_height=128; + iconSize=128; + } +#endif + + if (!iconSize) + iconSize = KGlobal::iconLoader()->currentSize(KIcon::Desktop); + if (iconSize != m_iconSize) + m_iconDict.clear(); + m_iconSize = iconSize; + + m_iconAlpha = metaData("iconAlpha").toInt(); + if (m_iconAlpha) + m_iconAlpha = (m_iconAlpha << 24) | 0xffffff; + + QImage img; + + KConfigGroup group( KGlobal::config(), "PreviewSettings" ); + + + // ### KFMI + bool kfmiThumb = false; + if (group.readBoolEntry( "UseFileThumbnails", true )) { + KService::Ptr service = + KServiceTypeProfile::preferredService( m_mimeType, "KFilePlugin"); + + if ( service && service->isValid() && /*url.isLocalFile() && */ + service->property("SupportsThumbnail").toBool()) + { + KFileMetaInfo info(url.path(), m_mimeType, KFileMetaInfo::Thumbnail); + if (info.isValid()) + { + KFileMetaInfoItem item = info.item(KFileMimeTypeInfo::Thumbnail); + if (item.isValid() && item.value().type() == QVariant::Image) + { + img = item.value().toImage(); + kdDebug(7115) << "using KFMI for the thumbnail\n"; + kfmiThumb = true; + } + } + } + } + ThumbCreator::Flags flags = ThumbCreator::None; + + if (!kfmiThumb) + { + kdDebug(7115) << "using thumb creator for the thumbnail\n"; + QString plugin = metaData("plugin"); +#ifdef THUMBNAIL_HACK + if (plugin.isEmpty()) + { + KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator"); + QMap<QString, KService::Ptr> mimeMap; + + for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it) + { + QStringList mimeTypes = (*it)->property("MimeTypes").toStringList(); + for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt) + { + if ((*mt)==m_mimeType) + { + plugin=(*it)->library(); + break; + } + } + if (!plugin.isEmpty()) + break; + } + } + kdDebug(7115) << "Guess plugin: " << plugin << endl; +#endif + if (plugin.isEmpty()) + { + error(KIO::ERR_INTERNAL, i18n("No plugin specified.")); + return; + } + + ThumbCreator *creator = m_creators[plugin]; + if (!creator) + { + // Don't use KLibFactory here, this is not a QObject and + // neither is ThumbCreator + KLibrary *library = KLibLoader::self()->library(QFile::encodeName(plugin)); + if (library) + { + newCreator create = (newCreator)library->symbol("new_creator"); + if (create) + creator = create(); + } + if (!creator) + { + error(KIO::ERR_INTERNAL, i18n("Cannot load ThumbCreator %1").arg(plugin)); + return; + } + m_creators.insert(plugin, creator); + } + + if (!creator->create(url.path(), m_width, m_height, img)) + { + error(KIO::ERR_INTERNAL, i18n("Cannot create thumbnail for %1").arg(url.path())); + return; + } + flags = creator->flags(); + } + + if (img.width() > m_width || img.height() > m_height) + { + double imgRatio = (double)img.height() / (double)img.width(); + if (imgRatio > (double)m_height / (double)m_width) + img = img.smoothScale( int(QMAX((double)m_height / imgRatio, 1)), m_height); + else + img = img.smoothScale(m_width, int(QMAX((double)m_width * imgRatio, 1))); + } + +// ### FIXME +#ifndef USE_KINSTANCE + if (flags & ThumbCreator::DrawFrame) + { + QPixmap pix; + pix.convertFromImage(img); + int x2 = pix.width() - 1; + int y2 = pix.height() - 1; + // paint a black rectangle around the "page" + QPainter p; + p.begin( &pix ); + p.setPen( QColor( 48, 48, 48 )); + p.drawLine( x2, 0, x2, y2 ); + p.drawLine( 0, y2, x2, y2 ); + p.setPen( QColor( 215, 215, 215 )); + p.drawLine( 0, 0, x2, 0 ); + p.drawLine( 0, 0, 0, y2 ); + p.end(); + + const QBitmap *mask = pix.mask(); + if ( mask ) // need to update it so we can see the frame + { + QBitmap bitmap( *mask ); + QPainter painter; + painter.begin( &bitmap ); + painter.drawLine( x2, 0, x2, y2 ); + painter.drawLine( 0, y2, x2, y2 ); + painter.drawLine( 0, 0, x2, 0 ); + painter.drawLine( 0, 0, 0, y2 ); + painter.end(); + + pix.setMask( bitmap ); + } + + img = pix.convertToImage(); + } +#endif + + if ((flags & ThumbCreator::BlendIcon) && KGlobal::iconLoader()->alphaBlending(KIcon::Desktop)) + { + // blending the mimetype icon in + QImage icon = getIcon(); + + int x = img.width() - icon.width() - 4; + x = QMAX( x, 0 ); + int y = img.height() - icon.height() - 6; + y = QMAX( y, 0 ); + KImageEffect::blendOnLower( x, y, icon, img ); + } + + if (img.isNull()) + { + error(KIO::ERR_INTERNAL, i18n("Failed to create a thumbnail.")); + return; + } + + const QString shmid = metaData("shmid"); + if (shmid.isEmpty()) + { +#ifdef THUMBNAIL_HACK + if (direct) + { + // If thumbnail was called directly from Konqueror, then the image needs to be raw + //kdDebug(7115) << "RAW IMAGE TO STREAM" << endl; + QBuffer buf; + if (!buf.open(IO_WriteOnly)) + { + error(KIO::ERR_INTERNAL, i18n("Could not write image.")); + return; + } + img.save(&buf,"PNG"); + buf.close(); + data(buf.buffer()); + } + else +#endif + { + QByteArray imgData; + QDataStream stream( imgData, IO_WriteOnly ); + //kdDebug(7115) << "IMAGE TO STREAM" << endl; + stream << img; + data(imgData); + } + } + else + { + QByteArray imgData; + QDataStream stream( imgData, IO_WriteOnly ); + //kdDebug(7115) << "IMAGE TO SHMID" << endl; + void *shmaddr = shmat(shmid.toInt(), 0, 0); + if (shmaddr == (void *)-1) + { + error(KIO::ERR_INTERNAL, i18n("Failed to attach to shared memory segment %1").arg(shmid)); + return; + } + if (img.width() * img.height() > m_width * m_height) + { + error(KIO::ERR_INTERNAL, i18n("Image is too big for the shared memory segment")); + shmdt((char*)shmaddr); + return; + } + if( img.depth() != 32 ) // KIO::PreviewJob and this code below completely + img = img.convertDepth( 32 ); // ignores colortable :-/, so make sure there is none + stream << img.width() << img.height() << img.depth() + << img.hasAlphaBuffer(); + memcpy(shmaddr, img.bits(), img.numBytes()); + shmdt((char*)shmaddr); + data(imgData); + } + finished(); +} + +const QImage& ThumbnailProtocol::getIcon() +{ + QImage* icon = m_iconDict.find(m_mimeType); + if ( !icon ) // generate it! + { + icon = new QImage( KMimeType::mimeType(m_mimeType)->pixmap( KIcon::Desktop, m_iconSize ).convertToImage() ); + icon->setAlphaBuffer( true ); + + int w = icon->width(); + int h = icon->height(); + for ( int y = 0; y < h; y++ ) + { + QRgb *line = (QRgb *) icon->scanLine( y ); + for ( int x = 0; x < w; x++ ) + line[x] &= m_iconAlpha; // transparency + } + + m_iconDict.insert( m_mimeType, icon ); + } + + return *icon; +} + diff --git a/kioslave/thumbnail/thumbnail.h b/kioslave/thumbnail/thumbnail.h new file mode 100644 index 000000000..23118e21c --- /dev/null +++ b/kioslave/thumbnail/thumbnail.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <malte@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef _THUMBNAIL_H_ +#define _THUMBNAIL_H_ + +#include <qdict.h> + +#include <kio/slavebase.h> + +class ThumbCreator; +class QImage; + +class ThumbnailProtocol : public KIO::SlaveBase +{ +public: + ThumbnailProtocol(const QCString &pool, const QCString &app); + virtual ~ThumbnailProtocol(); + + virtual void get(const KURL &url); + +protected: + const QImage& getIcon(); + +private: + QString m_mimeType; + int m_width; + int m_height; + bool m_keepAspectRatio; + int m_iconSize; + int m_iconAlpha; + // Thumbnail creators + QDict<ThumbCreator> m_creators; + // transparent icon cache + QDict<QImage> m_iconDict; +}; + +#endif diff --git a/kioslave/thumbnail/thumbnail.protocol b/kioslave/thumbnail/thumbnail.protocol new file mode 100644 index 000000000..774db3225 --- /dev/null +++ b/kioslave/thumbnail/thumbnail.protocol @@ -0,0 +1,9 @@ +[Protocol] +exec=kio_thumbnail +protocol=thumbnail +input=stream +output=stream +reading=true +source=false +DocPath=kioslave/thumbnail.html +Icon=thumbnail diff --git a/kioslave/trash/DESIGN b/kioslave/trash/DESIGN new file mode 100644 index 000000000..63179da08 --- /dev/null +++ b/kioslave/trash/DESIGN @@ -0,0 +1,53 @@ +DESIGN +====== +kio_trash implements the XDG trash standard currently at http://www.ramendik.ru/docs/trashspec.html + +In case race conditions between the various instances of kio_trash +are a problem, trashimpl could be moved to a kded module, and +kio_trash would use DCOP to talk to it. It's a bit hard to come up +with use cases where the race conditions would matter though. + +BUGS +==== +* Undo of "restore" isn't available. Need to get origPath by metadata I guess. + +TODO +==== +* Clean up konq_popupmenu.cc for Type=Link URL=trash:/ :( +* Also, provide metainfo for trash contents for that desktop link. +=> maybe we need a new mimetype? + Like application/x-trash-desktop, inheriting application/x-desktop. + And a "trash.trashdesktop" filename or so (ouch, migration issues...) + +* Detect removeable media to avoid .Trash-foo on it. How? + +* Trashcan properties (properties for trash:/? hmm. Easier with separate dialog) + - Maximum size for trash can (#18109 suggests a %, but a MB size is easier). + This means to delete the oldest files from the trash automatically. #79553 + +* Err, should we support renaming? :) Difficult to disable... + In fact it's already not disabled in readonly directories (e.g. "/") -> todo + (for F2 and kpropertiesdialog) + +* Deleting oldest files when size is bigger than a certain configurable amount (#79553) + +Bugs closed by kio_trash +======================== +#79826 (3.3 only) +#62848 (configurable trash location) +#78116 (.directory) +#18109 (general one) +#17744 (restore) +#76380 #56821 (trashing on same partition) + +Choice of URL scheme +==================== +We use trash:/trashid-fileid[/relativepath] +This gave problems with CopyJob::startRenameJob which exposed trashid-fileid +to the user as a filename when dropping a file out of the trash. +But this was fixed with the fileNameUsedForCopying=Name setting. + +A previous experiment was trash:/filename[/relativepath]?t=trashid&id=fileid +but this gives problems with going Up (it first removes the query), +with KDirLister (it wouldn't know when to remove the query, to find the URL +of the parent directory). diff --git a/kioslave/trash/Makefile.am b/kioslave/trash/Makefile.am new file mode 100644 index 000000000..7aaa28a9f --- /dev/null +++ b/kioslave/trash/Makefile.am @@ -0,0 +1,31 @@ +INCLUDES = $(all_includes) +METASOURCES = AUTO + +SUBDIRS = . kfile-plugin + +kde_module_LTLIBRARIES = kio_trash.la + +kio_trash_la_SOURCES = kio_trash.cpp +kio_trash_la_LIBADD = libtrashcommon.la $(LIB_KIO) +kio_trash_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined + +bin_PROGRAMS = ktrash +ktrash_SOURCES = ktrash.cpp +ktrash_LDADD = $(LIB_KIO) +ktrash_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +kde_services_DATA = trash.protocol + +noinst_LTLIBRARIES = libtrashcommon.la +libtrashcommon_la_SOURCES = trashimpl.cpp + +check_PROGRAMS = testtrash +testtrash_SOURCES = testtrash.cpp +testtrash_LDADD = libtrashcommon.la $(LIB_KIO) +testtrash_LDFLAGS = $(all_libraries) + +TESTS = testtrash + +messages: + $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/kio_trash.pot + diff --git a/kioslave/trash/kfile-plugin/Makefile.am b/kioslave/trash/kfile-plugin/Makefile.am new file mode 100644 index 000000000..0668553a8 --- /dev/null +++ b/kioslave/trash/kfile-plugin/Makefile.am @@ -0,0 +1,14 @@ +## Makefile.am for trash file meta info plugin + +AM_CPPFLAGS = $(all_includes) + +kde_module_LTLIBRARIES = kfile_trash.la + +kfile_trash_la_SOURCES = kfile_trash.cpp +kfile_trash_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +kfile_trash_la_LIBADD = ../libtrashcommon.la $(LIB_KIO) + +METASOURCES = AUTO + +services_DATA = kfile_trash.desktop kfile_trash_system.desktop +servicesdir = $(kde_servicesdir) diff --git a/kioslave/trash/kfile-plugin/RETURNED_ITEMS b/kioslave/trash/kfile-plugin/RETURNED_ITEMS new file mode 100644 index 000000000..3e34c5b6d --- /dev/null +++ b/kioslave/trash/kfile-plugin/RETURNED_ITEMS @@ -0,0 +1,4 @@ +kfile_trash +=========== +QString OriginalPath +DateTime DateOfDeletion diff --git a/kioslave/trash/kfile-plugin/kfile_trash.cpp b/kioslave/trash/kfile-plugin/kfile_trash.cpp new file mode 100644 index 000000000..baa27143c --- /dev/null +++ b/kioslave/trash/kfile-plugin/kfile_trash.cpp @@ -0,0 +1,93 @@ +/* This file is part of the KDE project + * Copyright (C) 2004 David Faure <faure@kde.org> + * Based on kfile_txt.cpp by Nadeem Hasan <nhasan@kde.org> + * + * 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 version 2. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "kfile_trash.h" + +#include <kgenericfactory.h> +#include <kdebug.h> + +#include <qfile.h> +#include <qstringlist.h> +#include <qdatetime.h> + +typedef KGenericFactory<KTrashPlugin> TrashFactory; + +K_EXPORT_COMPONENT_FACTORY(kfile_trash, TrashFactory("kfile_trash")) + +KTrashPlugin::KTrashPlugin(QObject *parent, const char *name, + const QStringList &args) : KFilePlugin(parent, name, args) +{ + KGlobal::locale()->insertCatalogue( "kio_trash" ); + + kdDebug(7034) << "Trash file meta info plugin\n"; + + makeMimeTypeInfo("trash"); + makeMimeTypeInfo("system"); + + (void)impl.init(); +} + +void KTrashPlugin::makeMimeTypeInfo(const QString& mimeType) +{ + KFileMimeTypeInfo* info = addMimeTypeInfo( mimeType ); + + KFileMimeTypeInfo::GroupInfo* group = + addGroupInfo(info, "General", i18n("General")); + + KFileMimeTypeInfo::ItemInfo* item; + item = addItemInfo(group, "OriginalPath", i18n("Original Path"), QVariant::String); + item = addItemInfo(group, "DateOfDeletion", i18n("Date of Deletion"), QVariant::DateTime); +} + +bool KTrashPlugin::readInfo(KFileMetaInfo& info, uint) +{ + KURL url = info.url(); + + if ( url.protocol()=="system" + && url.path().startsWith("/trash") ) + { + QString path = url.path(); + path.remove(0, 6); + url.setProtocol("trash"); + url.setPath(path); + } + + //kdDebug() << k_funcinfo << info.url() << endl; + if ( url.protocol() != "trash" ) + return false; + + int trashId; + QString fileId; + QString relativePath; + if ( !TrashImpl::parseURL( url, trashId, fileId, relativePath ) ) + return false; + + TrashImpl::TrashedFileInfo trashInfo; + if ( !impl.infoForFile( trashId, fileId, trashInfo ) ) + return false; + + KFileMetaInfoGroup group = appendGroup(info, "General"); + appendItem(group, "OriginalPath", trashInfo.origPath); + appendItem(group, "DateOfDeletion", trashInfo.deletionDate); + + return true; +} + +#include "kfile_trash.moc" diff --git a/kioslave/trash/kfile-plugin/kfile_trash.desktop b/kioslave/trash/kfile-plugin/kfile_trash.desktop new file mode 100644 index 000000000..02d90422a --- /dev/null +++ b/kioslave/trash/kfile-plugin/kfile_trash.desktop @@ -0,0 +1,79 @@ +[Desktop Entry] +Type=Service +Name=Trash File Info +Name[af]=Asblik inligting +Name[ar]=معلومات Ù…ÙÙ„ المهملات +Name[az]=Zibil Faylı MÉ™lumatı +Name[be]=Ð†Ð½Ñ„Ð°Ñ€Ð¼Ð°Ñ†Ñ‹Ñ Ð°Ð± файле Ñметніцы +Name[bg]=Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° кошчето +Name[bn]=আবরà§à¦œà¦¨à¦¾ ফাইল তথà§à¦¯ +Name[br]=Titouroù diwar-benn ar pod-lastez +Name[bs]=Smeće informacije o datoteci +Name[ca]=Informació del fitxer paperera +Name[cs]=Info o koÅ¡i +Name[csb]=Wëdowiédzô ò lopkù w kòszu +Name[da]=Fil-info om affald +Name[de]=Mülleimer-Information +Name[el]=ΠληÏοφοÏίες για τον Κάδο ΑποÏÏιμμάτων +Name[en_GB]=Wastebin File Info +Name[eo]=Rubuja informo +Name[es]=Información de la papelera +Name[et]=Prügikasti failiinfo +Name[eu]=Zakarontziaren infoa +Name[fa]=اطلاعات پروندۀ زباله +Name[fi]=Roskakorin tiedot +Name[fr]=Info Fichier Corbeille +Name[fy]=Jiskefet ynformaasje +Name[gl]=Información do Lixo +Name[he]=מידע ×ודות קובץ ×שפה +Name[hi]=रदà¥à¤¦à¥€ फ़ाइल जानकारी +Name[hr]=Podaci o otpadu +Name[hu]=Szemétkosár-jellemzÅ‘k +Name[is]=Upplýsingar um ruslaskrá +Name[it]=Informazioni file del cestino +Name[ja]=ã”ã¿ç®±æƒ…å ± +Name[ka]=ურნáƒáƒ¨áƒ˜ áƒáƒ სებული ფáƒáƒ˜áƒšáƒ˜áƒ¡ შესáƒáƒ®áƒ”ბ ცნáƒáƒ‘ი +Name[kk]=Өшірілген файл мәліметі +Name[km]=áž–áŸážáŸŒáž˜áž¶áž“​ឯកសារ​សំរាម +Name[ko]=휴지통 íŒŒì¼ ì •ë³´ +Name[lt]=Å iukÅ¡lių bylos informacija +Name[lv]=Atkritumu faila informÄcija +Name[mk]=Инфо. за датотека од Корпата +Name[ms]=Maklumat Fail Sampah +Name[mt]=Skart +Name[nb]=Søppelfilinformasjon +Name[nds]=Affalltünn-Informatschonen +Name[ne]=रदà¥à¤¦à¥€à¤Ÿà¥‹à¤•à¤°à¥€ फाइल सूचना +Name[nl]=Prullenbakinformatie +Name[nn]=Søppelfilinformasjon +Name[pa]=ਰੱਦੀ ਫਾਇਲ ਜਾਣਕਾਰੀ +Name[pl]=Informacja o pliku w koszu +Name[pt]=Informações de Ficheiros no Lixo +Name[pt_BR]=Informações sobre o Arquivo de Lixo +Name[ro]=InformaÈ›ii fiÈ™ier È™ters +Name[ru]=Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ файле в корзине +Name[rw]=Ibisobanuro byo Guta Idosiye +Name[se]=RuskalihttedieÄ‘ut +Name[sk]=Informácie o koÅ¡i +Name[sl]=Informacije o Smeteh +Name[sr]=Информације о фајлу у Ñмећу +Name[sr@Latn]=Informacije o fajlu u smeću +Name[sv]=Information om filer i papperskorgen +Name[ta]=கà¯à®ªà¯à®ªà¯ˆà®¤à¯à®¤à¯Šà®Ÿà¯à®Ÿà®¿ கோபà¯à®ªà¯ தகவல௠+Name[te]=చెతà±à°¤ à°¬à±à°Ÿà±à°Ÿ దసà±à°¤à±à°° వివరాలౠ+Name[tg]=Файли ахборотии ахлотдон +Name[th]=ข้à¸à¸¡à¸¹à¸¥à¹à¸Ÿà¹‰à¸¡à¸–ังขยะ +Name[tr]=Çöp Dosya Bilgisi +Name[tt]=TaÅŸlanÄŸan Birem Turında +Name[uk]=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ файл у Ñмітнику +Name[uz]=Chiqindilar qutisi haqida maʼlumot +Name[uz@cyrillic]=Чиқиндилар қутиÑи ҳақида маълумот +Name[vi]=Thông tin vá» Táºp tin trong Thùng rác +Name[wa]=InformÃ¥cion sol fitchî batch +Name[zh_CN]=å›žæ”¶ç«™æ–‡ä»¶ä¿¡æ¯ +Name[zh_TW]=資æºå›žæ”¶æ¡¶æª”案資訊 +ServiceTypes=KFilePlugin +X-KDE-Library=kfile_trash +X-KDE-Protocol=trash +PreferredGroups=General +PreferredItems=OriginalPath,DateOfDeletion diff --git a/kioslave/trash/kfile-plugin/kfile_trash.h b/kioslave/trash/kfile-plugin/kfile_trash.h new file mode 100644 index 000000000..8316f7402 --- /dev/null +++ b/kioslave/trash/kfile-plugin/kfile_trash.h @@ -0,0 +1,42 @@ +/* This file is part of the KDE project + * Copyright (C) 2004 David Faure <faure@kde.org> + * Based on kfile_txt.h by Nadeem Hasan <nhasan@kde.org> + * + * 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 version 2. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef __KFILE_TRASH_H_ +#define __KFILE_TRASH_H_ + +#include <kfilemetainfo.h> +#include "../trashimpl.h" + +class QStringList; + +class KTrashPlugin: public KFilePlugin +{ + Q_OBJECT + +public: + KTrashPlugin(QObject *parent, const char *name, const QStringList& args); + virtual bool readInfo(KFileMetaInfo& info, uint what); + +private: + void makeMimeTypeInfo(const QString& mimeType); + TrashImpl impl; +}; + +#endif diff --git a/kioslave/trash/kfile-plugin/kfile_trash_system.desktop b/kioslave/trash/kfile-plugin/kfile_trash_system.desktop new file mode 100644 index 000000000..5f57b5af3 --- /dev/null +++ b/kioslave/trash/kfile-plugin/kfile_trash_system.desktop @@ -0,0 +1,79 @@ +[Desktop Entry] +Type=Service +Name=Trash File Info +Name[af]=Asblik inligting +Name[ar]=معلومات Ù…ÙÙ„ المهملات +Name[az]=Zibil Faylı MÉ™lumatı +Name[be]=Ð†Ð½Ñ„Ð°Ñ€Ð¼Ð°Ñ†Ñ‹Ñ Ð°Ð± файле Ñметніцы +Name[bg]=Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° кошчето +Name[bn]=আবরà§à¦œà¦¨à¦¾ ফাইল তথà§à¦¯ +Name[br]=Titouroù diwar-benn ar pod-lastez +Name[bs]=Smeće informacije o datoteci +Name[ca]=Informació del fitxer paperera +Name[cs]=Info o koÅ¡i +Name[csb]=Wëdowiédzô ò lopkù w kòszu +Name[da]=Fil-info om affald +Name[de]=Mülleimer-Information +Name[el]=ΠληÏοφοÏίες για τον Κάδο ΑποÏÏιμμάτων +Name[en_GB]=Wastebin File Info +Name[eo]=Rubuja informo +Name[es]=Información de la papelera +Name[et]=Prügikasti failiinfo +Name[eu]=Zakarontziaren infoa +Name[fa]=اطلاعات پروندۀ زباله +Name[fi]=Roskakorin tiedot +Name[fr]=Info Fichier Corbeille +Name[fy]=Jiskefet ynformaasje +Name[gl]=Información do Lixo +Name[he]=מידע ×ודות קובץ ×שפה +Name[hi]=रदà¥à¤¦à¥€ फ़ाइल जानकारी +Name[hr]=Podaci o otpadu +Name[hu]=Szemétkosár-jellemzÅ‘k +Name[is]=Upplýsingar um ruslaskrá +Name[it]=Informazioni file del cestino +Name[ja]=ã”ã¿ç®±æƒ…å ± +Name[ka]=ურნáƒáƒ¨áƒ˜ áƒáƒ სებული ფáƒáƒ˜áƒšáƒ˜áƒ¡ შესáƒáƒ®áƒ”ბ ცნáƒáƒ‘ი +Name[kk]=Өшірілген файл мәліметі +Name[km]=áž–áŸážáŸŒáž˜áž¶áž“​ឯកសារ​សំរាម +Name[ko]=휴지통 íŒŒì¼ ì •ë³´ +Name[lt]=Å iukÅ¡lių bylos informacija +Name[lv]=Atkritumu faila informÄcija +Name[mk]=Инфо. за датотека од Корпата +Name[ms]=Maklumat Fail Sampah +Name[mt]=Skart +Name[nb]=Søppelfilinformasjon +Name[nds]=Affalltünn-Informatschonen +Name[ne]=रदà¥à¤¦à¥€à¤Ÿà¥‹à¤•à¤°à¥€ फाइल सूचना +Name[nl]=Prullenbakinformatie +Name[nn]=Søppelfilinformasjon +Name[pa]=ਰੱਦੀ ਫਾਇਲ ਜਾਣਕਾਰੀ +Name[pl]=Informacja o pliku w koszu +Name[pt]=Informações de Ficheiros no Lixo +Name[pt_BR]=Informações sobre o Arquivo de Lixo +Name[ro]=InformaÈ›ii fiÈ™ier È™ters +Name[ru]=Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ файле в корзине +Name[rw]=Ibisobanuro byo Guta Idosiye +Name[se]=RuskalihttedieÄ‘ut +Name[sk]=Informácie o koÅ¡i +Name[sl]=Informacije o Smeteh +Name[sr]=Информације о фајлу у Ñмећу +Name[sr@Latn]=Informacije o fajlu u smeću +Name[sv]=Information om filer i papperskorgen +Name[ta]=கà¯à®ªà¯à®ªà¯ˆà®¤à¯à®¤à¯Šà®Ÿà¯à®Ÿà®¿ கோபà¯à®ªà¯ தகவல௠+Name[te]=చెతà±à°¤ à°¬à±à°Ÿà±à°Ÿ దసà±à°¤à±à°° వివరాలౠ+Name[tg]=Файли ахборотии ахлотдон +Name[th]=ข้à¸à¸¡à¸¹à¸¥à¹à¸Ÿà¹‰à¸¡à¸–ังขยะ +Name[tr]=Çöp Dosya Bilgisi +Name[tt]=TaÅŸlanÄŸan Birem Turında +Name[uk]=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ файл у Ñмітнику +Name[uz]=Chiqindilar qutisi haqida maʼlumot +Name[uz@cyrillic]=Чиқиндилар қутиÑи ҳақида маълумот +Name[vi]=Thông tin vá» Táºp tin trong Thùng rác +Name[wa]=InformÃ¥cion sol fitchî batch +Name[zh_CN]=å›žæ”¶ç«™æ–‡ä»¶ä¿¡æ¯ +Name[zh_TW]=資æºå›žæ”¶æ¡¶æª”案資訊 +ServiceTypes=KFilePlugin +X-KDE-Library=kfile_trash +X-KDE-Protocol=system +PreferredGroups=General +PreferredItems=OriginalPath,DateOfDeletion diff --git a/kioslave/trash/kio_trash.cpp b/kioslave/trash/kio_trash.cpp new file mode 100644 index 000000000..7912cbb7c --- /dev/null +++ b/kioslave/trash/kio_trash.cpp @@ -0,0 +1,596 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "kio_trash.h" +#include <kio/job.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <klargefile.h> +#include <kcmdlineargs.h> +#include <kmimetype.h> +#include <kprocess.h> + +#include <dcopclient.h> +#include <qdatastream.h> +#include <qtextstream.h> +#include <qfile.h> +#include <qeventloop.h> + +#include <time.h> +#include <pwd.h> +#include <grp.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> + +static const KCmdLineOptions options[] = +{ + { "+protocol", I18N_NOOP( "Protocol name" ), 0 }, + { "+pool", I18N_NOOP( "Socket name" ), 0 }, + { "+app", I18N_NOOP( "Socket name" ), 0 }, + KCmdLineLastOption +}; + +extern "C" { + int KDE_EXPORT kdemain( int argc, char **argv ) + { + //KInstance instance( "kio_trash" ); + // KApplication is necessary to use kio_file + putenv(strdup("SESSION_MANAGER=")); + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc, argv, "kio_trash", 0, 0, 0, 0); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app( false, false ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + TrashProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + +#define INIT_IMPL \ + if ( !impl.init() ) { \ + error( impl.lastErrorCode(), impl.lastErrorMessage() ); \ + return; \ + } + +TrashProtocol::TrashProtocol( const QCString& protocol, const QCString &pool, const QCString &app) + : SlaveBase(protocol, pool, app ) +{ + struct passwd *user = getpwuid( getuid() ); + if ( user ) + m_userName = QString::fromLatin1(user->pw_name); + struct group *grp = getgrgid( getgid() ); + if ( grp ) + m_groupName = QString::fromLatin1(grp->gr_name); +} + +TrashProtocol::~TrashProtocol() +{ +} + +void TrashProtocol::restore( const KURL& trashURL ) +{ + int trashId; + QString fileId, relativePath; + bool ok = TrashImpl::parseURL( trashURL, trashId, fileId, relativePath ); + if ( !ok ) { + error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( trashURL.prettyURL() ) ); + return; + } + TrashedFileInfo info; + ok = impl.infoForFile( trashId, fileId, info ); + if ( !ok ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + return; + } + KURL dest; + dest.setPath( info.origPath ); + if ( !relativePath.isEmpty() ) + dest.addPath( relativePath ); + + // Check that the destination directory exists, to improve the error code in case it doesn't. + const QString destDir = dest.directory(); + KDE_struct_stat buff; + if ( KDE_lstat( QFile::encodeName( destDir ), &buff ) == -1 ) { + error( KIO::ERR_SLAVE_DEFINED, + i18n( "The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. " + "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it." ).arg( destDir ) ); + return; + } + + copyOrMove( trashURL, dest, false /*overwrite*/, Move ); +} + +void TrashProtocol::rename( const KURL &oldURL, const KURL &newURL, bool overwrite ) +{ + INIT_IMPL; + + kdDebug()<<"TrashProtocol::rename(): old="<<oldURL<<" new="<<newURL<<" overwrite=" << overwrite<<endl; + + if ( oldURL.protocol() == "trash" && newURL.protocol() == "trash" ) { + error( KIO::ERR_CANNOT_RENAME, oldURL.prettyURL() ); + return; + } + + copyOrMove( oldURL, newURL, overwrite, Move ); +} + +void TrashProtocol::copy( const KURL &src, const KURL &dest, int /*permissions*/, bool overwrite ) +{ + INIT_IMPL; + + kdDebug()<<"TrashProtocol::copy(): " << src << " " << dest << endl; + + if ( src.protocol() == "trash" && dest.protocol() == "trash" ) { + error( KIO::ERR_UNSUPPORTED_ACTION, i18n( "This file is already in the trash bin." ) ); + return; + } + + copyOrMove( src, dest, overwrite, Copy ); +} + +void TrashProtocol::copyOrMove( const KURL &src, const KURL &dest, bool overwrite, CopyOrMove action ) +{ + if ( src.protocol() == "trash" && dest.isLocalFile() ) { + // Extracting (e.g. via dnd). Ignore original location stored in info file. + int trashId; + QString fileId, relativePath; + bool ok = TrashImpl::parseURL( src, trashId, fileId, relativePath ); + if ( !ok ) { + error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( src.prettyURL() ) ); + return; + } + const QString destPath = dest.path(); + if ( QFile::exists( destPath ) ) { + if ( overwrite ) { + ok = QFile::remove( destPath ); + Q_ASSERT( ok ); // ### TODO + } else { + error( KIO::ERR_FILE_ALREADY_EXIST, destPath ); + return; + } + } + + if ( action == Move ) { + kdDebug() << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")" << endl; + ok = impl.moveFromTrash( destPath, trashId, fileId, relativePath ); + } else { // Copy + kdDebug() << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")" << endl; + ok = impl.copyFromTrash( destPath, trashId, fileId, relativePath ); + } + if ( !ok ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + } else { + if ( action == Move && relativePath.isEmpty() ) + (void)impl.deleteInfo( trashId, fileId ); + finished(); + } + return; + } else if ( src.isLocalFile() && dest.protocol() == "trash" ) { + QString dir = dest.directory(); + //kdDebug() << "trashing a file to " << dir << endl; + // Trashing a file + // We detect the case where this isn't normal trashing, but + // e.g. if kwrite tries to save (moving tempfile over destination) + if ( dir.length() <= 1 && src.fileName() == dest.fileName() ) // new toplevel entry + { + const QString srcPath = src.path(); + // In theory we should use TrashImpl::parseURL to give the right filename to createInfo, + // in case the trash URL didn't contain the same filename as srcPath. + // But this can only happen with copyAs/moveAs, not available in the GUI + // for the trash (New/... or Rename from iconview/listview). + int trashId; + QString fileId; + if ( !impl.createInfo( srcPath, trashId, fileId ) ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + } else { + bool ok; + if ( action == Move ) { + kdDebug() << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")" << endl; + ok = impl.moveToTrash( srcPath, trashId, fileId ); + } else { // Copy + kdDebug() << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")" << endl; + ok = impl.copyToTrash( srcPath, trashId, fileId ); + } + if ( !ok ) { + (void)impl.deleteInfo( trashId, fileId ); + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + } else { + // Inform caller of the final URL. Used by konq_undo. + const KURL url = impl.makeURL( trashId, fileId, QString::null ); + setMetaData( "trashURL-" + srcPath, url.url() ); + finished(); + } + } + return; + } else { + kdDebug() << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory" << endl; + // It's not allowed to add a file to an existing trash directory. + error( KIO::ERR_ACCESS_DENIED, dest.prettyURL() ); + return; + } + } else + error( KIO::ERR_UNSUPPORTED_ACTION, "should never happen" ); +} + +static void addAtom(KIO::UDSEntry& entry, unsigned int ID, long long l, const QString& s = QString::null) +{ + KIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + +void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry& entry) +{ + entry.clear(); + addAtom(entry, KIO::UDS_NAME, 0, "."); + addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, KIO::UDS_ACCESS, 0700); + addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, KIO::UDS_USER, 0, m_userName); + addAtom(entry, KIO::UDS_GROUP, 0, m_groupName); +} + +void TrashProtocol::stat(const KURL& url) +{ + INIT_IMPL; + const QString path = url.path(); + if( path.isEmpty() || path == "/" ) { + // The root is "virtual" - it's not a single physical directory + KIO::UDSEntry entry; + createTopLevelDirEntry( entry ); + statEntry( entry ); + finished(); + } else { + int trashId; + QString fileId, relativePath; + + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + + if ( !ok ) { + // ######## do we still need this? + kdDebug() << k_funcinfo << url << " looks fishy, returning does-not-exist" << endl; + // A URL like trash:/file simply means that CopyJob is trying to see if + // the destination exists already (it made up the URL by itself). + error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + //error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + + const QString filePath = impl.physicalPath( trashId, fileId, relativePath ); + if ( filePath.isEmpty() ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + return; + } + + QString fileName = filePath.section('/', -1, -1, QString::SectionSkipEmpty); + + QString fileURL = QString::null; + if ( url.path().length() > 1 ) { + fileURL = url.url(); + } + + KIO::UDSEntry entry; + TrashedFileInfo info; + ok = impl.infoForFile( trashId, fileId, info ); + if ( ok ) + ok = createUDSEntry( filePath, fileName, fileURL, entry, info ); + + if ( !ok ) { + error( KIO::ERR_COULD_NOT_STAT, url.prettyURL() ); + } + + statEntry( entry ); + finished(); + } +} + +void TrashProtocol::del( const KURL &url, bool /*isfile*/ ) +{ + int trashId; + QString fileId, relativePath; + + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + if ( !ok ) { + error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + + ok = relativePath.isEmpty(); + if ( !ok ) { + error( KIO::ERR_ACCESS_DENIED, url.prettyURL() ); + return; + } + + ok = impl.del(trashId, fileId); + if ( !ok ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + return; + } + + finished(); +} + +void TrashProtocol::listDir(const KURL& url) +{ + INIT_IMPL; + kdDebug() << "listdir: " << url << endl; + if ( url.path().length() <= 1 ) { + listRoot(); + return; + } + int trashId; + QString fileId; + QString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + if ( !ok ) { + error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + //was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath ); + + // Get info for deleted directory - the date of deletion and orig path will be used + // for all the items in it, and we need the physicalPath. + TrashedFileInfo info; + ok = impl.infoForFile( trashId, fileId, info ); + if ( !ok || info.physicalPath.isEmpty() ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + return; + } + if ( !relativePath.isEmpty() ) { + info.physicalPath += "/"; + info.physicalPath += relativePath; + } + + // List subdir. Can't use kio_file here since we provide our own info... + kdDebug() << k_funcinfo << "listing " << info.physicalPath << endl; + QStrList entryNames = impl.listDir( info.physicalPath ); + totalSize( entryNames.count() ); + KIO::UDSEntry entry; + QStrListIterator entryIt( entryNames ); + for (; entryIt.current(); ++entryIt) { + QString fileName = QFile::decodeName( entryIt.current() ); + if ( fileName == ".." ) + continue; + const QString filePath = info.physicalPath + "/" + fileName; + // shouldn't be necessary + //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + "/" + fileName ); + entry.clear(); + TrashedFileInfo infoForItem( info ); + infoForItem.origPath += '/'; + infoForItem.origPath += fileName; + if ( ok && createUDSEntry( filePath, fileName, QString::null /*url*/, entry, infoForItem ) ) { + listEntry( entry, false ); + } + } + entry.clear(); + listEntry( entry, true ); + finished(); +} + +bool TrashProtocol::createUDSEntry( const QString& physicalPath, const QString& fileName, const QString& url, KIO::UDSEntry& entry, const TrashedFileInfo& info ) +{ + QCString physicalPath_c = QFile::encodeName( physicalPath ); + KDE_struct_stat buff; + if ( KDE_lstat( physicalPath_c, &buff ) == -1 ) { + kdWarning() << "couldn't stat " << physicalPath << endl; + return false; + } + if (S_ISLNK(buff.st_mode)) { + char buffer2[ 1000 ]; + int n = readlink( physicalPath_c, buffer2, 1000 ); + if ( n != -1 ) { + buffer2[ n ] = 0; + } + + addAtom( entry, KIO::UDS_LINK_DEST, 0, QFile::decodeName( buffer2 ) ); + // Follow symlink + // That makes sense in kio_file, but not in the trash, especially for the size + // #136876 +#if 0 + if ( KDE_stat( physicalPath_c, &buff ) == -1 ) { + // It is a link pointing to nowhere + buff.st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; + buff.st_mtime = 0; + buff.st_atime = 0; + buff.st_size = 0; + } +#endif + } + mode_t type = buff.st_mode & S_IFMT; // extract file type + mode_t access = buff.st_mode & 07777; // extract permissions + access &= 07555; // make it readonly, since it's in the trashcan + addAtom( entry, KIO::UDS_NAME, 0, fileName ); + addAtom( entry, KIO::UDS_FILE_TYPE, type ); + if ( !url.isEmpty() ) + addAtom( entry, KIO::UDS_URL, 0, url ); + + KMimeType::Ptr mt = KMimeType::findByPath( physicalPath, buff.st_mode ); + addAtom( entry, KIO::UDS_MIME_TYPE, 0, mt->name() ); + addAtom( entry, KIO::UDS_ACCESS, access ); + addAtom( entry, KIO::UDS_SIZE, buff.st_size ); + addAtom( entry, KIO::UDS_USER, 0, m_userName ); // assumption + addAtom( entry, KIO::UDS_GROUP, 0, m_groupName ); // assumption + addAtom( entry, KIO::UDS_MODIFICATION_TIME, buff.st_mtime ); + addAtom( entry, KIO::UDS_ACCESS_TIME, buff.st_atime ); // ## or use it for deletion time? + addAtom( entry, KIO::UDS_EXTRA, 0, info.origPath ); + addAtom( entry, KIO::UDS_EXTRA, 0, info.deletionDate.toString( Qt::ISODate ) ); + return true; +} + +void TrashProtocol::listRoot() +{ + INIT_IMPL; + const TrashedFileInfoList lst = impl.list(); + totalSize( lst.count() ); + KIO::UDSEntry entry; + createTopLevelDirEntry( entry ); + listEntry( entry, false ); + for ( TrashedFileInfoList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { + const KURL url = TrashImpl::makeURL( (*it).trashId, (*it).fileId, QString::null ); + KURL origURL; + origURL.setPath( (*it).origPath ); + entry.clear(); + if ( createUDSEntry( (*it).physicalPath, origURL.fileName(), url.url(), entry, *it ) ) + listEntry( entry, false ); + } + entry.clear(); + listEntry( entry, true ); + finished(); +} + +void TrashProtocol::special( const QByteArray & data ) +{ + INIT_IMPL; + QDataStream stream( data, IO_ReadOnly ); + int cmd; + stream >> cmd; + + switch (cmd) { + case 1: + if ( impl.emptyTrash() ) + finished(); + else + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + break; + case 2: + impl.migrateOldTrash(); + finished(); + break; + case 3: + { + KURL url; + stream >> url; + restore( url ); + break; + } + default: + kdWarning(7116) << "Unknown command in special(): " << cmd << endl; + error( KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd) ); + break; + } +} + +void TrashProtocol::put( const KURL& url, int /*permissions*/, bool /*overwrite*/, bool /*resume*/ ) +{ + INIT_IMPL; + kdDebug() << "put: " << url << endl; + // create deleted file. We need to get the mtime and original location from metadata... + // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed... + error( KIO::ERR_ACCESS_DENIED, url.prettyURL() ); +} + +void TrashProtocol::get( const KURL& url ) +{ + INIT_IMPL; + kdDebug() << "get() : " << url << endl; + if ( !url.isValid() ) { + kdDebug() << kdBacktrace() << endl; + error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.url() ) ); + return; + } + if ( url.path().length() <= 1 ) { + error( KIO::ERR_IS_DIRECTORY, url.prettyURL() ); + return; + } + int trashId; + QString fileId; + QString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + if ( !ok ) { + error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath ); + if ( physicalPath.isEmpty() ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + return; + } + + // Usually we run jobs in TrashImpl (for e.g. future kdedmodule) + // But for this one we wouldn't use DCOP for every bit of data... + KURL fileURL; + fileURL.setPath( physicalPath ); + KIO::Job* job = KIO::get( fileURL ); + connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ), + this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) ); + connect( job, SIGNAL( mimetype( KIO::Job*, const QString& ) ), + this, SLOT( slotMimetype( KIO::Job*, const QString& ) ) ); + connect( job, SIGNAL( result(KIO::Job *) ), + this, SLOT( jobFinished(KIO::Job *) ) ); + qApp->eventLoop()->enterLoop(); +} + +void TrashProtocol::slotData( KIO::Job*, const QByteArray&arr ) +{ + data( arr ); +} + +void TrashProtocol::slotMimetype( KIO::Job*, const QString& mt ) +{ + mimeType( mt ); +} + +void TrashProtocol::jobFinished( KIO::Job* job ) +{ + if ( job->error() ) + error( job->error(), job->errorText() ); + else + finished(); + qApp->eventLoop()->exitLoop(); +} + +#if 0 +void TrashProtocol::mkdir( const KURL& url, int /*permissions*/ ) +{ + INIT_IMPL; + // create info about deleted dir + // ############ Problem: we don't know the original path. + // Let's try to avoid this case (we should get to copy() instead, for local files) + kdDebug() << "mkdir: " << url << endl; + QString dir = url.directory(); + + if ( dir.length() <= 1 ) // new toplevel entry + { + // ## we should use TrashImpl::parseURL to give the right filename to createInfo + int trashId; + QString fileId; + if ( !impl.createInfo( url.path(), trashId, fileId ) ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + } else { + if ( !impl.mkdir( trashId, fileId, permissions ) ) { + (void)impl.deleteInfo( trashId, fileId ); + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + } else + finished(); + } + } else { + // Well it's not allowed to add a directory to an existing deleted directory. + error( KIO::ERR_ACCESS_DENIED, url.prettyURL() ); + } +} +#endif + +#include "kio_trash.moc" diff --git a/kioslave/trash/kio_trash.h b/kioslave/trash/kio_trash.h new file mode 100644 index 000000000..726db431e --- /dev/null +++ b/kioslave/trash/kio_trash.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef KIO_TRASH_H +#define KIO_TRASH_H + +#include <kio/slavebase.h> +#include "trashimpl.h" +namespace KIO { class Job; } + +typedef TrashImpl::TrashedFileInfo TrashedFileInfo; +typedef TrashImpl::TrashedFileInfoList TrashedFileInfoList; + +class TrashProtocol : public QObject, public KIO::SlaveBase +{ + Q_OBJECT +public: + TrashProtocol( const QCString& protocol, const QCString &pool, const QCString &app); + virtual ~TrashProtocol(); + virtual void stat(const KURL& url); + virtual void listDir(const KURL& url); + virtual void get( const KURL& url ); + virtual void put( const KURL& url, int , bool overwrite, bool ); + virtual void rename( const KURL &, const KURL &, bool ); + virtual void copy( const KURL &src, const KURL &dest, int permissions, bool overwrite ); + // TODO (maybe) chmod( const KURL& url, int permissions ); + virtual void del( const KURL &url, bool isfile ); + /** + * Special actions: (first int in the byte array) + * 1 : empty trash + * 2 : migrate old (pre-kde-3.4) trash contents + * 3 : restore a file to its original location. Args: KURL trashURL. + */ + virtual void special( const QByteArray & data ); + +private slots: + void slotData( KIO::Job*, const QByteArray& ); + void slotMimetype( KIO::Job*, const QString& ); + void jobFinished( KIO::Job* job ); + +private: + typedef enum CopyOrMove { Copy, Move }; + void copyOrMove( const KURL& src, const KURL& dest, bool overwrite, CopyOrMove action ); + void createTopLevelDirEntry(KIO::UDSEntry& entry); + bool createUDSEntry( const QString& physicalPath, const QString& fileName, const QString& url, + KIO::UDSEntry& entry, const TrashedFileInfo& info ); + void listRoot(); + void restore( const KURL& trashURL ); + + TrashImpl impl; + QString m_userName; + QString m_groupName; +}; + +#endif diff --git a/kioslave/trash/ktrash.cpp b/kioslave/trash/ktrash.cpp new file mode 100644 index 000000000..4fa5ccd27 --- /dev/null +++ b/kioslave/trash/ktrash.cpp @@ -0,0 +1,102 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 <kapplication.h> +#include <kio/netaccess.h> +#include <kio/job.h> +#include <kcmdlineargs.h> +#include <klocale.h> +#include <kdirnotify_stub.h> +#include <kdebug.h> + +static KCmdLineOptions options[] = +{ + { "empty", I18N_NOOP( "Empty the contents of the trash" ), 0 }, + //{ "migrate", I18N_NOOP( "Migrate contents of old trash" ), 0 }, + { "restore <file>", I18N_NOOP( "Restore a trashed file to its original location" ), 0 }, + // This hack is for the servicemenu on trash.desktop which uses Exec=ktrash -empty. %f is implied... + { "+[ignored]", I18N_NOOP( "Ignored" ), 0 }, + KCmdLineLastOption +}; + +int main(int argc, char *argv[]) +{ + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init( argc, argv, "ktrash", + I18N_NOOP( "ktrash" ), + I18N_NOOP( "Helper program to handle the KDE trash can\n" + "Note: to move files to the trash, do not use ktrash, but \"kfmclient move 'url' trash:/\"" ), + KDE_VERSION_STRING ); + KCmdLineArgs::addCmdLineOptions( options ); + KApplication app; + + KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + if ( args->isSet( "empty" ) ) { + // We use a kio job instead of linking to TrashImpl, for a smaller binary + // (and the possibility of a central service at some point) + QByteArray packedArgs; + QDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)1; + KIO::Job* job = KIO::special( "trash:/", packedArgs ); + (void)KIO::NetAccess::synchronousRun( job, 0 ); + + // Update konq windows opened on trash:/ + KDirNotify_stub allDirNotify("*", "KDirNotify*"); + allDirNotify.FilesAdded( "trash:/" ); // yeah, files were removed, but we don't know which ones... + return 0; + } + +#if 0 + // This is only for testing. KDesktop handles it automatically. + if ( args->isSet( "migrate" ) ) { + QByteArray packedArgs; + QDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)2; + KIO::Job* job = KIO::special( "trash:/", packedArgs ); + (void)KIO::NetAccess::synchronousRun( job, 0 ); + return 0; + } +#endif + + QCString restoreArg = args->getOption( "restore" ); + if ( !restoreArg.isEmpty() ) { + + if (restoreArg.find("system:/trash")==0) { + restoreArg.remove(0, 13); + restoreArg.prepend("trash:"); + } + + KURL trashURL( restoreArg ); + if ( !trashURL.isValid() || trashURL.protocol() != "trash" ) { + kdError() << "Invalid URL for restoring a trashed file:" << trashURL << endl; + return 1; + } + + QByteArray packedArgs; + QDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << trashURL; + KIO::Job* job = KIO::special( trashURL, packedArgs ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + if ( !ok ) + kdError() << KIO::NetAccess::lastErrorString() << endl; + return 0; + } + + return 0; +} diff --git a/kioslave/trash/testtrash.cpp b/kioslave/trash/testtrash.cpp new file mode 100644 index 000000000..4557c5031 --- /dev/null +++ b/kioslave/trash/testtrash.cpp @@ -0,0 +1,1198 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +// Get those asserts to work +#undef NDEBUG +#undef NO_DEBUG + +#include "kio_trash.h" +#include "testtrash.h" + +#include <config.h> + +#include <kurl.h> +#include <klocale.h> +#include <kapplication.h> +#include <kio/netaccess.h> +#include <kio/job.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#include <qdir.h> +#include <qfileinfo.h> +#include <qvaluevector.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> +#include <kfileitem.h> +#include <kstandarddirs.h> + +static bool check(const QString& txt, QString a, QString b) +{ + if (a.isEmpty()) + a = QString::null; + if (b.isEmpty()) + b = QString::null; + if (a == b) { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl; + } + else { + kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl; + exit(1); + } + return true; +} + +// There are two ways to test encoding things: +// * with utf8 filenames +// * with latin1 filenames +// +//#define UTF8TEST 1 + +int main(int argc, char *argv[]) +{ + // Ensure a known QFile::encodeName behavior for trashUtf8FileFromHome + // However this assume your $HOME doesn't use characters from other locales... + setenv( "LC_ALL", "en_GB.ISO-8859-1", 1 ); +#ifdef UTF8TEST + setenv( "KDE_UTF8_FILENAMES", "true", 1 ); +#else + unsetenv( "KDE_UTF8_FILENAMES" ); +#endif + + // Use another directory than the real one, just to keep things clean + setenv( "XDG_DATA_HOME", QFile::encodeName( QDir::homeDirPath() + "/.local-testtrash" ), true ); + setenv( "KDE_FORK_SLAVES", "yes", true ); + + KApplication::disableAutoDcopRegistration(); + KCmdLineArgs::init(argc,argv,"testtrash", 0, 0, 0, 0); + KApplication app; + + TestTrash test; + test.setup(); + test.runAll(); + kdDebug() << "All tests OK." << endl; + return 0; // success. The exit(1) in check() is what happens in case of failure. +} + +QString TestTrash::homeTmpDir() const +{ + return QDir::homeDirPath() + "/.kde/testtrash/"; +} + +QString TestTrash::readOnlyDirPath() const +{ + return homeTmpDir() + QString( "readonly" ); +} + +QString TestTrash::otherTmpDir() const +{ + // This one needs to be on another partition + return "/tmp/testtrash/"; +} + +QString TestTrash::utf8FileName() const +{ + return QString( "test" ) + QChar( 0x2153 ); // "1/3" character, not part of latin1 +} + +QString TestTrash::umlautFileName() const +{ + return QString( "umlaut" ) + QChar( 0xEB ); +} + +static void removeFile( const QString& trashDir, const QString& fileName ) +{ + QDir dir; + dir.remove( trashDir + fileName ); + assert( !QDir( trashDir + fileName ).exists() ); +} + +static void removeDir( const QString& trashDir, const QString& dirName ) +{ + QDir dir; + dir.rmdir( trashDir + dirName ); + assert( !QDir( trashDir + dirName ).exists() ); +} + +void TestTrash::setup() +{ + m_trashDir = KGlobal::dirs()->localxdgdatadir() + "Trash"; + kdDebug() << "setup: using trash directory " << m_trashDir << endl; + + // Look for another writable partition than $HOME (not mandatory) + TrashImpl impl; + impl.init(); + + TrashImpl::TrashDirMap trashDirs = impl.trashDirectories(); + TrashImpl::TrashDirMap topDirs = impl.topDirectories(); + bool foundTrashDir = false; + m_otherPartitionId = 0; + m_tmpIsWritablePartition = false; + m_tmpTrashId = -1; + QValueVector<int> writableTopDirs; + for ( TrashImpl::TrashDirMap::ConstIterator it = trashDirs.begin(); it != trashDirs.end() ; ++it ) { + if ( it.key() == 0 ) { + assert( it.data() == m_trashDir ); + assert( topDirs.find( 0 ) == topDirs.end() ); + foundTrashDir = true; + } else { + assert( topDirs.find( it.key() ) != topDirs.end() ); + const QString topdir = topDirs[it.key()]; + if ( QFileInfo( topdir ).isWritable() ) { + writableTopDirs.append( it.key() ); + if ( topdir == "/tmp/" ) { + m_tmpIsWritablePartition = true; + m_tmpTrashId = it.key(); + kdDebug() << "/tmp is on its own partition (trashid=" << m_tmpTrashId << "), some tests will be skipped" << endl; + removeFile( it.data(), "/info/fileFromOther.trashinfo" ); + removeFile( it.data(), "/files/fileFromOther" ); + removeFile( it.data(), "/info/symlinkFromOther.trashinfo" ); + removeFile( it.data(), "/files/symlinkFromOther" ); + removeFile( it.data(), "/info/trashDirFromOther.trashinfo" ); + removeFile( it.data(), "/files/trashDirFromOther/testfile" ); + removeDir( it.data(), "/files/trashDirFromOther" ); + } + } + } + } + for ( QValueVector<int>::const_iterator it = writableTopDirs.begin(); it != writableTopDirs.end(); ++it ) { + const QString topdir = topDirs[ *it ]; + const QString trashdir = trashDirs[ *it ]; + assert( !topdir.isEmpty() ); + assert( !trashDirs.isEmpty() ); + if ( topdir != "/tmp/" || // we'd prefer not to use /tmp here, to separate the tests + ( writableTopDirs.count() > 1 ) ) // but well, if we have no choice, take it + { + m_otherPartitionTopDir = topdir; + m_otherPartitionTrashDir = trashdir; + m_otherPartitionId = *it; + kdDebug() << "OK, found another writable partition: topDir=" << m_otherPartitionTopDir + << " trashDir=" << m_otherPartitionTrashDir << " id=" << m_otherPartitionId << endl; + break; + } + } + // Check that m_trashDir got listed + assert( foundTrashDir ); + if ( m_otherPartitionTrashDir.isEmpty() ) + kdWarning() << "No writable partition other than $HOME found, some tests will be skipped" << endl; + + // Start with a clean base dir + if ( QFileInfo( homeTmpDir() ).exists() ) { + bool ok = KIO::NetAccess::del( homeTmpDir(), 0 ); + if ( !ok ) + kdFatal() << "Couldn't delete " << homeTmpDir() << endl; + } + if ( QFileInfo( otherTmpDir() ).exists() ) { + bool ok = KIO::NetAccess::del( otherTmpDir(), 0 ); + if ( !ok ) + kdFatal() << "Couldn't delete " << otherTmpDir() << endl; + } + QDir dir; // TT: why not a static method? + bool ok = dir.mkdir( homeTmpDir() ); + if ( !ok ) + kdFatal() << "Couldn't create " << homeTmpDir() << endl; + ok = dir.mkdir( otherTmpDir() ); + if ( !ok ) + kdFatal() << "Couldn't create " << otherTmpDir() << endl; + cleanTrash(); +} + + +void TestTrash::cleanTrash() +{ + kdDebug() << k_funcinfo << endl; + // Start with a relatively clean trash too + removeFile( m_trashDir, "/info/fileFromHome.trashinfo" ); + removeFile( m_trashDir, "/files/fileFromHome" ); + removeFile( m_trashDir, "/info/fileFromHome_1.trashinfo" ); + removeFile( m_trashDir, "/files/fileFromHome_1" ); + removeFile( m_trashDir, "/info/file%2f.trashinfo" ); + removeFile( m_trashDir, "/files/file%2f" ); + removeFile( m_trashDir, "/info/" + utf8FileName() + ".trashinfo" ); + removeFile( m_trashDir, "/files/" + utf8FileName() ); + removeFile( m_trashDir, "/info/" + umlautFileName() + ".trashinfo" ); + removeFile( m_trashDir, "/files/" + umlautFileName() ); + removeFile( m_trashDir, "/info/fileFromOther.trashinfo" ); + removeFile( m_trashDir, "/files/fileFromOther" ); + removeFile( m_trashDir, "/info/symlinkFromHome.trashinfo" ); + removeFile( m_trashDir, "/files/symlinkFromHome" ); + removeFile( m_trashDir, "/info/symlinkFromOther.trashinfo" ); + removeFile( m_trashDir, "/files/symlinkFromOther" ); + removeFile( m_trashDir, "/info/brokenSymlinkFromHome.trashinfo" ); + removeFile( m_trashDir, "/files/brokenSymlinkFromHome" ); + removeFile( m_trashDir, "/info/trashDirFromHome.trashinfo" ); + removeFile( m_trashDir, "/files/trashDirFromHome/testfile" ); + removeFile( m_trashDir, "/info/readonly.trashinfo" ); + removeDir( m_trashDir, "/files/trashDirFromHome" ); + removeFile( m_trashDir, "/info/trashDirFromHome_1.trashinfo" ); + removeFile( m_trashDir, "/files/trashDirFromHome_1/testfile" ); + removeDir( m_trashDir, "/files/trashDirFromHome_1" ); + removeFile( m_trashDir, "/info/trashDirFromOther.trashinfo" ); + removeFile( m_trashDir, "/files/trashDirFromOther/testfile" ); + removeDir( m_trashDir, "/files/trashDirFromOther" ); + KIO::NetAccess::del( m_trashDir + "/files/readonly", 0 ); + // for trashDirectoryOwnedByRoot + KIO::NetAccess::del( m_trashDir + "/files/cups", 0 ); + KIO::NetAccess::del( m_trashDir + "/files/boot", 0 ); + KIO::NetAccess::del( m_trashDir + "/files/etc", 0 ); + + //system( "find ~/.local-testtrash/share/Trash" ); +} + +void TestTrash::runAll() +{ + urlTestFile(); + urlTestDirectory(); + urlTestSubDirectory(); + + trashFileFromHome(); + trashPercentFileFromHome(); +#ifdef UTF8TEST + trashUtf8FileFromHome(); +#endif + trashUmlautFileFromHome(); + trashReadOnlyDirFromHome(); + testTrashNotEmpty(); + trashFileFromOther(); + trashFileIntoOtherPartition(); + trashFileOwnedByRoot(); + trashSymlinkFromHome(); + trashSymlinkFromOther(); + trashBrokenSymlinkFromHome(); + trashDirectoryFromHome(); + trashDirectoryFromOther(); + trashDirectoryOwnedByRoot(); + + tryRenameInsideTrash(); + + statRoot(); + statFileInRoot(); + statDirectoryInRoot(); + statSymlinkInRoot(); + statFileInDirectory(); + + copyFileFromTrash(); + // To test case of already-existing destination, uncomment this. + // This brings up the "rename" dialog though, so it can't be fully automated + //copyFileFromTrash(); + copyFileInDirectoryFromTrash(); + copyDirectoryFromTrash(); + copySymlinkFromTrash(); + + moveFileFromTrash(); + moveFileInDirectoryFromTrash(); + moveDirectoryFromTrash(); + moveSymlinkFromTrash(); + + listRootDir(); + listRecursiveRootDir(); + listSubDir(); + + delRootFile(); + delFileInDirectory(); + delDirectory(); + + getFile(); + restoreFile(); + restoreFileFromSubDir(); + restoreFileToDeletedDirectory(); + + emptyTrash(); + + // TODO: test + // - trash migration + // - the actual updating of the trash icon on the desktop +} + +void TestTrash::urlTestFile() +{ + const KURL url = TrashImpl::makeURL( 1, "fileId", QString::null ); + check( "makeURL for a file", url.url(), "trash:/1-fileId" ); + + int trashId; + QString fileId; + QString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + assert( ok ); + check( "parseURL: trashId", QString::number( trashId ), "1" ); + check( "parseURL: fileId", fileId, "fileId" ); + check( "parseURL: relativePath", relativePath, QString::null ); +} + +void TestTrash::urlTestDirectory() +{ + const KURL url = TrashImpl::makeURL( 1, "fileId", "subfile" ); + check( "makeURL", url.url(), "trash:/1-fileId/subfile" ); + + int trashId; + QString fileId; + QString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + assert( ok ); + check( "parseURL: trashId", QString::number( trashId ), "1" ); + check( "parseURL: fileId", fileId, "fileId" ); + check( "parseURL: relativePath", relativePath, "subfile" ); +} + +void TestTrash::urlTestSubDirectory() +{ + const KURL url = TrashImpl::makeURL( 1, "fileId", "subfile/foobar" ); + check( "makeURL", url.url(), "trash:/1-fileId/subfile/foobar" ); + + int trashId; + QString fileId; + QString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + assert( ok ); + check( "parseURL: trashId", QString::number( trashId ), "1" ); + check( "parseURL: fileId", fileId, "fileId" ); + check( "parseURL: relativePath", relativePath, "subfile/foobar" ); +} + +static void checkInfoFile( const QString& infoPath, const QString& origFilePath ) +{ + kdDebug() << k_funcinfo << infoPath << endl; + QFileInfo info( infoPath ); + assert( info.exists() ); + assert( info.isFile() ); + KSimpleConfig infoFile( info.absFilePath(), true ); + if ( !infoFile.hasGroup( "Trash Info" ) ) + kdFatal() << "no Trash Info group in " << info.absFilePath() << endl; + infoFile.setGroup( "Trash Info" ); + const QString origPath = infoFile.readEntry( "Path" ); + assert( !origPath.isEmpty() ); + assert( origPath == KURL::encode_string( origFilePath, KGlobal::locale()->fileEncodingMib() ) ); + const QString date = infoFile.readEntry( "DeletionDate" ); + assert( !date.isEmpty() ); + assert( date.contains( "T" ) ); +} + +static void createTestFile( const QString& path ) +{ + QFile f( path ); + if ( !f.open( IO_WriteOnly ) ) + kdFatal() << "Can't create " << path << endl; + f.writeBlock( "Hello world\n", 12 ); + f.close(); + assert( QFile::exists( path ) ); +} + +void TestTrash::trashFile( const QString& origFilePath, const QString& fileId ) +{ + // setup + if ( !QFile::exists( origFilePath ) ) + createTestFile( origFilePath ); + KURL u; + u.setPath( origFilePath ); + + // test + KIO::Job* job = KIO::move( u, "trash:/" ); + QMap<QString, QString> metaData; + //bool ok = KIO::NetAccess::move( u, "trash:/" ); + bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData ); + if ( !ok ) + kdError() << "moving " << u << " to trash failed with error " << KIO::NetAccess::lastError() << " " << KIO::NetAccess::lastErrorString() << endl; + assert( ok ); + if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) { + kdDebug() << " TESTS SKIPPED" << endl; + } else { + checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath ); + + QFileInfo files( m_trashDir + "/files/" + fileId ); + assert( files.isFile() ); + assert( files.size() == 12 ); + } + + // coolo suggests testing that the original file is actually gone, too :) + assert( !QFile::exists( origFilePath ) ); + + assert( !metaData.isEmpty() ); + bool found = false; + QMap<QString, QString>::ConstIterator it = metaData.begin(); + for ( ; it != metaData.end() ; ++it ) { + if ( it.key().startsWith( "trashURL" ) ) { + const QString origPath = it.key().mid( 9 ); + KURL trashURL( it.data() ); + kdDebug() << trashURL << endl; + assert( !trashURL.isEmpty() ); + assert( trashURL.protocol() == "trash" ); + int trashId = 0; + if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) + trashId = m_tmpTrashId; + assert( trashURL.path() == "/" + QString::number( trashId ) + "-" + fileId ); + found = true; + } + } + assert( found ); +} + +void TestTrash::trashFileFromHome() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = "fileFromHome"; + trashFile( homeTmpDir() + fileName, fileName ); + + // Do it again, check that we got a different id + trashFile( homeTmpDir() + fileName, fileName + "_1" ); +} + +void TestTrash::trashPercentFileFromHome() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = "file%2f"; + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::trashUtf8FileFromHome() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = utf8FileName(); + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::trashUmlautFileFromHome() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = umlautFileName(); + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::testTrashNotEmpty() +{ + KSimpleConfig cfg( "trashrc", true ); + assert( cfg.hasGroup( "Status" ) ); + cfg.setGroup( "Status" ); + assert( cfg.readBoolEntry( "Empty", true ) == false ); +} + +void TestTrash::trashFileFromOther() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = "fileFromOther"; + trashFile( otherTmpDir() + fileName, fileName ); +} + +void TestTrash::trashFileIntoOtherPartition() +{ + if ( m_otherPartitionTrashDir.isEmpty() ) { + kdDebug() << k_funcinfo << " - SKIPPED" << endl; + return; + } + kdDebug() << k_funcinfo << endl; + const QString fileName = "testtrash-file"; + const QString origFilePath = m_otherPartitionTopDir + fileName; + const QString fileId = fileName; + // cleanup + QFile::remove( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo" ); + QFile::remove( m_otherPartitionTrashDir + "/files/" + fileId ); + + // setup + if ( !QFile::exists( origFilePath ) ) + createTestFile( origFilePath ); + KURL u; + u.setPath( origFilePath ); + + // test + KIO::Job* job = KIO::move( u, "trash:/" ); + QMap<QString, QString> metaData; + bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData ); + assert( ok ); + // Note that the Path stored in the info file is relative, on other partitions (#95652) + checkInfoFile( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo", fileName ); + + QFileInfo files( m_otherPartitionTrashDir + "/files/" + fileId ); + assert( files.isFile() ); + assert( files.size() == 12 ); + + // coolo suggests testing that the original file is actually gone, too :) + assert( !QFile::exists( origFilePath ) ); + + assert( !metaData.isEmpty() ); + bool found = false; + QMap<QString, QString>::ConstIterator it = metaData.begin(); + for ( ; it != metaData.end() ; ++it ) { + if ( it.key().startsWith( "trashURL" ) ) { + const QString origPath = it.key().mid( 9 ); + KURL trashURL( it.data() ); + kdDebug() << trashURL << endl; + assert( !trashURL.isEmpty() ); + assert( trashURL.protocol() == "trash" ); + assert( trashURL.path() == QString( "/%1-%2" ).arg( m_otherPartitionId ).arg( fileId ) ); + found = true; + } + } + assert( found ); +} + +void TestTrash::trashFileOwnedByRoot() +{ + kdDebug() << k_funcinfo << endl; + KURL u; + u.setPath( "/etc/passwd" ); + const QString fileId = "passwd"; + + KIO::CopyJob* job = KIO::move( u, "trash:/" ); + job->setInteractive( false ); // no skip dialog, thanks + QMap<QString, QString> metaData; + //bool ok = KIO::NetAccess::move( u, "trash:/" ); + bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData ); + assert( !ok ); + assert( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED ); + const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( !QFile::exists( infoPath ) ); + + QFileInfo files( m_trashDir + "/files/" + fileId ); + assert( !files.exists() ); + + assert( QFile::exists( u.path() ) ); +} + +void TestTrash::trashSymlink( const QString& origFilePath, const QString& fileId, bool broken ) +{ + kdDebug() << k_funcinfo << endl; + // setup + const char* target = broken ? "/nonexistent" : "/tmp"; + bool ok = ::symlink( target, QFile::encodeName( origFilePath ) ) == 0; + assert( ok ); + KURL u; + u.setPath( origFilePath ); + + // test + ok = KIO::NetAccess::move( u, "trash:/" ); + assert( ok ); + if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) { + kdDebug() << " TESTS SKIPPED" << endl; + return; + } + checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath ); + + QFileInfo files( m_trashDir + "/files/" + fileId ); + assert( files.isSymLink() ); + assert( files.readLink() == QFile::decodeName( target ) ); + assert( !QFile::exists( origFilePath ) ); +} + +void TestTrash::trashSymlinkFromHome() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = "symlinkFromHome"; + trashSymlink( homeTmpDir() + fileName, fileName, false ); +} + +void TestTrash::trashSymlinkFromOther() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = "symlinkFromOther"; + trashSymlink( otherTmpDir() + fileName, fileName, false ); +} + +void TestTrash::trashBrokenSymlinkFromHome() +{ + kdDebug() << k_funcinfo << endl; + const QString fileName = "brokenSymlinkFromHome"; + trashSymlink( homeTmpDir() + fileName, fileName, true ); +} + +void TestTrash::trashDirectory( const QString& origPath, const QString& fileId ) +{ + kdDebug() << k_funcinfo << fileId << endl; + // setup + if ( !QFileInfo( origPath ).exists() ) { + QDir dir; + bool ok = dir.mkdir( origPath ); + Q_ASSERT( ok ); + } + createTestFile( origPath + "/testfile" ); + KURL u; u.setPath( origPath ); + + // test + bool ok = KIO::NetAccess::move( u, "trash:/" ); + assert( ok ); + if ( origPath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) { + kdDebug() << " TESTS SKIPPED" << endl; + return; + } + checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origPath ); + + QFileInfo filesDir( m_trashDir + "/files/" + fileId ); + assert( filesDir.isDir() ); + QFileInfo files( m_trashDir + "/files/" + fileId + "/testfile" ); + assert( files.exists() ); + assert( files.isFile() ); + assert( files.size() == 12 ); + assert( !QFile::exists( origPath ) ); +} + +void TestTrash::trashDirectoryFromHome() +{ + kdDebug() << k_funcinfo << endl; + QString dirName = "trashDirFromHome"; + trashDirectory( homeTmpDir() + dirName, dirName ); + // Do it again, check that we got a different id + trashDirectory( homeTmpDir() + dirName, dirName + "_1" ); +} + +void TestTrash::trashReadOnlyDirFromHome() +{ + kdDebug() << k_funcinfo << endl; + const QString dirName = readOnlyDirPath(); + QDir dir; + bool ok = dir.mkdir( dirName ); + Q_ASSERT( ok ); + // #130780 + const QString subDirPath = dirName + "/readonly_subdir"; + ok = dir.mkdir( subDirPath ); + Q_ASSERT( ok ); + createTestFile( subDirPath + "/testfile_in_subdir" ); + ::chmod( QFile::encodeName( subDirPath ), 0500 ); + + trashDirectory( dirName, "readonly" ); +} + +void TestTrash::trashDirectoryFromOther() +{ + kdDebug() << k_funcinfo << endl; + QString dirName = "trashDirFromOther"; + trashDirectory( otherTmpDir() + dirName, dirName ); +} + +void TestTrash::tryRenameInsideTrash() +{ + kdDebug() << k_funcinfo << " with file_move" << endl; + bool worked = KIO::NetAccess::file_move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" ); + assert( !worked ); + assert( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME ); + + kdDebug() << k_funcinfo << " with move" << endl; + worked = KIO::NetAccess::move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" ); + assert( !worked ); + assert( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME ); +} + +void TestTrash::delRootFile() +{ + kdDebug() << k_funcinfo << endl; + + // test deleting a trashed file + bool ok = KIO::NetAccess::del( "trash:/0-fileFromHome", 0L ); + assert( ok ); + + QFileInfo file( m_trashDir + "/files/fileFromHome" ); + assert( !file.exists() ); + QFileInfo info( m_trashDir + "/info/fileFromHome.trashinfo" ); + assert( !info.exists() ); + + // trash it again, we might need it later + const QString fileName = "fileFromHome"; + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::delFileInDirectory() +{ + kdDebug() << k_funcinfo << endl; + + // test deleting a file inside a trashed directory -> not allowed + bool ok = KIO::NetAccess::del( "trash:/0-trashDirFromHome/testfile", 0L ); + assert( !ok ); + assert( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED ); + + QFileInfo dir( m_trashDir + "/files/trashDirFromHome" ); + assert( dir.exists() ); + QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" ); + assert( file.exists() ); + QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" ); + assert( info.exists() ); +} + +void TestTrash::delDirectory() +{ + kdDebug() << k_funcinfo << endl; + + // test deleting a trashed directory + bool ok = KIO::NetAccess::del( "trash:/0-trashDirFromHome", 0L ); + assert( ok ); + + QFileInfo dir( m_trashDir + "/files/trashDirFromHome" ); + assert( !dir.exists() ); + QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" ); + assert( !file.exists() ); + QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" ); + assert( !info.exists() ); + + // trash it again, we'll need it later + QString dirName = "trashDirFromHome"; + trashDirectory( homeTmpDir() + dirName, dirName ); +} + +void TestTrash::statRoot() +{ + kdDebug() << k_funcinfo << endl; + KURL url( "trash:/" ); + KIO::UDSEntry entry; + bool ok = KIO::NetAccess::stat( url, entry, 0 ); + assert( ok ); + KFileItem item( entry, url ); + assert( item.isDir() ); + assert( !item.isLink() ); + assert( item.isReadable() ); + assert( item.isWritable() ); + assert( !item.isHidden() ); + assert( item.name() == "." ); + assert( item.acceptsDrops() ); +} + +void TestTrash::statFileInRoot() +{ + kdDebug() << k_funcinfo << endl; + KURL url( "trash:/0-fileFromHome" ); + KIO::UDSEntry entry; + bool ok = KIO::NetAccess::stat( url, entry, 0 ); + assert( ok ); + KFileItem item( entry, url ); + assert( item.isFile() ); + assert( !item.isDir() ); + assert( !item.isLink() ); + assert( item.isReadable() ); + assert( !item.isWritable() ); + assert( !item.isHidden() ); + assert( item.name() == "fileFromHome" ); + assert( !item.acceptsDrops() ); +} + +void TestTrash::statDirectoryInRoot() +{ + kdDebug() << k_funcinfo << endl; + KURL url( "trash:/0-trashDirFromHome" ); + KIO::UDSEntry entry; + bool ok = KIO::NetAccess::stat( url, entry, 0 ); + assert( ok ); + KFileItem item( entry, url ); + assert( item.isDir() ); + assert( !item.isLink() ); + assert( item.isReadable() ); + assert( !item.isWritable() ); + assert( !item.isHidden() ); + assert( item.name() == "trashDirFromHome" ); + assert( !item.acceptsDrops() ); +} + +void TestTrash::statSymlinkInRoot() +{ + kdDebug() << k_funcinfo << endl; + KURL url( "trash:/0-symlinkFromHome" ); + KIO::UDSEntry entry; + bool ok = KIO::NetAccess::stat( url, entry, 0 ); + assert( ok ); + KFileItem item( entry, url ); + assert( item.isLink() ); + assert( item.linkDest() == "/tmp" ); + assert( item.isReadable() ); + assert( !item.isWritable() ); + assert( !item.isHidden() ); + assert( item.name() == "symlinkFromHome" ); + assert( !item.acceptsDrops() ); +} + +void TestTrash::statFileInDirectory() +{ + kdDebug() << k_funcinfo << endl; + KURL url( "trash:/0-trashDirFromHome/testfile" ); + KIO::UDSEntry entry; + bool ok = KIO::NetAccess::stat( url, entry, 0 ); + assert( ok ); + KFileItem item( entry, url ); + assert( item.isFile() ); + assert( !item.isLink() ); + assert( item.isReadable() ); + assert( !item.isWritable() ); + assert( !item.isHidden() ); + assert( item.name() == "testfile" ); + assert( !item.acceptsDrops() ); +} + +void TestTrash::copyFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath ) +{ + KURL src( "trash:/0-" + fileId ); + if ( !relativePath.isEmpty() ) + src.addPath( relativePath ); + KURL dest; + dest.setPath( destPath ); + + assert( KIO::NetAccess::exists( src, true, (QWidget*)0 ) ); + + // A dnd would use copy(), but we use copyAs to ensure the final filename + //kdDebug() << k_funcinfo << "copyAs:" << src << " -> " << dest << endl; + KIO::Job* job = KIO::copyAs( src, dest ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( QFile::exists( infoFile ) ); + + QFileInfo filesItem( m_trashDir + "/files/" + fileId ); + assert( filesItem.exists() ); + + assert( QFile::exists( destPath ) ); +} + +void TestTrash::copyFileFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "fileFromHome_copied"; + copyFromTrash( "fileFromHome", destPath ); + assert( QFileInfo( destPath ).isFile() ); + assert( QFileInfo( destPath ).size() == 12 ); +} + +void TestTrash::copyFileInDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "testfile_copied"; + copyFromTrash( "trashDirFromHome", destPath, "testfile" ); + assert( QFileInfo( destPath ).isFile() ); + assert( QFileInfo( destPath ).size() == 12 ); +} + +void TestTrash::copyDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "trashDirFromHome_copied"; + copyFromTrash( "trashDirFromHome", destPath ); + assert( QFileInfo( destPath ).isDir() ); +} + +void TestTrash::copySymlinkFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "symlinkFromHome_copied"; + copyFromTrash( "symlinkFromHome", destPath ); + assert( QFileInfo( destPath ).isSymLink() ); +} + +void TestTrash::moveFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath ) +{ + KURL src( "trash:/0-" + fileId ); + if ( !relativePath.isEmpty() ) + src.addPath( relativePath ); + KURL dest; + dest.setPath( destPath ); + + assert( KIO::NetAccess::exists( src, true, (QWidget*)0 ) ); + + // A dnd would use move(), but we use moveAs to ensure the final filename + KIO::Job* job = KIO::moveAs( src, dest ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( !QFile::exists( infoFile ) ); + + QFileInfo filesItem( m_trashDir + "/files/" + fileId ); + assert( !filesItem.exists() ); + + assert( QFile::exists( destPath ) ); +} + +void TestTrash::moveFileFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "fileFromHome_restored"; + moveFromTrash( "fileFromHome", destPath ); + assert( QFileInfo( destPath ).isFile() ); + assert( QFileInfo( destPath ).size() == 12 ); + + // trash it again for later + const QString fileName = "fileFromHome"; + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::moveFileInDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "testfile_restored"; + copyFromTrash( "trashDirFromHome", destPath, "testfile" ); + assert( QFileInfo( destPath ).isFile() ); + assert( QFileInfo( destPath ).size() == 12 ); +} + +void TestTrash::moveDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "trashDirFromHome_restored"; + moveFromTrash( "trashDirFromHome", destPath ); + assert( QFileInfo( destPath ).isDir() ); + + // trash it again, we'll need it later + QString dirName = "trashDirFromHome"; + trashDirectory( homeTmpDir() + dirName, dirName ); +} + +void TestTrash::trashDirectoryOwnedByRoot() +{ + KURL u; + if ( QFile::exists( "/etc/cups" ) ) + u.setPath( "/etc/cups" ); + else if ( QFile::exists( "/boot" ) ) + u.setPath( "/boot" ); + else + u.setPath( "/etc" ); + const QString fileId = u.path(); + kdDebug() << k_funcinfo << "fileId=" << fileId << endl; + + KIO::CopyJob* job = KIO::move( u, "trash:/" ); + job->setInteractive( false ); // no skip dialog, thanks + QMap<QString, QString> metaData; + bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData ); + assert( !ok ); + const int err = KIO::NetAccess::lastError(); + assert( err == KIO::ERR_ACCESS_DENIED + || err == KIO::ERR_CANNOT_OPEN_FOR_READING ); + + const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( !QFile::exists( infoPath ) ); + + QFileInfo files( m_trashDir + "/files/" + fileId ); + assert( !files.exists() ); + + assert( QFile::exists( u.path() ) ); +} + +void TestTrash::moveSymlinkFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString destPath = otherTmpDir() + "symlinkFromHome_restored"; + moveFromTrash( "symlinkFromHome", destPath ); + assert( QFileInfo( destPath ).isSymLink() ); +} + +void TestTrash::getFile() +{ + kdDebug() << k_funcinfo << endl; + const QString fileId = "fileFromHome_1"; + const KURL url = TrashImpl::makeURL( 0, fileId, QString::null ); + QString tmpFile; + bool ok = KIO::NetAccess::download( url, tmpFile, 0 ); + assert( ok ); + QFile file( tmpFile ); + ok = file.open( IO_ReadOnly ); + assert( ok ); + QByteArray str = file.readAll(); + QCString cstr( str.data(), str.size() + 1 ); + if ( cstr != "Hello world\n" ) + kdFatal() << "get() returned the following data:" << cstr << endl; + file.close(); + KIO::NetAccess::removeTempFile( tmpFile ); +} + +void TestTrash::restoreFile() +{ + kdDebug() << k_funcinfo << endl; + const QString fileId = "fileFromHome_1"; + const KURL url = TrashImpl::makeURL( 0, fileId, QString::null ); + const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + const QString filesItem( m_trashDir + "/files/" + fileId ); + + assert( QFile::exists( infoFile ) ); + assert( QFile::exists( filesItem ) ); + + QByteArray packedArgs; + QDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << url; + KIO::Job* job = KIO::special( url, packedArgs ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + + assert( !QFile::exists( infoFile ) ); + assert( !QFile::exists( filesItem ) ); + + const QString destPath = homeTmpDir() + "fileFromHome"; + assert( QFile::exists( destPath ) ); +} + +void TestTrash::restoreFileFromSubDir() +{ + kdDebug() << k_funcinfo << endl; + const QString fileId = "trashDirFromHome_1/testfile"; + assert( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) ); + + const KURL url = TrashImpl::makeURL( 0, fileId, QString::null ); + const QString infoFile( m_trashDir + "/info/trashDirFromHome_1.trashinfo" ); + const QString filesItem( m_trashDir + "/files/trashDirFromHome_1/testfile" ); + + assert( QFile::exists( infoFile ) ); + assert( QFile::exists( filesItem ) ); + + QByteArray packedArgs; + QDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << url; + KIO::Job* job = KIO::special( url, packedArgs ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( !ok ); + // dest dir doesn't exist -> error message + assert( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED ); + + // check that nothing happened + assert( QFile::exists( infoFile ) ); + assert( QFile::exists( filesItem ) ); + assert( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) ); +} + +void TestTrash::restoreFileToDeletedDirectory() +{ + kdDebug() << k_funcinfo << endl; + // Ensure we'll get "fileFromHome" as fileId + removeFile( m_trashDir, "/info/fileFromHome.trashinfo" ); + removeFile( m_trashDir, "/files/fileFromHome" ); + trashFileFromHome(); + // Delete orig dir + bool delOK = KIO::NetAccess::del( homeTmpDir(), 0 ); + assert( delOK ); + + const QString fileId = "fileFromHome"; + const KURL url = TrashImpl::makeURL( 0, fileId, QString::null ); + const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + const QString filesItem( m_trashDir + "/files/" + fileId ); + + assert( QFile::exists( infoFile ) ); + assert( QFile::exists( filesItem ) ); + + QByteArray packedArgs; + QDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << url; + KIO::Job* job = KIO::special( url, packedArgs ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( !ok ); + // dest dir doesn't exist -> error message + assert( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED ); + + // check that nothing happened + assert( QFile::exists( infoFile ) ); + assert( QFile::exists( filesItem ) ); + + const QString destPath = homeTmpDir() + "fileFromHome"; + assert( !QFile::exists( destPath ) ); +} + +void TestTrash::listRootDir() +{ + kdDebug() << k_funcinfo << endl; + m_entryCount = 0; + m_listResult.clear(); + KIO::ListJob* job = KIO::listDir( "trash:/" ); + connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ), + SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl; + assert( m_entryCount > 1 ); + + kdDebug() << k_funcinfo << m_listResult << endl; + assert( m_listResult.contains( "." ) == 1 ); // found it, and only once +} + +void TestTrash::listRecursiveRootDir() +{ + kdDebug() << k_funcinfo << endl; + m_entryCount = 0; + m_listResult.clear(); + KIO::ListJob* job = KIO::listRecursive( "trash:/" ); + connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ), + SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl; + assert( m_entryCount > 1 ); + + kdDebug() << k_funcinfo << m_listResult << endl; + assert( m_listResult.contains( "." ) == 1 ); // found it, and only once +} + +void TestTrash::listSubDir() +{ + kdDebug() << k_funcinfo << endl; + m_entryCount = 0; + m_listResult.clear(); + KIO::ListJob* job = KIO::listDir( "trash:/0-trashDirFromHome" ); + connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ), + SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl; + assert( m_entryCount == 2 ); + + kdDebug() << k_funcinfo << m_listResult << endl; + assert( m_listResult.contains( "." ) == 1 ); // found it, and only once + assert( m_listResult.contains( "testfile" ) == 1 ); // found it, and only once +} + +void TestTrash::slotEntries( KIO::Job*, const KIO::UDSEntryList& lst ) +{ + for( KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { + KIO::UDSEntry::ConstIterator it2 = (*it).begin(); + QString displayName; + KURL url; + for( ; it2 != (*it).end(); it2++ ) { + switch ((*it2).m_uds) { + case KIO::UDS_NAME: + displayName = (*it2).m_str; + break; + case KIO::UDS_URL: + url = (*it2).m_str; + break; + } + } + kdDebug() << k_funcinfo << displayName << " " << url << endl; + if ( !url.isEmpty() ) { + assert( url.protocol() == "trash" ); + } + m_listResult << displayName; + } + m_entryCount += lst.count(); +} + +void TestTrash::emptyTrash() +{ + // ## Even though we use a custom XDG_DATA_HOME value, emptying the + // trash would still empty the other trash directories in other partitions. + // So we can't activate this test by default. +#if 0 + kdDebug() << k_funcinfo << endl; + QByteArray packedArgs; + QDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)1; + KIO::Job* job = KIO::special( "trash:/", packedArgs ); + bool ok = KIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + + KSimpleConfig cfg( "trashrc", true ); + assert( cfg.hasGroup( "Status" ) ); + cfg.setGroup( "Status" ); + assert( cfg.readBoolEntry( "Empty", false ) == true ); + + assert( !QFile::exists( m_trashDir + "/files/fileFromHome" ) ); + assert( !QFile::exists( m_trashDir + "/files/readonly" ) ); + assert( !QFile::exists( m_trashDir + "/info/readonly.trashinfo" ) ); + +#else + kdDebug() << k_funcinfo << " : SKIPPED" << endl; +#endif +} + +#include "testtrash.moc" diff --git a/kioslave/trash/testtrash.h b/kioslave/trash/testtrash.h new file mode 100644 index 000000000..70d06dc8b --- /dev/null +++ b/kioslave/trash/testtrash.h @@ -0,0 +1,118 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef TESTTRASH_H +#define TESTTRASH_H + +#include <qobject.h> + +class TestTrash : public QObject +{ + Q_OBJECT + +public: + TestTrash() {} + void setup(); + void cleanTrash(); + void runAll(); + + // tests + + void urlTestFile(); + void urlTestDirectory(); + void urlTestSubDirectory(); + + void trashFileFromHome(); + void trashPercentFileFromHome(); + void trashUtf8FileFromHome(); + void trashUmlautFileFromHome(); + void testTrashNotEmpty(); + void trashFileFromOther(); + void trashFileIntoOtherPartition(); + void trashFileOwnedByRoot(); + void trashSymlinkFromHome(); + void trashSymlinkFromOther(); + void trashBrokenSymlinkFromHome(); + void trashDirectoryFromHome(); + void trashReadOnlyDirFromHome(); + void trashDirectoryFromOther(); + void trashDirectoryOwnedByRoot(); + + void tryRenameInsideTrash(); + + void statRoot(); + void statFileInRoot(); + void statDirectoryInRoot(); + void statSymlinkInRoot(); + void statFileInDirectory(); + + void copyFileFromTrash(); + void copyFileInDirectoryFromTrash(); + void copyDirectoryFromTrash(); + void copySymlinkFromTrash(); + + void moveFileFromTrash(); + void moveFileInDirectoryFromTrash(); + void moveDirectoryFromTrash(); + void moveSymlinkFromTrash(); + + void listRootDir(); + void listRecursiveRootDir(); + void listSubDir(); + + void delRootFile(); + void delFileInDirectory(); + void delDirectory(); + + void getFile(); + void restoreFile(); + void restoreFileFromSubDir(); + void restoreFileToDeletedDirectory(); + + void emptyTrash(); + +private slots: + void slotEntries( KIO::Job*, const KIO::UDSEntryList& ); + +private: + void trashFile( const QString& origFilePath, const QString& fileId ); + void trashSymlink( const QString& origFilePath, const QString& fileName, bool broken ); + void trashDirectory( const QString& origPath, const QString& fileName ); + void copyFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath = QString::null ); + void moveFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath = QString::null ); + + QString homeTmpDir() const; + QString otherTmpDir() const; + QString utf8FileName() const; + QString umlautFileName() const; + QString readOnlyDirPath() const; + + QString m_trashDir; + + QString m_otherPartitionTopDir; + QString m_otherPartitionTrashDir; + bool m_tmpIsWritablePartition; + int m_tmpTrashId; + int m_otherPartitionId; + + int m_entryCount; + QStringList m_listResult; +}; + +#endif diff --git a/kioslave/trash/trash.protocol b/kioslave/trash/trash.protocol new file mode 100644 index 000000000..8387fdcb7 --- /dev/null +++ b/kioslave/trash/trash.protocol @@ -0,0 +1,89 @@ +[Protocol] +exec=kio_trash +protocol=trash +input=none +output=filesystem +# AccessDate doesn't make sense. +# OTOH we need deletion date :) +listing=Name,Type,Size,Extra1,Extra2,Date,Access,Owner,Group,Link +reading=true +writing=true +makedir=false +deleting=true +linking=false +moving=true +Icon=trashcan_full +maxInstances=2 +Class=:local +renameFromFile=true +renameToFile=true +copyFromFile=true +copyToFile=true +deleteRecursive=true +fileNameUsedForCopying=Name +#TODO DocPath +ExtraNames=Original Path,Deletion Date +ExtraNames[af]=Oorspronklike gids, Uitvee datum +ExtraNames[ar]=المسار الأصلي ØŒ تاريخ المØÙˆ +ExtraNames[be]=Ðрыгінальнае меÑцазнаходжанне,Ð§Ð°Ñ Ð²Ñ‹Ð´Ð°Ð»ÐµÐ½Ð½Ñ +ExtraNames[bg]=МеÑтоположение, дата на изтриване +ExtraNames[bn]=পূরà§à¦¬à¦¤à¦¨ পাথ, মোছার তারিখ +ExtraNames[bs]=Originalni put,Datum brisanja +ExtraNames[ca]=Camà original,Data d'esborrat +ExtraNames[cs]=Původnà cesta,Datum smazánà +ExtraNames[csb]=Ã’riginalnô stegna,Datum remniãcô +ExtraNames[da]=Original sti, sletningsdato +ExtraNames[de]=Ursprünglicher Pfad, Löschzeitpunkt +ExtraNames[el]=ΑÏχική διαδÏομή,ΗμεÏομηνία διαγÏαφής +ExtraNames[eo]=Originala Vojo,Forigo Dato +ExtraNames[es]=Ruta original,Fecha de borrado +ExtraNames[et]=Algne asukoht,Kustutamisaeg +ExtraNames[eu]=Jatorrizko bideizena, ezabatzeko data +ExtraNames[fa]=مسیر اصلی، تاریخ ØØ°Ù +ExtraNames[fi]=Alkuperäinen polku, poistopäivä +ExtraNames[fr]=Emplacement d'origine, date de suppression +ExtraNames[fy]=Oarspronklike lokaasje,datum fan wiskjen +ExtraNames[ga]=Bunchonair,Dáta Scriosta +ExtraNames[gl]=Rota Orixinal,Data de Eliminación +ExtraNames[he]=× ×ª×™×‘ מקורי, ת×ריך מחיקה +ExtraNames[hi]=मूल पथ, मिटाने की तिथि +ExtraNames[hr]=Izvorna putanja,Datum brisanja +ExtraNames[hu]=Eredeti elérési út,Törlési dátum +ExtraNames[is]=Upprunaleg slóð,dagsetning á eyðingu +ExtraNames[it]=Percorso originale,ora di eliminazione +ExtraNames[ja]=å…ƒã®ãƒ‘ス,削除日 +ExtraNames[ka]=სáƒáƒ¬áƒ§áƒ˜áƒ¡áƒ˜ გეზი, წáƒáƒ¨áƒšáƒ˜áƒ¡ თáƒáƒ იღი +ExtraNames[kk]=Бұрынғы жолы, Өшірілген кезі +ExtraNames[km]=ផ្លូវ​លំនាំដើម កាលបរិច្ឆáŸáž‘​លុប +ExtraNames[ko]=ì›ë³¸ 경로,ì‚ì œ ë‚ ì§œ +ExtraNames[lt]=Originalus kelias,Trynimo data +ExtraNames[lv]=OrÄ£inÄlais ceļš,DzÄ“Å¡anas datums +ExtraNames[mk]=Оригинална патека,Датум на бришење +ExtraNames[ms]=Laluan Asli, Tarikh Penghapusan +ExtraNames[nb]=Opprinnelig sti, slettedato +ExtraNames[nds]=Orginaalpadd,Wegdodatum +ExtraNames[ne]=मौलिक मारà¥à¤—, मेटà¥à¤¨à¥‡ मिति +ExtraNames[nl]=Oorspronkelijke locatie,Datum van verwijdering +ExtraNames[nn]=Opprinneleg stig, slettedato +ExtraNames[pa]=ਅਸਲੀ ਮਾਰਗ,ਹਟਾਉਣ ਮਿਤੀ +ExtraNames[pl]=Åšcieżka oryginalna,Data usuniÄ™cia +ExtraNames[pt]=Localização Original,Data de Remoção +ExtraNames[pt_BR]=Caminho Original,Data da Remoção +ExtraNames[ro]=Calea originală,Data È™tergerii +ExtraNames[ru]=ИÑходный путь, дата ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ +ExtraNames[rw]=Inzira Mwimerere, Itariki y'Ihanagura +ExtraNames[sl]=Prvotna pot,Datum brisanja +ExtraNames[sr]=првобитна путања,датум бриÑања +ExtraNames[sr@Latn]=prvobitna putanja,datum brisanja +ExtraNames[sv]=Ursprunglig sökväg,Borttagsdatum +ExtraNames[ta]=மூல பாதை,நீகà¯à®•à®ªà¯à®ªà®Ÿà¯à®Ÿ தேதி +ExtraNames[te]=అసలౠదారà±, తీసివేసిన తేది +ExtraNames[th]=พาธเดิม,วันที่ที่ลบ +ExtraNames[tr]=Orjinal Yol, Silinme Tarihi +ExtraNames[tt]=Çığanaq Yulı,Beterelü Çağı +ExtraNames[uk]=ШлÑÑ…,Дата Ð²Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ +ExtraNames[vi]=ÄÆ°á»ng dẫn TrÆ°á»›c khi vứt,Ngà y Vứt +ExtraNames[wa]=OridjinÃ¥ tchmin,Date di disfaçaedje +ExtraNames[zh_CN]=原始路径,åˆ é™¤æ—¥æœŸ +ExtraNames[zh_TW]=原始路徑,刪除日期 +ExtraTypes=QString,QDateTime diff --git a/kioslave/trash/trashimpl.cpp b/kioslave/trash/trashimpl.cpp new file mode 100644 index 000000000..9507c77ab --- /dev/null +++ b/kioslave/trash/trashimpl.cpp @@ -0,0 +1,962 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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 "trashimpl.h" +#include <klocale.h> +#include <klargefile.h> +#include <kio/global.h> +#include <kio/renamedlg.h> +#include <kio/job.h> +#include <kdebug.h> +#include <kurl.h> +#include <kdirnotify_stub.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kglobalsettings.h> +#include <kmountpoint.h> +#include <kfileitem.h> +#include <kio/chmodjob.h> + +#include <dcopref.h> + +#include <qapplication.h> +#include <qeventloop.h> +#include <qfile.h> +#include <qdir.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/param.h> +#include <fcntl.h> +#include <unistd.h> +#include <dirent.h> +#include <stdlib.h> +#include <errno.h> + +TrashImpl::TrashImpl() : + QObject(), + m_lastErrorCode( 0 ), + m_initStatus( InitToBeDone ), + m_lastId( 0 ), + m_homeDevice( 0 ), + m_trashDirectoriesScanned( false ), + m_mibEnum( KGlobal::locale()->fileEncodingMib() ), + // not using kio_trashrc since KIO uses that one already for kio_trash + // so better have a separate one, for faster parsing by e.g. kmimetype.cpp + m_config( "trashrc" ) +{ + KDE_struct_stat buff; + if ( KDE_lstat( QFile::encodeName( QDir::homeDirPath() ), &buff ) == 0 ) { + m_homeDevice = buff.st_dev; + } else { + kdError() << "Should never happen: couldn't stat $HOME " << strerror( errno ) << endl; + } +} + +/** + * Test if a directory exists, create otherwise + * @param _name full path of the directory + * @return errorcode, or 0 if the dir was created or existed already + * Warning, don't use return value like a bool + */ +int TrashImpl::testDir( const QString &_name ) const +{ + DIR *dp = opendir( QFile::encodeName(_name) ); + if ( dp == NULL ) + { + QString name = _name; + if ( name.endsWith( "/" ) ) + name.truncate( name.length() - 1 ); + QCString path = QFile::encodeName(name); + + bool ok = ::mkdir( path, S_IRWXU ) == 0; + if ( !ok && errno == EEXIST ) { +#if 0 // this would require to use SlaveBase's method to ask the question + //int ret = KMessageBox::warningYesNo( 0, i18n("%1 is a file, but KDE needs it to be a directory. Move it to %2.orig and create directory?").arg(name).arg(name) ); + //if ( ret == KMessageBox::Yes ) { +#endif + if ( ::rename( path, path + ".orig" ) == 0 ) { + ok = ::mkdir( path, S_IRWXU ) == 0; + } else { // foo.orig existed already. How likely is that? + ok = false; + } + if ( !ok ) { + return KIO::ERR_DIR_ALREADY_EXIST; + } +#if 0 + //} else { + // return 0; + //} +#endif + } + if ( !ok ) + { + //KMessageBox::sorry( 0, i18n( "Couldn't create directory %1. Check for permissions." ).arg( name ) ); + kdWarning() << "could not create " << name << endl; + return KIO::ERR_COULD_NOT_MKDIR; + } else { + kdDebug() << name << " created." << endl; + } + } + else // exists already + { + closedir( dp ); + } + return 0; // success +} + +bool TrashImpl::init() +{ + if ( m_initStatus == InitOK ) + return true; + if ( m_initStatus == InitError ) + return false; + + // Check the trash directory and its info and files subdirs + // see also kdesktop/init.cc for first time initialization + m_initStatus = InitError; + // $XDG_DATA_HOME/Trash, i.e. ~/.local/share/Trash by default. + const QString xdgDataDir = KGlobal::dirs()->localxdgdatadir(); + if ( !KStandardDirs::makeDir( xdgDataDir, 0700 ) ) { + kdWarning() << "failed to create " << xdgDataDir << endl; + return false; + } + + const QString trashDir = xdgDataDir + "Trash"; + int err; + if ( ( err = testDir( trashDir ) ) ) { + error( err, trashDir ); + return false; + } + if ( ( err = testDir( trashDir + "/info" ) ) ) { + error( err, trashDir + "/info" ); + return false; + } + if ( ( err = testDir( trashDir + "/files" ) ) ) { + error( err, trashDir + "/files" ); + return false; + } + m_trashDirectories.insert( 0, trashDir ); + m_initStatus = InitOK; + kdDebug() << k_funcinfo << "initialization OK, home trash dir: " << trashDir << endl; + return true; +} + +void TrashImpl::migrateOldTrash() +{ + kdDebug() << k_funcinfo << endl; + const QString oldTrashDir = KGlobalSettings::trashPath(); + const QStrList entries = listDir( oldTrashDir ); + bool allOK = true; + QStrListIterator entryIt( entries ); + for (; entryIt.current(); ++entryIt) { + QString srcPath = QFile::decodeName( *entryIt ); + if ( srcPath == "." || srcPath == ".." || srcPath == ".directory" ) + continue; + srcPath.prepend( oldTrashDir ); // make absolute + int trashId; + QString fileId; + if ( !createInfo( srcPath, trashId, fileId ) ) { + kdWarning() << "Trash migration: failed to create info for " << srcPath << endl; + allOK = false; + } else { + bool ok = moveToTrash( srcPath, trashId, fileId ); + if ( !ok ) { + (void)deleteInfo( trashId, fileId ); + kdWarning() << "Trash migration: failed to create info for " << srcPath << endl; + allOK = false; + } else { + kdDebug() << "Trash migration: moved " << srcPath << endl; + } + } + } + if ( allOK ) { + // We need to remove the old one, otherwise the desktop will have two trashcans... + kdDebug() << "Trash migration: all OK, removing old trash directory" << endl; + synchronousDel( oldTrashDir, false, true ); + } +} + +bool TrashImpl::createInfo( const QString& origPath, int& trashId, QString& fileId ) +{ + kdDebug() << k_funcinfo << origPath << endl; + // Check source + const QCString origPath_c( QFile::encodeName( origPath ) ); + KDE_struct_stat buff_src; + if ( KDE_lstat( origPath_c.data(), &buff_src ) == -1 ) { + if ( errno == EACCES ) + error( KIO::ERR_ACCESS_DENIED, origPath ); + else + error( KIO::ERR_DOES_NOT_EXIST, origPath ); + return false; + } + + // Choose destination trash + trashId = findTrashDirectory( origPath ); + if ( trashId < 0 ) { + kdWarning() << "OUCH - internal error, TrashImpl::findTrashDirectory returned " << trashId << endl; + return false; // ### error() needed? + } + kdDebug() << k_funcinfo << "trashing to " << trashId << endl; + + // Grab original filename + KURL url; + url.setPath( origPath ); + const QString origFileName = url.fileName(); + + // Make destination file in info/ + url.setPath( infoPath( trashId, origFileName ) ); // we first try with origFileName + KURL baseDirectory; + baseDirectory.setPath( url.directory() ); + // Here we need to use O_EXCL to avoid race conditions with other kioslave processes + int fd = 0; + do { + kdDebug() << k_funcinfo << "trying to create " << url.path() << endl; + fd = ::open( QFile::encodeName( url.path() ), O_WRONLY | O_CREAT | O_EXCL, 0600 ); + if ( fd < 0 ) { + if ( errno == EEXIST ) { + url.setFileName( KIO::RenameDlg::suggestName( baseDirectory, url.fileName() ) ); + // and try again on the next iteration + } else { + error( KIO::ERR_COULD_NOT_WRITE, url.path() ); + return false; + } + } + } while ( fd < 0 ); + const QString infoPath = url.path(); + fileId = url.fileName(); + Q_ASSERT( fileId.endsWith( ".trashinfo" ) ); + fileId.truncate( fileId.length() - 10 ); // remove .trashinfo from fileId + + FILE* file = ::fdopen( fd, "w" ); + if ( !file ) { // can't see how this would happen + error( KIO::ERR_COULD_NOT_WRITE, infoPath ); + return false; + } + + // Contents of the info file. We could use KSimpleConfig, but that would + // mean closing and reopening fd, i.e. opening a race condition... + QCString info = "[Trash Info]\n"; + info += "Path="; + // Escape filenames according to the way they are encoded on the filesystem + // All this to basically get back to the raw 8-bit representation of the filename... + if ( trashId == 0 ) // home trash: absolute path + info += KURL::encode_string( origPath, m_mibEnum ).latin1(); + else + info += KURL::encode_string( makeRelativePath( topDirectoryPath( trashId ), origPath ), m_mibEnum ).latin1(); + info += "\n"; + info += "DeletionDate="; + info += QDateTime::currentDateTime().toString( Qt::ISODate ).latin1(); + info += "\n"; + size_t sz = info.size() - 1; // avoid trailing 0 from QCString + + size_t written = ::fwrite(info.data(), 1, sz, file); + if ( written != sz ) { + ::fclose( file ); + QFile::remove( infoPath ); + error( KIO::ERR_DISK_FULL, infoPath ); + return false; + } + + ::fclose( file ); + + kdDebug() << k_funcinfo << "info file created in trashId=" << trashId << " : " << fileId << endl; + return true; +} + +QString TrashImpl::makeRelativePath( const QString& topdir, const QString& path ) +{ + const QString realPath = KStandardDirs::realFilePath( path ); + // topdir ends with '/' + if ( realPath.startsWith( topdir ) ) { + const QString rel = realPath.mid( topdir.length() ); + Q_ASSERT( rel[0] != '/' ); + return rel; + } else { // shouldn't happen... + kdWarning() << "Couldn't make relative path for " << realPath << " (" << path << "), with topdir=" << topdir << endl; + return realPath; + } +} + +QString TrashImpl::infoPath( int trashId, const QString& fileId ) const +{ + QString trashPath = trashDirectoryPath( trashId ); + trashPath += "/info/"; + trashPath += fileId; + trashPath += ".trashinfo"; + return trashPath; +} + +QString TrashImpl::filesPath( int trashId, const QString& fileId ) const +{ + QString trashPath = trashDirectoryPath( trashId ); + trashPath += "/files/"; + trashPath += fileId; + return trashPath; +} + +bool TrashImpl::deleteInfo( int trashId, const QString& fileId ) +{ + bool ok = QFile::remove( infoPath( trashId, fileId ) ); + if ( ok ) + fileRemoved(); + return ok; +} + +bool TrashImpl::moveToTrash( const QString& origPath, int trashId, const QString& fileId ) +{ + kdDebug() << k_funcinfo << endl; + const QString dest = filesPath( trashId, fileId ); + if ( !move( origPath, dest ) ) { + // Maybe the move failed due to no permissions to delete source. + // In that case, delete dest to keep things consistent, since KIO doesn't do it. + if ( QFileInfo( dest ).isFile() ) + QFile::remove( dest ); + else + synchronousDel( dest, false, true ); + return false; + } + fileAdded(); + return true; +} + +bool TrashImpl::moveFromTrash( const QString& dest, int trashId, const QString& fileId, const QString& relativePath ) +{ + QString src = filesPath( trashId, fileId ); + if ( !relativePath.isEmpty() ) { + src += '/'; + src += relativePath; + } + if ( !move( src, dest ) ) + return false; + return true; +} + +bool TrashImpl::move( const QString& src, const QString& dest ) +{ + if ( directRename( src, dest ) ) { + // This notification is done by KIO::moveAs when using the code below + // But if we do a direct rename we need to do the notification ourselves + KDirNotify_stub allDirNotify( "*", "KDirNotify*" ); + KURL urlDest; urlDest.setPath( dest ); + urlDest.setPath( urlDest.directory() ); + allDirNotify.FilesAdded( urlDest ); + return true; + } + if ( m_lastErrorCode != KIO::ERR_UNSUPPORTED_ACTION ) + return false; + + KURL urlSrc, urlDest; + urlSrc.setPath( src ); + urlDest.setPath( dest ); + kdDebug() << k_funcinfo << urlSrc << " -> " << urlDest << endl; + KIO::CopyJob* job = KIO::moveAs( urlSrc, urlDest, false ); +#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE + job->setInteractive( false ); +#endif + connect( job, SIGNAL( result(KIO::Job *) ), + this, SLOT( jobFinished(KIO::Job *) ) ); + qApp->eventLoop()->enterLoop(); + + return m_lastErrorCode == 0; +} + +void TrashImpl::jobFinished(KIO::Job* job) +{ + kdDebug() << k_funcinfo << " error=" << job->error() << endl; + error( job->error(), job->errorText() ); + qApp->eventLoop()->exitLoop(); +} + +bool TrashImpl::copyToTrash( const QString& origPath, int trashId, const QString& fileId ) +{ + kdDebug() << k_funcinfo << endl; + const QString dest = filesPath( trashId, fileId ); + if ( !copy( origPath, dest ) ) + return false; + fileAdded(); + return true; +} + +bool TrashImpl::copyFromTrash( const QString& dest, int trashId, const QString& fileId, const QString& relativePath ) +{ + QString src = filesPath( trashId, fileId ); + if ( !relativePath.isEmpty() ) { + src += '/'; + src += relativePath; + } + return copy( src, dest ); +} + +bool TrashImpl::copy( const QString& src, const QString& dest ) +{ + // kio_file's copy() method is quite complex (in order to be fast), let's just call it... + m_lastErrorCode = 0; + KURL urlSrc; + urlSrc.setPath( src ); + KURL urlDest; + urlDest.setPath( dest ); + kdDebug() << k_funcinfo << "copying " << src << " to " << dest << endl; + KIO::CopyJob* job = KIO::copyAs( urlSrc, urlDest, false ); +#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE + job->setInteractive( false ); +#endif + connect( job, SIGNAL( result( KIO::Job* ) ), + this, SLOT( jobFinished( KIO::Job* ) ) ); + qApp->eventLoop()->enterLoop(); + + return m_lastErrorCode == 0; +} + +bool TrashImpl::directRename( const QString& src, const QString& dest ) +{ + kdDebug() << k_funcinfo << src << " -> " << dest << endl; + if ( ::rename( QFile::encodeName( src ), QFile::encodeName( dest ) ) != 0 ) { + if (errno == EXDEV) { + error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("rename") ); + } else { + if (( errno == EACCES ) || (errno == EPERM)) { + error( KIO::ERR_ACCESS_DENIED, dest ); + } else if (errno == EROFS) { // The file is on a read-only filesystem + error( KIO::ERR_CANNOT_DELETE, src ); + } else { + error( KIO::ERR_CANNOT_RENAME, src ); + } + } + return false; + } + return true; +} + +#if 0 +bool TrashImpl::mkdir( int trashId, const QString& fileId, int permissions ) +{ + const QString path = filesPath( trashId, fileId ); + if ( ::mkdir( QFile::encodeName( path ), permissions ) != 0 ) { + if ( errno == EACCES ) { + error( KIO::ERR_ACCESS_DENIED, path ); + return false; + } else if ( errno == ENOSPC ) { + error( KIO::ERR_DISK_FULL, path ); + return false; + } else { + error( KIO::ERR_COULD_NOT_MKDIR, path ); + return false; + } + } else { + if ( permissions != -1 ) + ::chmod( QFile::encodeName( path ), permissions ); + } + return true; +} +#endif + +bool TrashImpl::del( int trashId, const QString& fileId ) +{ + QString info = infoPath(trashId, fileId); + QString file = filesPath(trashId, fileId); + + QCString info_c = QFile::encodeName(info); + + KDE_struct_stat buff; + if ( KDE_lstat( info_c.data(), &buff ) == -1 ) { + if ( errno == EACCES ) + error( KIO::ERR_ACCESS_DENIED, file ); + else + error( KIO::ERR_DOES_NOT_EXIST, file ); + return false; + } + + if ( !synchronousDel( file, true, QFileInfo(file).isDir() ) ) + return false; + + QFile::remove( info ); + fileRemoved(); + return true; +} + +bool TrashImpl::synchronousDel( const QString& path, bool setLastErrorCode, bool isDir ) +{ + const int oldErrorCode = m_lastErrorCode; + const QString oldErrorMsg = m_lastErrorMessage; + KURL url; + url.setPath( path ); + + // First ensure that all dirs have u+w permissions, + // otherwise we won't be able to delete files in them (#130780). + if ( isDir ) { + kdDebug() << k_funcinfo << "chmod'ing " << url << endl; + KFileItem fileItem( url, "inode/directory", KFileItem::Unknown ); + KFileItemList fileItemList; + fileItemList.append( &fileItem ); + KIO::ChmodJob* chmodJob = KIO::chmod( fileItemList, 0200, 0200, QString::null, QString::null, true /*recursive*/, false /*showProgressInfo*/ ); + connect( chmodJob, SIGNAL( result(KIO::Job *) ), + this, SLOT( jobFinished(KIO::Job *) ) ); + qApp->eventLoop()->enterLoop(); + } + + kdDebug() << k_funcinfo << "deleting " << url << endl; + KIO::DeleteJob *job = KIO::del( url, false, false ); + connect( job, SIGNAL( result(KIO::Job *) ), + this, SLOT( jobFinished(KIO::Job *) ) ); + qApp->eventLoop()->enterLoop(); + bool ok = m_lastErrorCode == 0; + if ( !setLastErrorCode ) { + m_lastErrorCode = oldErrorCode; + m_lastErrorMessage = oldErrorMsg; + } + return ok; +} + +bool TrashImpl::emptyTrash() +{ + kdDebug() << k_funcinfo << endl; + // The naive implementation "delete info and files in every trash directory" + // breaks when deleted directories contain files owned by other users. + // We need to ensure that the .trashinfo file is only removed when the + // corresponding files could indeed be removed. + + const TrashedFileInfoList fileInfoList = list(); + + TrashedFileInfoList::const_iterator it = fileInfoList.begin(); + const TrashedFileInfoList::const_iterator end = fileInfoList.end(); + for ( ; it != end ; ++it ) { + const TrashedFileInfo& info = *it; + const QString filesPath = info.physicalPath; + if ( synchronousDel( filesPath, true, true ) ) { + QFile::remove( infoPath( info.trashId, info.fileId ) ); + } // else error code is set + } + fileRemoved(); + + return m_lastErrorCode == 0; +} + +TrashImpl::TrashedFileInfoList TrashImpl::list() +{ + // Here we scan for trash directories unconditionally. This allows + // noticing plugged-in [e.g. removeable] devices, or new mounts etc. + scanTrashDirectories(); + + TrashedFileInfoList lst; + // For each known trash directory... + TrashDirMap::const_iterator it = m_trashDirectories.begin(); + for ( ; it != m_trashDirectories.end() ; ++it ) { + const int trashId = it.key(); + QString infoPath = it.data(); + infoPath += "/info"; + // Code taken from kio_file + QStrList entryNames = listDir( infoPath ); + //char path_buffer[PATH_MAX]; + //getcwd(path_buffer, PATH_MAX - 1); + //if ( chdir( infoPathEnc ) ) + // continue; + QStrListIterator entryIt( entryNames ); + for (; entryIt.current(); ++entryIt) { + QString fileName = QFile::decodeName( *entryIt ); + if ( fileName == "." || fileName == ".." ) + continue; + if ( !fileName.endsWith( ".trashinfo" ) ) { + kdWarning() << "Invalid info file found in " << infoPath << " : " << fileName << endl; + continue; + } + fileName.truncate( fileName.length() - 10 ); + + TrashedFileInfo info; + if ( infoForFile( trashId, fileName, info ) ) + lst << info; + } + } + return lst; +} + +// Returns the entries in a given directory - including "." and ".." +QStrList TrashImpl::listDir( const QString& physicalPath ) +{ + const QCString physicalPathEnc = QFile::encodeName( physicalPath ); + kdDebug() << k_funcinfo << "listing " << physicalPath << endl; + QStrList entryNames; + DIR *dp = opendir( physicalPathEnc ); + if ( dp == 0 ) + return entryNames; + KDE_struct_dirent *ep; + while ( ( ep = KDE_readdir( dp ) ) != 0L ) + entryNames.append( ep->d_name ); + closedir( dp ); + return entryNames; +} + +bool TrashImpl::infoForFile( int trashId, const QString& fileId, TrashedFileInfo& info ) +{ + kdDebug() << k_funcinfo << trashId << " " << fileId << endl; + info.trashId = trashId; // easy :) + info.fileId = fileId; // equally easy + info.physicalPath = filesPath( trashId, fileId ); + return readInfoFile( infoPath( trashId, fileId ), info, trashId ); +} + +bool TrashImpl::readInfoFile( const QString& infoPath, TrashedFileInfo& info, int trashId ) +{ + KSimpleConfig cfg( infoPath, true ); + if ( !cfg.hasGroup( "Trash Info" ) ) { + error( KIO::ERR_CANNOT_OPEN_FOR_READING, infoPath ); + return false; + } + cfg.setGroup( "Trash Info" ); + info.origPath = KURL::decode_string( cfg.readEntry( "Path" ), m_mibEnum ); + if ( info.origPath.isEmpty() ) + return false; // path is mandatory... + if ( trashId == 0 ) + Q_ASSERT( info.origPath[0] == '/' ); + else { + const QString topdir = topDirectoryPath( trashId ); // includes trailing slash + info.origPath.prepend( topdir ); + } + QString line = cfg.readEntry( "DeletionDate" ); + if ( !line.isEmpty() ) { + info.deletionDate = QDateTime::fromString( line, Qt::ISODate ); + } + return true; +} + +QString TrashImpl::physicalPath( int trashId, const QString& fileId, const QString& relativePath ) +{ + QString filePath = filesPath( trashId, fileId ); + if ( !relativePath.isEmpty() ) { + filePath += "/"; + filePath += relativePath; + } + return filePath; +} + +void TrashImpl::error( int e, const QString& s ) +{ + if ( e ) + kdDebug() << k_funcinfo << e << " " << s << endl; + m_lastErrorCode = e; + m_lastErrorMessage = s; +} + +bool TrashImpl::isEmpty() const +{ + // For each known trash directory... + if ( !m_trashDirectoriesScanned ) + scanTrashDirectories(); + TrashDirMap::const_iterator it = m_trashDirectories.begin(); + for ( ; it != m_trashDirectories.end() ; ++it ) { + QString infoPath = it.data(); + infoPath += "/info"; + + DIR *dp = opendir( QFile::encodeName( infoPath ) ); + if ( dp ) + { + struct dirent *ep; + ep = readdir( dp ); + ep = readdir( dp ); // ignore '.' and '..' dirent + ep = readdir( dp ); // look for third file + closedir( dp ); + if ( ep != 0 ) { + //kdDebug() << ep->d_name << " in " << infoPath << " -> not empty" << endl; + return false; // not empty + } + } + } + return true; +} + +void TrashImpl::fileAdded() +{ + m_config.setGroup( "Status" ); + if ( m_config.readBoolEntry( "Empty", true ) == true ) { + m_config.writeEntry( "Empty", false ); + m_config.sync(); + } + // The apps showing the trash (e.g. kdesktop) will be notified + // of this change when KDirNotify::FilesAdded("trash:/") is emitted, + // which will be done by the job soon after this. +} + +void TrashImpl::fileRemoved() +{ + if ( isEmpty() ) { + m_config.setGroup( "Status" ); + m_config.writeEntry( "Empty", true ); + m_config.sync(); + } + // The apps showing the trash (e.g. kdesktop) will be notified + // of this change when KDirNotify::FilesRemoved(...) is emitted, + // which will be done by the job soon after this. +} + +int TrashImpl::findTrashDirectory( const QString& origPath ) +{ + kdDebug() << k_funcinfo << origPath << endl; + // First check if same device as $HOME, then we use the home trash right away. + KDE_struct_stat buff; + if ( KDE_lstat( QFile::encodeName( origPath ), &buff ) == 0 + && buff.st_dev == m_homeDevice ) + return 0; + + QString mountPoint = KIO::findPathMountPoint( origPath ); + const QString trashDir = trashForMountPoint( mountPoint, true ); + kdDebug() << "mountPoint=" << mountPoint << " trashDir=" << trashDir << endl; + if ( trashDir.isEmpty() ) + return 0; // no trash available on partition + int id = idForTrashDirectory( trashDir ); + if ( id > -1 ) { + kdDebug() << " known with id " << id << endl; + return id; + } + // new trash dir found, register it + // but we need stability in the trash IDs, so that restoring or asking + // for properties works even kio_trash gets killed because idle. +#if 0 + kdDebug() << k_funcinfo << "found " << trashDir << endl; + m_trashDirectories.insert( ++m_lastId, trashDir ); + if ( !mountPoint.endsWith( "/" ) ) + mountPoint += '/'; + m_topDirectories.insert( m_lastId, mountPoint ); + return m_lastId; +#endif + scanTrashDirectories(); + return idForTrashDirectory( trashDir ); +} + +void TrashImpl::scanTrashDirectories() const +{ + const KMountPoint::List lst = KMountPoint::currentMountPoints(); + for ( KMountPoint::List::ConstIterator it = lst.begin() ; it != lst.end() ; ++it ) { + const QCString str = (*it)->mountType().latin1(); + // Skip pseudo-filesystems, there's no chance we'll find a .Trash on them :) + // ## Maybe we should also skip readonly filesystems + if ( str != "proc" && str != "devfs" && str != "usbdevfs" && + str != "sysfs" && str != "devpts" && str != "subfs" /* #96259 */ && + str != "autofs" /* #101116 */ ) { + QString topdir = (*it)->mountPoint(); + QString trashDir = trashForMountPoint( topdir, false ); + if ( !trashDir.isEmpty() ) { + // OK, trashDir is a valid trash directory. Ensure it's registered. + int trashId = idForTrashDirectory( trashDir ); + if ( trashId == -1 ) { + // new trash dir found, register it + m_trashDirectories.insert( ++m_lastId, trashDir ); + kdDebug() << k_funcinfo << "found " << trashDir << " gave it id " << m_lastId << endl; + if ( !topdir.endsWith( "/" ) ) + topdir += '/'; + m_topDirectories.insert( m_lastId, topdir ); + } + } + } + } + m_trashDirectoriesScanned = true; +} + +TrashImpl::TrashDirMap TrashImpl::trashDirectories() const +{ + if ( !m_trashDirectoriesScanned ) + scanTrashDirectories(); + return m_trashDirectories; +} + +TrashImpl::TrashDirMap TrashImpl::topDirectories() const +{ + if ( !m_trashDirectoriesScanned ) + scanTrashDirectories(); + return m_topDirectories; +} + +QString TrashImpl::trashForMountPoint( const QString& topdir, bool createIfNeeded ) const +{ + // (1) Administrator-created $topdir/.Trash directory + + const QString rootTrashDir = topdir + "/.Trash"; + const QCString rootTrashDir_c = QFile::encodeName( rootTrashDir ); + // Can't use QFileInfo here since we need to test for the sticky bit + uid_t uid = getuid(); + KDE_struct_stat buff; + const uint requiredBits = S_ISVTX; // Sticky bit required + if ( KDE_lstat( rootTrashDir_c, &buff ) == 0 ) { + if ( (S_ISDIR(buff.st_mode)) // must be a dir + && (!S_ISLNK(buff.st_mode)) // not a symlink + && ((buff.st_mode & requiredBits) == requiredBits) + && (::access(rootTrashDir_c, W_OK)) + ) { + const QString trashDir = rootTrashDir + "/" + QString::number( uid ); + const QCString trashDir_c = QFile::encodeName( trashDir ); + if ( KDE_lstat( trashDir_c, &buff ) == 0 ) { + if ( (buff.st_uid == uid) // must be owned by user + && (S_ISDIR(buff.st_mode)) // must be a dir + && (!S_ISLNK(buff.st_mode)) // not a symlink + && (buff.st_mode & 0777) == 0700 ) { // rwx for user + return trashDir; + } + kdDebug() << "Directory " << trashDir << " exists but didn't pass the security checks, can't use it" << endl; + } + else if ( createIfNeeded && initTrashDirectory( trashDir_c ) ) { + return trashDir; + } + } else { + kdDebug() << "Root trash dir " << rootTrashDir << " exists but didn't pass the security checks, can't use it" << endl; + } + } + + // (2) $topdir/.Trash-$uid + const QString trashDir = topdir + "/.Trash-" + QString::number( uid ); + const QCString trashDir_c = QFile::encodeName( trashDir ); + if ( KDE_lstat( trashDir_c, &buff ) == 0 ) + { + if ( (buff.st_uid == uid) // must be owned by user + && (S_ISDIR(buff.st_mode)) // must be a dir + && (!S_ISLNK(buff.st_mode)) // not a symlink + && ((buff.st_mode & 0777) == 0700) ) { // rwx for user, ------ for group and others + + if ( checkTrashSubdirs( trashDir_c ) ) + return trashDir; + } + kdDebug() << "Directory " << trashDir << " exists but didn't pass the security checks, can't use it" << endl; + // Exists, but not useable + return QString::null; + } + if ( createIfNeeded && initTrashDirectory( trashDir_c ) ) { + return trashDir; + } + return QString::null; +} + +int TrashImpl::idForTrashDirectory( const QString& trashDir ) const +{ + // If this is too slow we can always use a reverse map... + TrashDirMap::ConstIterator it = m_trashDirectories.begin(); + for ( ; it != m_trashDirectories.end() ; ++it ) { + if ( it.data() == trashDir ) { + return it.key(); + } + } + return -1; +} + +bool TrashImpl::initTrashDirectory( const QCString& trashDir_c ) const +{ + if ( ::mkdir( trashDir_c, 0700 ) != 0 ) + return false; + // This trash dir will be useable only if the directory is owned by user. + // In theory this is the case, but not on e.g. USB keys... + uid_t uid = getuid(); + KDE_struct_stat buff; + if ( KDE_lstat( trashDir_c, &buff ) != 0 ) + return false; // huh? + if ( (buff.st_uid == uid) // must be owned by user + && ((buff.st_mode & 0777) == 0700) ) { // rwx for user, --- for group and others + + return checkTrashSubdirs( trashDir_c ); + + } else { + kdDebug() << trashDir_c << " just created, by it doesn't have the right permissions, must be a FAT partition. Removing it again." << endl; + // Not good, e.g. USB key. Delete again. + // I'm paranoid, it would be better to find a solution that allows + // to trash directly onto the USB key, but I don't see how that would + // pass the security checks. It would also make the USB key appears as + // empty when it's in fact full... + ::rmdir( trashDir_c ); + return false; + } + return true; +} + +bool TrashImpl::checkTrashSubdirs( const QCString& trashDir_c ) const +{ + // testDir currently works with a QString - ## optimize + QString trashDir = QFile::decodeName( trashDir_c ); + const QString info = trashDir + "/info"; + if ( testDir( info ) != 0 ) + return false; + const QString files = trashDir + "/files"; + if ( testDir( files ) != 0 ) + return false; + return true; +} + +QString TrashImpl::trashDirectoryPath( int trashId ) const +{ + // Never scanned for trash dirs? (This can happen after killing kio_trash + // and reusing a directory listing from the earlier instance.) + if ( !m_trashDirectoriesScanned ) + scanTrashDirectories(); + Q_ASSERT( m_trashDirectories.contains( trashId ) ); + return m_trashDirectories[trashId]; +} + +QString TrashImpl::topDirectoryPath( int trashId ) const +{ + if ( !m_trashDirectoriesScanned ) + scanTrashDirectories(); + assert( trashId != 0 ); + Q_ASSERT( m_topDirectories.contains( trashId ) ); + return m_topDirectories[trashId]; +} + +// Helper method. Creates a URL with the format trash:/trashid-fileid or +// trash:/trashid-fileid/relativePath/To/File for a file inside a trashed directory. +KURL TrashImpl::makeURL( int trashId, const QString& fileId, const QString& relativePath ) +{ + KURL url; + url.setProtocol( "trash" ); + QString path = "/"; + path += QString::number( trashId ); + path += '-'; + path += fileId; + if ( !relativePath.isEmpty() ) { + path += '/'; + path += relativePath; + } + url.setPath( path ); + return url; +} + +// Helper method. Parses a trash URL with the URL scheme defined in makeURL. +// The trash:/ URL itself isn't parsed here, must be caught by the caller before hand. +bool TrashImpl::parseURL( const KURL& url, int& trashId, QString& fileId, QString& relativePath ) +{ + if ( url.protocol() != "trash" ) + return false; + const QString path = url.path(); + int start = 0; + if ( path[0] == '/' ) // always true I hope + start = 1; + int slashPos = path.find( '-', 0 ); // don't match leading slash + if ( slashPos <= 0 ) + return false; + bool ok = false; + trashId = path.mid( start, slashPos - start ).toInt( &ok ); + Q_ASSERT( ok ); + if ( !ok ) + return false; + start = slashPos + 1; + slashPos = path.find( '/', start ); + if ( slashPos <= 0 ) { + fileId = path.mid( start ); + relativePath = QString::null; + return true; + } + fileId = path.mid( start, slashPos - start ); + relativePath = path.mid( slashPos + 1 ); + return true; +} + +#include "trashimpl.moc" diff --git a/kioslave/trash/trashimpl.h b/kioslave/trash/trashimpl.h new file mode 100644 index 000000000..2b16ec67e --- /dev/null +++ b/kioslave/trash/trashimpl.h @@ -0,0 +1,182 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <faure@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 as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + 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. +*/ + +#ifndef TRASHIMPL_H +#define TRASHIMPL_H + +#include <kio/jobclasses.h> +#include <ksimpleconfig.h> + +#include <qstring.h> +#include <qdatetime.h> +#include <qmap.h> +#include <qvaluelist.h> +#include <qstrlist.h> +#include <assert.h> + +/** + * Implementation of all low-level operations done by kio_trash + * The structure of the trash directory follows the freedesktop.org standard <TODO URL> + */ +class TrashImpl : public QObject +{ + Q_OBJECT +public: + TrashImpl(); + + /// Check the "home" trash directory + /// This MUST be called before doing anything else + bool init(); + + /// Create info for a file to be trashed + /// Returns trashId and fileId + /// The caller is then responsible for actually trashing the file + bool createInfo( const QString& origPath, int& trashId, QString& fileId ); + + /// Delete info file for a file to be trashed + /// Usually used for undoing what createInfo did if trashing failed + bool deleteInfo( int trashId, const QString& fileId ); + + /// Moving a file or directory into the trash. The ids come from createInfo. + bool moveToTrash( const QString& origPath, int trashId, const QString& fileId ); + + /// Moving a file or directory out of the trash. The ids come from createInfo. + bool moveFromTrash( const QString& origPath, int trashId, const QString& fileId, const QString& relativePath ); + + /// Copying a file or directory into the trash. The ids come from createInfo. + bool copyToTrash( const QString& origPath, int trashId, const QString& fileId ); + + /// Copying a file or directory out of the trash. The ids come from createInfo. + bool copyFromTrash( const QString& origPath, int trashId, const QString& fileId, const QString& relativePath ); + + /// Create a top-level trashed directory + //bool mkdir( int trashId, const QString& fileId, int permissions ); + + /// Get rid of a trashed file + bool del( int trashId, const QString& fileId ); + + /// Empty trash, i.e. delete all trashed files + bool emptyTrash(); + + /// Return true if the trash is empty + bool isEmpty() const; + + struct TrashedFileInfo { + int trashId; // for the url + QString fileId; // for the url + QString physicalPath; // for stat'ing etc. + QString origPath; // from info file + QDateTime deletionDate; // from info file + }; + /// List trashed files + typedef QValueList<TrashedFileInfo> TrashedFileInfoList; + TrashedFileInfoList list(); + + /// Return the info for a given trashed file + bool infoForFile( int trashId, const QString& fileId, TrashedFileInfo& info ); + + /// Return the physicalPath for a given trashed file - helper method which + /// encapsulates the call to infoForFile. Don't use if you need more info from TrashedFileInfo. + QString physicalPath( int trashId, const QString& fileId, const QString& relativePath ); + + /// Move data from the old trash system to the new one + void migrateOldTrash(); + + /// KIO error code + int lastErrorCode() const { return m_lastErrorCode; } + QString lastErrorMessage() const { return m_lastErrorMessage; } + + QStrList listDir( const QString& physicalPath ); + + static KURL makeURL( int trashId, const QString& fileId, const QString& relativePath ); + static bool parseURL( const KURL& url, int& trashId, QString& fileId, QString& relativePath ); + + typedef QMap<int, QString> TrashDirMap; + /// @internal This method is for TestTrash only. Home trash is included (id 0). + TrashDirMap trashDirectories() const; + /// @internal This method is for TestTrash only. No entry with id 0. + TrashDirMap topDirectories() const; + +private: + /// Helper method. Moves a file or directory using the appropriate method. + bool move( const QString& src, const QString& dest ); + bool copy( const QString& src, const QString& dest ); + /// Helper method. Tries to call ::rename(src,dest) and does error handling. + bool directRename( const QString& src, const QString& dest ); + + void fileAdded(); + void fileRemoved(); + + // Warning, returns error code, not a bool + int testDir( const QString& name ) const; + void error( int e, const QString& s ); + + bool readInfoFile( const QString& infoPath, TrashedFileInfo& info, int trashId ); + + QString infoPath( int trashId, const QString& fileId ) const; + QString filesPath( int trashId, const QString& fileId ) const; + + /// Find the trash dir to use for a given file to delete, based on original path + int findTrashDirectory( const QString& origPath ); + + QString trashDirectoryPath( int trashId ) const; + QString topDirectoryPath( int trashId ) const; + + bool synchronousDel( const QString& path, bool setLastErrorCode, bool isDir ); + + void scanTrashDirectories() const; + + int idForTrashDirectory( const QString& trashDir ) const; + bool initTrashDirectory( const QCString& trashDir_c ) const; + bool checkTrashSubdirs( const QCString& trashDir_c ) const; + QString trashForMountPoint( const QString& topdir, bool createIfNeeded ) const; + static QString makeRelativePath( const QString& topdir, const QString& path ); + +private slots: + void jobFinished(KIO::Job *job); + +private: + /// Last error code stored in class to simplify API. + /// Note that this means almost no method can be const. + int m_lastErrorCode; + QString m_lastErrorMessage; + + enum { InitToBeDone, InitOK, InitError } m_initStatus; + + // A "trash directory" is a physical directory on disk, + // e.g. $HOME/.local/share/Trash/$uid or /mnt/foo/.Trash/$uid + // It has an id (number) and a path. + // The home trash has id 0. + mutable TrashDirMap m_trashDirectories; // id -> path of trash directory + mutable TrashDirMap m_topDirectories; // id -> $topdir of partition + mutable int m_lastId; + dev_t m_homeDevice; + mutable bool m_trashDirectoriesScanned; + int m_mibEnum; + + KSimpleConfig m_config; + + // We don't cache any data related to the trashed files. + // Another kioslave could change that behind our feet. + // If we want to start caching data - and avoiding some race conditions -, + // we should turn this class into a kded module and use DCOP to talk to it + // from the kioslave. +}; + +#endif |