diff options
Diffstat (limited to 'tdeioslave')
476 files changed, 68146 insertions, 0 deletions
diff --git a/tdeioslave/CMakeLists.txt b/tdeioslave/CMakeLists.txt new file mode 100644 index 000000000..56b05c852 --- /dev/null +++ b/tdeioslave/CMakeLists.txt @@ -0,0 +1,37 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include( ConfigureChecks.cmake ) + +add_subdirectory( about ) +add_subdirectory( cgi ) +add_subdirectory( floppy ) +add_subdirectory( filter ) +add_subdirectory( fish ) +add_subdirectory( info ) +add_subdirectory( mac ) +add_subdirectory( man ) +add_subdirectory( nfs ) +add_subdirectory( nntp ) +add_subdirectory( pop3 ) +add_subdirectory( smtp ) +add_subdirectory( sftp ) +add_subdirectory( tar ) +add_subdirectory( finger ) +add_subdirectory( thumbnail ) +add_subdirectory( settings ) +add_subdirectory( trash ) +add_subdirectory( media ) +add_subdirectory( remote ) +add_subdirectory( home ) +add_subdirectory( system ) +tde_conditional_add_subdirectory( WITH_LDAP ldap ) +tde_conditional_add_subdirectory( WITH_SAMBA smb ) diff --git a/tdeioslave/ConfigureChecks.cmake b/tdeioslave/ConfigureChecks.cmake new file mode 100644 index 000000000..8f0783133 --- /dev/null +++ b/tdeioslave/ConfigureChecks.cmake @@ -0,0 +1,20 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +if( WITH_SASL ) + check_include_file( "sasl/sasl.h" HAVE_SASL_SASL_H ) + check_library_exists( sasl2 sasl_client_init "" HAVE_LIBSASL2 ) + if( HAVE_SASL_SASL_H AND HAVE_LIBSASL2 ) + set( SASL_LIBRARIES sasl2 ) + else( ) + tde_message_fatal( "sasl2 are requested, but not found on your system" ) + endif( ) +endif( ) diff --git a/tdeioslave/DEBUG.howto b/tdeioslave/DEBUG.howto new file mode 100644 index 000000000..6218f2fba --- /dev/null +++ b/tdeioslave/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 'tdelauncher' via DCOP for a slave. If 'tdelauncher' does +not have an idle slave ready, it will ask tdeinit to start a new one. +tdeinit 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 tdelauncher 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: + + TDE_SLAVE_DEBUG_WAIT=http tdeinit + +This will restart 'tdeinit' and 'tdelauncher'. + +When your application now requests a http slave, the slave will be started +by tdeinit, 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 tdeinit you will get the following +message: + +tdeinit: Suspending process +tdeinit: 'gdb tdeinit 16779' to debug +tdeinit: 'kill -SIGCONT 16779' to continue + +You can now debug your slave by typing (or pasting) 'gdb tdeinit 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 tdelauncher run all https io-slaves via valgrind: + + TDE_SLAVE_VALGRIND=https tdeinit + +The valgrind output will appear as the stderr output of the tdeinit process. +The $VALGRIND_OPTS environment variable can be used to pass options to valgrind. +If you want to use a different skin: + + TDE_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 +$TDEDIR/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 +tdeioslaverc: + +[SMB] +DebugLevel=100 + +This will print additional debug info to the stderr of your tdeinit process, +which typically ends up in ~/.X.err or ~/.xsession-errors + +Happy debugging. diff --git a/tdeioslave/DESIGN b/tdeioslave/DESIGN new file mode 100644 index 000000000..9b62f3c34 --- /dev/null +++ b/tdeioslave/DESIGN @@ -0,0 +1,43 @@ +What is a tdeioslave you ask yourself? + +A tdeioslave 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 tdeioslaves, +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 tdeioslave is for you. + +That's nice, but how do I use it? + +Basically, you create "jobs" by calling a public TDEIO::blah method +(the correct prototypes, etc, are in tdeio/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 tdeio/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/tdeioslave/Makefile.am b/tdeioslave/Makefile.am new file mode 100644 index 000000000..d7e98a4bc --- /dev/null +++ b/tdeioslave/Makefile.am @@ -0,0 +1,11 @@ +if include_tdeioslave_ldap +LDAP_SUBDIR=ldap +endif + +if include_tdeioslave_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/tdeioslave/about/CMakeLists.txt b/tdeioslave/about/CMakeLists.txt new file mode 100644 index 000000000..aaa2a5aff --- /dev/null +++ b/tdeioslave/about/CMakeLists.txt @@ -0,0 +1,36 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES about.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_about (module) ######################## + +set( target kio_about ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kio_about.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/about/Makefile.am b/tdeioslave/about/Makefile.am new file mode 100644 index 000000000..18bffeeec --- /dev/null +++ b/tdeioslave/about/Makefile.am @@ -0,0 +1,21 @@ +## Makefile.am of tdebase/tdeioslave/about + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +####### 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)/tdeio_about.pot diff --git a/tdeioslave/about/about.protocol b/tdeioslave/about/about.protocol new file mode 100644 index 000000000..bc685bfe5 --- /dev/null +++ b/tdeioslave/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/tdeioslave/about/kio_about.cpp b/tdeioslave/about/kio_about.cpp new file mode 100644 index 000000000..29fba8e4a --- /dev/null +++ b/tdeioslave/about/kio_about.cpp @@ -0,0 +1,76 @@ +/* This file is part of the KDE libraries + + Copyright (c) 2002 John Firebaugh <[email protected]> + + 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 <tqstring.h> +#include <kinstance.h> +#include <kurl.h> + +using namespace TDEIO; + +AboutProtocol::AboutProtocol(const TQCString &pool_socket, const TQCString &app_socket) + : SlaveBase("about", pool_socket, app_socket) +{ +} + +AboutProtocol::~AboutProtocol() +{ +} + +void AboutProtocol::get( const KURL& ) +{ + TQByteArray output; + + TQTextStream os( output, IO_WriteOnly ); + os.setEncoding( TQTextStream::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 ) { + + TDEInstance 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/tdeioslave/about/kio_about.h b/tdeioslave/about/kio_about.h new file mode 100644 index 000000000..eacaf09dc --- /dev/null +++ b/tdeioslave/about/kio_about.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE libraries + + Copyright (c) 2002 John Firebaugh <[email protected]> + + 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 <tqcstring.h> + +#include <tdeio/global.h> +#include <tdeio/slavebase.h> + + +class AboutProtocol : public TDEIO::SlaveBase +{ +public: + AboutProtocol(const TQCString &pool_socket, const TQCString &app_socket); + virtual ~AboutProtocol(); + + virtual void get(const KURL& url); + virtual void mimetype(const KURL& url); +}; + +#endif diff --git a/tdeioslave/cgi/CMakeLists.txt b/tdeioslave/cgi/CMakeLists.txt new file mode 100644 index 000000000..87923f86c --- /dev/null +++ b/tdeioslave/cgi/CMakeLists.txt @@ -0,0 +1,39 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +add_subdirectory( kcmcgi ) + + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES cgi.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_cgi (module) ########################## + +set( target kio_cgi ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES cgi.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/cgi/Makefile.am b/tdeioslave/cgi/Makefile.am new file mode 100644 index 000000000..cc71753e9 --- /dev/null +++ b/tdeioslave/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/tdeioslave/cgi/README b/tdeioslave/cgi/README new file mode 100644 index 000000000..d68fc9d2c --- /dev/null +++ b/tdeioslave/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 diff --git a/tdeioslave/cgi/cgi.cpp b/tdeioslave/cgi/cgi.cpp new file mode 100644 index 000000000..317b1610a --- /dev/null +++ b/tdeioslave/cgi/cgi.cpp @@ -0,0 +1,274 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <[email protected]> + Copyright 2006 Michael Pyne <[email protected]> + + 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 <tqdir.h> +#include <tqregexp.h> + +#include <kdebug.h> +#include <kprocess.h> +#include <kstandarddirs.h> +#include <kinstance.h> +#include <klocale.h> +#include <tdeconfig.h> + +#include "cgi.h" + +using namespace TDEIO; + +CgiProtocol::CgiProtocol( const TQCString &pool, const TQCString &app ) + : SlaveBase( "cgi", pool, app ) +{ + kdDebug(7124) << "CgiProtocol::CgiProtocol" << endl; + + TDEConfig cfg( "kcmcgirc" ); + cfg.setGroup( "General" ); + mCgiPaths = cfg.readListEntry( "Paths" ); +} + +CgiProtocol::~CgiProtocol() +{ + kdDebug(7124) << "CgiProtocol::~CgiProtocol" << endl; +} + +/** + * Search in reverse order through a TQByteArray for a given character. The position + * of the character is returned, or -1 if it was not found. + */ +static int qByteArrayFindRev( const TQByteArray &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 TQCString::mid() + */ +static TQCString extractQCString( const TQByteArray &ba, uint start, uint len = 0xffffffff ) +{ + uint realLen = len; + + if ( ( ba.size() - start ) < len ) + realLen = ba.size() - start; + +// return TQCString( &ba[ start ], realLen + 1 ); + return TQCString( ba ).mid(start, realLen + 1); +} + +/** + * Search through a TQByteArray for a given string. The position of the string + * is returned, or -1 if it was not found. + */ +static int qByteArrayFindStr( const TQByteArray &ba, const char *str ) +{ + int strLen = tqstrlen( str ); + int searchLen = ba.size() - strLen; + + for ( int i = 0; i <= searchLen; ++i ) { + TQCString 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 + TQCString protocol = "SERVER_PROTOCOL=HTTP"; + putenv( protocol.data() ); + + TQCString requestMethod = "REQUEST_METHOD=GET"; + putenv( requestMethod.data() ); + + TQCString query = url.query().mid( 1 ).local8Bit(); + query.prepend( "QUERY_STRING=" ); + putenv( query.data() ); + + TQString path = url.path(); + + TQString file; + + int pos = path.findRev('/'); + if ( pos >= 0 ) file = path.mid( pos + 1 ); + else file = path; + + TQString cmd; + + bool stripHeader = false; + bool forwardFile = true; + + TQStringList::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; + + TQCString filepath = TQFile::encodeName( path ); + + fd = fopen( filepath.data(), "r" ); + + if ( !fd ) { + kdDebug(7124) << "Error opening '" << filepath << "'" << endl; + error( TDEIO::ERR_CANNOT_OPEN_FOR_READING, filepath ); + return; + } + } else { + kdDebug(7124) << "Cmd: " << cmd << endl; + + fd = popen( TQFile::encodeName(TDEProcess::quote( cmd )).data(), "r" ); + + if ( !fd ) { + kdDebug(7124) << "Error running '" << cmd << "'" << endl; + error( TDEIO::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 ) { + TQByteArray 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 + + TQCString 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 { + TQByteArray 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 ) +{ + TDEInstance 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/tdeioslave/cgi/cgi.h b/tdeioslave/cgi/cgi.h new file mode 100644 index 000000000..165b34a47 --- /dev/null +++ b/tdeioslave/cgi/cgi.h @@ -0,0 +1,48 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <[email protected]> + + 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 <tqobject.h> + +#include <tdeio/slavebase.h> + +class TDEProcess; + +/*! + This class implements an ioslave for viewing CGI script output without the + need to run a web server. +*/ +class CgiProtocol : public TDEIO::SlaveBase +{ + public: + CgiProtocol( const TQCString &pool, const TQCString &app ); + virtual ~CgiProtocol(); + + virtual void get( const KURL& url ); + +// virtual void mimetype( const KURL& url ); + + protected: +// TQCString errorMessage(); + + private: + TQStringList mCgiPaths; +}; + +#endif diff --git a/tdeioslave/cgi/cgi.protocol b/tdeioslave/cgi/cgi.protocol new file mode 100644 index 000000000..eb6c45217 --- /dev/null +++ b/tdeioslave/cgi/cgi.protocol @@ -0,0 +1,8 @@ +[Protocol] +exec=kio_cgi +protocol=cgi +input=none +output=filesystem +reading=true +DocPath=tdeioslave/cgi.html +Icon=html diff --git a/tdeioslave/cgi/kcmcgi/CMakeLists.txt b/tdeioslave/cgi/kcmcgi/CMakeLists.txt new file mode 100644 index 000000000..09354b919 --- /dev/null +++ b/tdeioslave/cgi/kcmcgi/CMakeLists.txt @@ -0,0 +1,36 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES kcmcgi.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) + + +##### kcm_cgi (module) ########################## + +set( target kcm_cgi ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kcmcgi.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/cgi/kcmcgi/Makefile.am b/tdeioslave/cgi/kcmcgi/Makefile.am new file mode 100644 index 000000000..32f4be3ea --- /dev/null +++ b/tdeioslave/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 = -ltdeui $(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/tdeioslave/cgi/kcmcgi/kcmcgi.cpp b/tdeioslave/cgi/kcmcgi/kcmcgi.cpp new file mode 100644 index 000000000..9f270374a --- /dev/null +++ b/tdeioslave/cgi/kcmcgi/kcmcgi.cpp @@ -0,0 +1,151 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <[email protected]> + + 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 <tdeconfig.h> +#include <klocale.h> +#include <kglobal.h> +#include <kaboutdata.h> +#include <tdefiledialog.h> + +#include <tqlayout.h> +#include <tqlistbox.h> +#include <tqpushbutton.h> +#include <tqgroupbox.h> +#include <tqhbox.h> + +#include "kcmcgi.h" +#include "kcmcgi.moc" + +extern "C" +{ + KDE_EXPORT TDECModule *create_cgi( TQWidget *parent, const char * ) + { + TDEGlobal::locale()->insertCatalogue("kcmcgi"); + return new KCMCgi( parent, "kcmcgi" ); + } +} + + +KCMCgi::KCMCgi(TQWidget *parent, const char *name) + : TDECModule(parent, name) +{ + setButtons(Default|Apply); + + TQVBoxLayout *topLayout = new TQVBoxLayout(this, 0, KDialog::spacingHint()); + + TQGroupBox *topBox = new TQGroupBox( 1, Qt::Horizontal, i18n("Paths to Local CGI Programs"), this ); + topLayout->addWidget( topBox ); + + mListBox = new TQListBox( topBox ); + + TQHBox *buttonBox = new TQHBox( topBox ); + buttonBox->setSpacing( KDialog::spacingHint() ); + + mAddButton = new TQPushButton( i18n("Add..."), buttonBox ); + connect( mAddButton, TQT_SIGNAL( clicked() ), TQT_SLOT( addPath() ) ); + + mRemoveButton = new TQPushButton( i18n("Remove"), buttonBox ); + connect( mRemoveButton, TQT_SIGNAL( clicked() ), TQT_SLOT( removePath() ) ); + connect( mListBox, TQT_SIGNAL( clicked ( TQListBoxItem * )),this, TQT_SLOT( slotItemSelected( TQListBoxItem *))); + + mConfig = new TDEConfig("kcmcgirc"); + + load(); + updateButton(); + TDEAboutData *about = + new TDEAboutData( I18N_NOOP("kcmcgi"), + I18N_NOOP("CGI KIO Slave Control Module"), + 0, 0, TDEAboutData::License_GPL, + I18N_NOOP("(c) 2002 Cornelius Schumacher") ); + + about->addAuthor( "Cornelius Schumacher", 0, "[email protected]" ); + setAboutData(about); +} + +KCMCgi::~KCMCgi() +{ + delete mConfig; +} + +void KCMCgi::slotItemSelected( TQListBoxItem * ) +{ + updateButton(); +} + +void KCMCgi::updateButton() +{ + mRemoveButton->setEnabled( mListBox->selectedItem ()); +} + +void KCMCgi::defaults() +{ + mListBox->clear(); + updateButton(); +} + +void KCMCgi::save() +{ + TQStringList 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" ); + TQStringList paths = mConfig->readListEntry( "Paths" ); + + mListBox->insertStringList( paths ); +} + +void KCMCgi::addPath() +{ + TQString path = KFileDialog::getExistingDirectory( TQString::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(); +} + +TQString 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/tdeioslave/cgi/kcmcgi/kcmcgi.desktop b/tdeioslave/cgi/kcmcgi/kcmcgi.desktop new file mode 100644 index 000000000..0b4bb3b2c --- /dev/null +++ b/tdeioslave/cgi/kcmcgi/kcmcgi.desktop @@ -0,0 +1,231 @@ +[Desktop Entry] +Icon=run +Type=Application +Exec=tdecmshell kcmcgi +DocPath= +X-TDE-ModuleType=Library +X-TDE-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 TDE +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 TDE-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,tdeioslave,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;TDE;X-TDE-settings-webbrowsing; diff --git a/tdeioslave/cgi/kcmcgi/kcmcgi.h b/tdeioslave/cgi/kcmcgi/kcmcgi.h new file mode 100644 index 000000000..ccd2f2f3d --- /dev/null +++ b/tdeioslave/cgi/kcmcgi/kcmcgi.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2002 Cornelius Schumacher <[email protected]> + + 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 <tdecmodule.h> + +class TQListBox; +class TQPushButton; + +class TDEConfig; + +class KCMCgi : public TDECModule +{ + Q_OBJECT + public: + KCMCgi( TQWidget *parent = 0, const char *name = 0 ); + ~KCMCgi(); + + void load(); + void save(); + void defaults(); + TQString quickHelp() const; + + public slots: + + protected slots: + void addPath(); + void removePath(); + void slotItemSelected( TQListBoxItem * item ); + private: + void updateButton(); + TQListBox *mListBox; + TQPushButton *mAddButton; + TQPushButton *mRemoveButton; + + TDEConfig *mConfig; +}; + +#endif diff --git a/tdeioslave/configure.in.bot b/tdeioslave/configure.in.bot new file mode 100644 index 000000000..11b66748d --- /dev/null +++ b/tdeioslave/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/tdeioslave/configure.in.in b/tdeioslave/configure.in.in new file mode 100644 index 000000000..331a300b8 --- /dev/null +++ b/tdeioslave/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/tdeioslave/filter/CMakeLists.txt b/tdeioslave/filter/CMakeLists.txt new file mode 100644 index 000000000..4b0c3d36c --- /dev/null +++ b/tdeioslave/filter/CMakeLists.txt @@ -0,0 +1,36 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES gzip.protocol bzip.protocol bzip2.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_filter (module) ####################### + +set( target kio_filter ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES filter.cc + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/filter/Makefile.am b/tdeioslave/filter/Makefile.am new file mode 100644 index 000000000..f315064c8 --- /dev/null +++ b/tdeioslave/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/tdeioslave/filter/bzip.protocol b/tdeioslave/filter/bzip.protocol new file mode 100644 index 000000000..4d729f5ea --- /dev/null +++ b/tdeioslave/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=tdeioslave/bzip.html +Icon=ark diff --git a/tdeioslave/filter/bzip2.protocol b/tdeioslave/filter/bzip2.protocol new file mode 100644 index 000000000..c2b8d2c51 --- /dev/null +++ b/tdeioslave/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=tdeioslave/bzip2.html +Icon=ark diff --git a/tdeioslave/filter/configure.in.in b/tdeioslave/filter/configure.in.in new file mode 100644 index 000000000..5ef522b6f --- /dev/null +++ b/tdeioslave/filter/configure.in.in @@ -0,0 +1 @@ +AC_FIND_BZIP2 diff --git a/tdeioslave/filter/filter.cc b/tdeioslave/filter/filter.cc new file mode 100644 index 000000000..793921c6c --- /dev/null +++ b/tdeioslave/filter/filter.cc @@ -0,0 +1,167 @@ +/* +This file is part of KDE + + Copyright (C) 1999-2000 Waldo Bastian ([email protected]) + +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) +{ + TDEInstance 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 TQCString & protocol, const TQCString &pool, const TQCString &app ) + : TDEIO::SlaveBase( protocol, pool, app ) +{ + TQString mimetype = TQString::fromLatin1("application/x-") + TQString::fromLatin1(protocol); + filter = KFilterBase::findFilterByMimeType( mimetype ); + Q_ASSERT(filter); +} + +void FilterProtocol::get( const KURL & ) +{ + if (subURL.isEmpty()) + { + error( TDEIO::ERR_NO_SOURCE_PROTOCOL, mProtocol ); + return; + } + if (!filter) + { + error( TDEIO::ERR_INTERNAL, mProtocol ); + return; + } + needSubURLData(); + + filter->init(IO_ReadOnly); + + bool bNeedHeader = true; + bool bNeedMimetype = true; + bool bError = true; + int result; + + TQByteArray inputBuffer; + TQByteArray 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( TQByteArray() ); // Send EOF + } + + filter->terminate(); + + if (bError) + { + error(TDEIO::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( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("put")); +} + +void FilterProtocol::setSubURL(const KURL &url) +{ + subURL = url; +} + diff --git a/tdeioslave/filter/filter.h b/tdeioslave/filter/filter.h new file mode 100644 index 000000000..9457f0b73 --- /dev/null +++ b/tdeioslave/filter/filter.h @@ -0,0 +1,46 @@ +/* +This file is part of KDE + + Copyright (C) 2000 Waldo Bastian ([email protected]) + +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 <tqobject.h> +#include <tdeio/global.h> +#include <tdeio/slavebase.h> + +class FilterProtocol : public TQObject, public TDEIO::SlaveBase +{ +public: + FilterProtocol( const TQCString & protocol, const TQCString &pool, const TQCString &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/tdeioslave/filter/gzip.protocol b/tdeioslave/filter/gzip.protocol new file mode 100644 index 000000000..f0f57911c --- /dev/null +++ b/tdeioslave/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=tdeioslave/gzip.html +Icon=ark diff --git a/tdeioslave/finger/CMakeLists.txt b/tdeioslave/finger/CMakeLists.txt new file mode 100644 index 000000000..096b483d0 --- /dev/null +++ b/tdeioslave/finger/CMakeLists.txt @@ -0,0 +1,37 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES finger.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES kio_finger.pl kio_finger.css DESTINATION ${DATA_INSTALL_DIR}/tdeio_finger ) + + +##### kio_finger (module) ####################### + +set( target kio_finger ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kio_finger.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/finger/Makefile.am b/tdeioslave/finger/Makefile.am new file mode 100644 index 000000000..9c2792df3 --- /dev/null +++ b/tdeioslave/finger/Makefile.am @@ -0,0 +1,27 @@ +## Makfile.am for kio_finger +## Edit from Makefile.am of tdebase/tdeioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +####### 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)/tdeio_finger +EXTRA_DIST=$(kio_finger_data_DATA) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp -o $(podir)/tdeio_finger.pot diff --git a/tdeioslave/finger/finger.protocol b/tdeioslave/finger/finger.protocol new file mode 100644 index 000000000..8e2cdc80f --- /dev/null +++ b/tdeioslave/finger/finger.protocol @@ -0,0 +1,9 @@ +[Protocol] +exec=kio_finger +protocol=finger +input=none +output=stream +reading=true +defaultMimetype=text/html +DocPath=tdeioslave/finger.html +Icon=tdmconfig diff --git a/tdeioslave/finger/kio_finger.cpp b/tdeioslave/finger/kio_finger.cpp new file mode 100644 index 000000000..b223e1024 --- /dev/null +++ b/tdeioslave/finger/kio_finger.cpp @@ -0,0 +1,266 @@ + +/*************************************************************************** + kio_finger.cpp - description + ------------------- + begin : Sun Aug 12 2000 + copyright : (C) 2000 by Andreas Schlapbach + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <tqtextstream.h> +#include <tqdict.h> +#include <tqcstring.h> +#include <tqfile.h> +#include <tqregexp.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 TDEIO; + +static const TQString defaultRefreshRate = "60"; + +extern "C" +{ + KDE_EXPORT int kdemain( int argc, char **argv ) + { + TDEInstance 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 TQCString &pool_socket, const TQCString &app_socket) + : TQObject(), SlaveBase("finger", pool_socket, app_socket) +{ + myStdStream = new TQString(); + 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=""; + + TQString query = myURL->query(); + TQString refreshRate = defaultRefreshRate; + + //kdDebug() << "query: " << query << endl; + + // Check the validity of the query + + TQRegExp regExp("?refreshRate=[0-9][0-9]*", true, true); + if (query.contains(regExp)) { + //kdDebug() << "looks like a valid query" << endl; + TQRegExp regExp( "([0-9]+)" ); + regExp.search(query); + refreshRate = regExp.cap(0); + } + + //kdDebug() << "Refresh rate: " << refreshRate << endl; + + myTDEProcess = new TDEProcess(); + *myTDEProcess << *myPerlPath << *myFingerPerlScript + << *myFingerPath << *myFingerCSSFile + << refreshRate << myURL->host() << myURL->user() ; + + connect(myTDEProcess, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + //connect(myTDEProcess, TQT_SIGNAL(receivedStderr(TDEProcess *, char *, int)), + // this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + + myTDEProcess->start(TDEProcess::Block, TDEProcess::All); + + data(TQCString(TQString(*myStdStream).local8Bit())); + + data(TQByteArray()); + finished(); + + //clean up + + delete myTDEProcess; +} + + +/* ---------------------------------------------------------------------------------- */ + + +void FingerProtocol::slotGetStdOutput(TDEProcess* /* p */, char *s, int len) +{ + //kdDebug() << "void FingerProtocol::slotGetStdoutOutput()" << endl; + *myStdStream += TQString::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 TQString(TDEGlobal::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 TQString(TDEGlobal::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 TQString(locate("data","kio_finger/tdeio_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 TQString(locate("data","kio_finger/tdeio_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/tdeioslave/finger/kio_finger.css b/tdeioslave/finger/kio_finger.css new file mode 100644 index 000000000..06deb81aa --- /dev/null +++ b/tdeioslave/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/tdeioslave/finger/kio_finger.h b/tdeioslave/finger/kio_finger.h new file mode 100644 index 000000000..eac8ad291 --- /dev/null +++ b/tdeioslave/finger/kio_finger.h @@ -0,0 +1,64 @@ + +/*************************************************************************** + kio_finger.h - description + ------------------- + begin : Sun Aug 12 2000 + copyright : (C) 2000 by Andreas Schlapbach + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <tqstring.h> +#include <tqcstring.h> + +#include <kurl.h> +#include <kprocess.h> +#include <tdeio/global.h> +#include <tdeio/slavebase.h> + +class FingerProtocol : public TQObject, public TDEIO::SlaveBase +{ + Q_OBJECT + +public: + + FingerProtocol(const TQCString &pool_socket, const TQCString &app_socket); + virtual ~FingerProtocol(); + + virtual void mimetype(const KURL& url); + virtual void get(const KURL& url); + +private slots: + void slotGetStdOutput(TDEProcess*, char*, int); + +private: + KURL *myURL; + + QString *myPerlPath; + TQString *myFingerPath; + TQString *myFingerPerlScript; + TQString *myFingerCSSFile; + + QString *myStdStream; + + + TDEProcess *myTDEProcess; + + void getProgramPath(); + void parseCommandLine(const KURL& url); +}; + + +#endif diff --git a/tdeioslave/finger/kio_finger.pl b/tdeioslave/finger/kio_finger.pl new file mode 100644 index 000000000..8965ea523 --- /dev/null +++ b/tdeioslave/finger/kio_finger.pl @@ -0,0 +1,175 @@ +##!/usr/bin/perl +# +# Copyright Andreas Schlapbach, [email protected], 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/tdeioslave/fish/AUTHORS b/tdeioslave/fish/AUTHORS new file mode 100644 index 000000000..fc4f1567e --- /dev/null +++ b/tdeioslave/fish/AUTHORS @@ -0,0 +1 @@ +J�rg Walter <[email protected]> diff --git a/tdeioslave/fish/CMakeLists.txt b/tdeioslave/fish/CMakeLists.txt new file mode 100644 index 000000000..445ea418f --- /dev/null +++ b/tdeioslave/fish/CMakeLists.txt @@ -0,0 +1,45 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include( ConfigureChecks.cmake ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES fish.protocol nxfish.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_fish (module) ######################### + +add_custom_command( OUTPUT fishcode.h + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/genfishcode.pl ${CMAKE_CURRENT_SOURCE_DIR}/fish.pl > fishcode.h + DEPENDS fish.pl ) + +set_property( SOURCE fish.cpp APPEND PROPERTY OBJECT_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fishcode.h ) + +set( target kio_fish ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES fish.cpp + LINK tdeio-shared util + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/fish/COPYING b/tdeioslave/fish/COPYING new file mode 100644 index 000000000..2d08eab44 --- /dev/null +++ b/tdeioslave/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/tdeioslave/fish/ChangeLog b/tdeioslave/fish/ChangeLog new file mode 100644 index 000000000..7f621f3d4 --- /dev/null +++ b/tdeioslave/fish/ChangeLog @@ -0,0 +1,71 @@ +1.2.3 by Willy De la Court <[email protected]> + 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 <[email protected]> + Security fix tempfile should not be world readable + bugfix write to the file not the command + +1.2.1 by Willy De la Court <[email protected]> + 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 <[email protected]> + 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 <[email protected]> + 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/tdeioslave/fish/ConfigureChecks.cmake b/tdeioslave/fish/ConfigureChecks.cmake new file mode 100644 index 000000000..46ea1c181 --- /dev/null +++ b/tdeioslave/fish/ConfigureChecks.cmake @@ -0,0 +1,32 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +check_include_file( stropts.h HAVE_STROPTS ) +check_include_file( libutil.h HAVE_LIBUTIL_H ) +check_include_file( util.h HAVE_UTIL_H ) +check_include_file( pty.h HAVE_PTY_H ) + + +tde_save( CMAKE_REQUIRED_LIBRARIES ) +set( CMAKE_REQUIRED_LIBRARIES util ) + +check_c_source_runs(" + #include <pty.h> + int main(int argc, char* argv) { + int master_fd, slave_fd; + int result; + result = openpty(&master_fd, &slave_fd, 0, 0, 0); + return 0; + }" + HAVE_OPENPTY +) + +tde_restore( CMAKE_REQUIRED_LIBRARIES ) diff --git a/tdeioslave/fish/FAQ b/tdeioslave/fish/FAQ new file mode 100644 index 000000000..dce0aef41 --- /dev/null +++ b/tdeioslave/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/tdeioslave/fish/INSTALL b/tdeioslave/fish/INSTALL new file mode 100644 index 000000000..02a4a0740 --- /dev/null +++ b/tdeioslave/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/tdeioslave/fish/Makefile.am b/tdeioslave/fish/Makefile.am new file mode 100644 index 000000000..35ca64fd4 --- /dev/null +++ b/tdeioslave/fish/Makefile.am @@ -0,0 +1,33 @@ +kde_module_LTLIBRARIES = kio_fish.la + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +kio_fish_la_SOURCES = fish.cpp +kio_fish_la_LIBADD = $(LIB_KSYCOCA) $(LIBUTIL) +kio_fish_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -lutil +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)/tdeio_fish.pot + + + diff --git a/tdeioslave/fish/README b/tdeioslave/fish/README new file mode 100644 index 000000000..d1afdc3d1 --- /dev/null +++ b/tdeioslave/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 <[email protected]> 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 <[email protected]> + + + +From: Pavel Machek <[email protected]> +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:[email protected]/ + +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! + + +-- +I'm really [email protected]. Pavel +Look at http://atrey.karlin.mff.cuni.cz/~pavel/ ;-). diff --git a/tdeioslave/fish/TODO b/tdeioslave/fish/TODO new file mode 100644 index 000000000..ba3bf69bb --- /dev/null +++ b/tdeioslave/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/tdeioslave/fish/configure.in.in b/tdeioslave/fish/configure.in.in new file mode 100644 index 000000000..086dc0dae --- /dev/null +++ b/tdeioslave/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/tdeioslave/fish/fish.cpp b/tdeioslave/fish/fish.cpp new file mode 100644 index 000000000..0bf44482e --- /dev/null +++ b/tdeioslave/fish/fish.cpp @@ -0,0 +1,1661 @@ +/*************************************************************************** + fish.cpp - a FISH tdeioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001-2003 by J�rg Walter + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 tdeioslave + done by David Faure <[email protected]>. + + Structure is a bit complicated, since I made the mistake to use + TDEProcess... now there is a lightweight homebrew async IO system + inside, but if signals/slots become available for ioslaves, switching + back to TDEProcess should be easy. +*/ + +#include "config.h" + +#include <tqcstring.h> +#include <tqfile.h> +#include <tqsocket.h> +#include <tqdatetime.h> +#include <tqbitarray.h> +#include <tqregexp.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 TDEIO; +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"); + TDEInstance 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 TQCString &pool_socket, const TQCString &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(TQFile::encodeName(KStandardDirs::findExe("nxfish"))); + else + sshPath = strdup(TQFile::encodeName(KStandardDirs::findExe("ssh"))); + } + if (suPath == NULL) { + suPath = strdup(TQFile::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 = TQString::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( TDEIO::ERR_UNKNOWN_HOST, TQString::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 = TQString::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", TQString::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, TDEIO::fileoffset_t len) { + if (outBufPos >= 0 && outBuf) { +#if 0 + TQString 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, TDEIO::fileoffset_t len) { + TQString 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++; + TQString str = buf.left(pos); + buf = buf.mid(pos); + if (str == "\n") + continue; + if (str == "FISH:\n") { + thisFn = TQString::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 = TQString::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 = TQString::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 = TQString::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 = TQString::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 TQString & host, int port, const TQString & u, const TQString & pass){ + TQString 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); + TQString realCmd = info.command; + TQString realAlt = info.alt; + static TQRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]"); + for (int i = 0; i < info.params; i++) { + TQString arg(va_arg(list, const char *)); + int pos = -2; + while ((pos = rx.search(arg,pos+2)) >= 0) { + arg.replace(pos,0,TQString("\\")); + } + //myDebug( << "arg " << i << ": " << arg << endl); + realCmd.append(" ").append(arg); + realAlt.replace(TQRegExp("%"+TQString::number(i+1)),arg); + } + TQString 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 TQString &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 TQString &monthStr, const TQString &dayStr, const TQString &timeyearStr) +{ + TQDateTime dt(TQDate::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 TQString &l) { + TQString line(l); + int rc = handleResponse(line); + UDSAtom atom; + TQDateTime dt; + TDEIO::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(TQDate(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(TQTime(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': { + TQString 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 = TQString::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 = TDEIO::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(TQString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(TQString::number(putPerm,8)),E(url.path())); + sendLen = rawData.size(); + } else if (fishCommand == FISH_WRITE) { + dataReq(); + if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(TQString::number(putPos)),E(TQString::number(rawData.size())),E(url.path())); + else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(TQString::number(putPerm,8)),E(url.path())); + putPos += rawData.size(); + sendLen = rawData.size(); + } else if (fishCommand == FISH_RETR) { + data(TQByteArray()); + } + finished(); + } +} + +void fishProtocol::writeStdin(const TQString &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, TDEIO::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 = TQMIN(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 + } + + TQByteArray 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) + { + TQString s = remoteEncoding()->decode(TQCString(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 TQString &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 + TQString 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: " << TQString::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(TQString::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 TQString& 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(TQString::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(TQString::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 TQByteArray &data ){ + int tmp; + + TQDataStream stream(data, IO_ReadOnly); + + stream >> tmp; + switch (tmp) { + case FISH_EXEC_CMD: // SSH EXEC + { + KURL u; + TQString command; + TQString 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,TQString().setNum(tmp)); + break; + } +} +/** report status */ +void fishProtocol::slave_status() { + myDebug( << "@@@@@@@@@ slave_status" << endl); + if (childPid > 0) + slaveStatus(connectionHost,isLoggedIn); + else + slaveStatus(TQString::null,false); +} diff --git a/tdeioslave/fish/fish.h b/tdeioslave/fish/fish.h new file mode 100644 index 000000000..43dd5477d --- /dev/null +++ b/tdeioslave/fish/fish.h @@ -0,0 +1,211 @@ +/*************************************************************************** + fish.h - a FISH tdeioslave + ------------------- + begin : Thu Oct 4 17:09:14 CEST 2001 + copyright : (C) 2001 by J�rg Walter + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <tqstring.h> +#include <tqcstring.h> + + +#include <kurl.h> +#include <tdeio/global.h> +#include <tdeio/slavebase.h> +#include <kprocess.h> +#include <tdeio/authinfo.h> +#include <time.h> + +#define FISH_EXEC_CMD 'X' + +class fishProtocol : public TDEIO::SlaveBase +{ +public: + fishProtocol(const TQCString &pool_socket, const TQCString &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 TQString & host, int port, const TQString & user, const TQString & 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 TQString &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 TQString& 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 TQByteArray &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 */ + TDEIO::fileoffset_t outBufPos; + /** length of buffer */ + TDEIO::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 */ + TQString redirectUser, redirectPass; + +protected: // Protected attributes + /** for LIST/STAT */ + TDEIO::UDSEntry udsEntry; + /** for LIST/STAT */ + TDEIO::UDSEntry udsStatEntry; + /** for LIST/STAT */ + TDEIO::UDSAtom typeAtom; + /** for LIST/STAT */ + TDEIO::UDSAtom mimeAtom; + /** for LIST/STAT */ + TQString thisFn; + /** for STAT */ + TQString wantedFn; + TQString statPath; + /** url of current request */ + KURL url; + /** true if connection is logged in successfully */ + bool isLoggedIn; + /** host name of current connection */ + TQString connectionHost; + /** user name of current connection */ + TQString connectionUser; + /** port of current connection */ + int connectionPort; + /** password of current connection */ + TQString connectionPassword; + /** AuthInfo object used for logging in */ + TDEIO::AuthInfo connectionAuth; + /** number of lines received, == 0 -> everything went ok */ + int errorCount; + /** queue for lines to be sent */ + TQStringList qlist; + /** queue for commands to be sent */ + TQStringList commandList; + /** queue for commands to be sent */ + TQValueList<int> commandCodes; + /** bytes still to be read in raw mode */ + TDEIO::fileoffset_t rawRead; + /** bytes still to be written in raw mode */ + TDEIO::fileoffset_t rawWrite; + /** data bytes to read in next read command */ + TDEIO::fileoffset_t recvLen; + /** data bytes to write in next write command */ + TDEIO::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 */ + TDEIO::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 */ + TQByteArray rawData; + /** buffer for storing bytes used for MimeMagic */ + TQByteArray mimeBuffer; + /** whther the mimetype has been sent already */ + bool mimeTypeSent; + /** number of bytes read so far */ + TDEIO::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, TDEIO::fileoffset_t buflen); + int received(const char *buffer, TDEIO::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 TQString &str); + /** parses a ls -l time spec */ + int makeTimeFromLs(const TQString &dayStr, const TQString &monthStr, const TQString &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, TDEIO::fileoffset_t len); + /** parses response from server and acts accordingly */ + void manageConnection(const TQString &line); + /** writes to process */ + void writeStdin(const TQString &line); +}; + + +#endif diff --git a/tdeioslave/fish/fish.pl b/tdeioslave/fish/fish.pl new file mode 100755 index 000000000..1fe13841f --- /dev/null +++ b/tdeioslave/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/tdeio_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/tdeioslave/fish/fish.protocol b/tdeioslave/fish/fish.protocol new file mode 100644 index 000000000..451050fcf --- /dev/null +++ b/tdeioslave/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 tdeioslave for the FISH protocol +Description[af]='n Kioslave vir die FISH protokol +Description[be]=Kioslave для пратакола FISH +Description[bn]=ফিশ (FISH) প্রোটোকল-এর জন্য একটি tdeioslave +Description[br]=Ur c'hioslave evit ar c'homenad FISH +Description[bs]=tdeioslave za FISH protokol +Description[ca]=Un tdeioslave pel protocol FISH +Description[cs]=Pomocný protokol pro FISH +Description[csb]=Plugins protokòłu FISH +Description[da]=En tdeioslave for FISH-protokollen +Description[de]=Ein-/Ausgabemodul für das FISH-Protokoll +Description[el]=Ένας tdeioslave για το πρωτόκολλο FISH +Description[eo]=K-enel-sklavo por la FISH protokolo +Description[es]=Un tdeioslave para el protocolo FISH +Description[et]=FISH protokolli IO-moodul +Description[eu]=FISH protokolorako tdeioslavea +Description[fa]=یک tdeioslave برای قرارداد FISH +Description[fi]=Liitäntä FISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole FISH +Description[fy]=In tdeioslave foar it FISH protokol +Description[ga]=tdeioslave le haghaidh an phrótacail FISH +Description[gl]=Un tdeioslave para o protocolo FISH +Description[he]=ממשק tdeioslave עבור פרוטוקול FISH +Description[hi]=फिश प्रोटोकॉल हेतु के-आई-ओ-स्लेव +Description[hr]=Kioslave za FISH protokol +Description[hu]=KDE-protokoll a FISH protokollhoz +Description[is]=tdeioslave fyrir FISH samskiptaregluna +Description[it]=Un tdeioslave per il protocollo FISH +Description[ja]=FISH プロトコルのための tdeioslave +Description[ka]=tdeioslave FISH პროტოკოლისთვის +Description[kk]=FISH протоколға арналған файл жүйесінің модулі +Description[km]=tdeioslave សម្រាប់ពិធីការ 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 tdeioslave voor het protocol FISH +Description[nn]=Ein IU-slave for FISH-protokollen +Description[pa]=FISH ਪਰੋਟੋਕਾਲ ਲਈ tdeioslave +Description[pl]=Wtyczka protokołu FISH +Description[pt]=Um 'tdeioslave' 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]=tdeioslave pre protokol FISH +Description[sl]=tdeioslave 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 tdeioslave +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 tdeioslave (đày tớ vào ra KDE) cho giao thức FISH +Description[wa]=On tdeioslave pol protocole FISH +Description[zh_CN]=FISH 协议的 KIO 仆人 +Description[zh_TW]=用於 FISH 通訊協定的 tdeioslave +DocPath=tdeioslave/fish.html diff --git a/tdeioslave/fish/genfishcode.cmake b/tdeioslave/fish/genfishcode.cmake new file mode 100644 index 000000000..9b35a51ec --- /dev/null +++ b/tdeioslave/fish/genfishcode.cmake @@ -0,0 +1,8 @@ +#!/bin/sh + +SUM=$( @MD5SUM@ @CMAKE_CURRENT_SOURCE_DIR@/fish.pl | cut -d ' ' @MD5SUM_CUT@ ) + +#echo "#define CHECKSUM "\"$SUM\"" > fishcode.h +#echo 'static const char *fishCode(' >> fishcode.h +#sed -e 's/\\/\\\\/g;s/"/\\"/g;s/^[ ]*/"/;/^"# /d;s/[ ]*$$/\\n"/;/^"\\n"$$/d;s/{CHECKSUM}/'$$SUM'/;' @CMAKE_CURRENT_SOURCE_DIR@/fish.pl >> fishcode.h +#echo ');' >> fishcode.h diff --git a/tdeioslave/fish/genfishcode.pl b/tdeioslave/fish/genfishcode.pl new file mode 100755 index 000000000..60dfff8de --- /dev/null +++ b/tdeioslave/fish/genfishcode.pl @@ -0,0 +1,43 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use Digest::MD5; + +sub md5sum { + my $filename = shift; + my $digest; + eval { + open( my $FILE, '<', $filename ) + or die "Can't find file $filename\n"; + my $ctx = Digest::MD5->new; + $ctx->addfile($FILE); + $digest = $ctx->hexdigest; + close($FILE); + }; + if ($@) { + warn $@; + } + return $digest; +} + +my $file = $ARGV[0] or die "Missing filename argument"; + +my $fish_md5 = md5sum($file) + or die "Couldn't compute MD5 for some reason\n"; +print qq{#define CHECKSUM "$fish_md5"\n}; +print qq{static const char *fishCode(\n}; + +open( my $FISH, "<", "$file" ) or die "Can't open $file\n"; +while (<$FISH>) { + chomp; + s|\\|\\\\|g; + s|"|\\"|g; + s/^\s*/"/; + next if /^"# /; + s/\s*$/\\n"/; + next if /^"\\n"$/; + print "$_\n"; +} +close($FISH); +print qq{);\n}; diff --git a/tdeioslave/fish/nxfish.protocol b/tdeioslave/fish/nxfish.protocol new file mode 100644 index 000000000..1a6ffbb48 --- /dev/null +++ b/tdeioslave/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 tdeioslave 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]=tdeioslave za NXFISH protokol +Description[ca]=Un tdeioslave pel protocol NXFISH +Description[cs]=Pomocný protokol pro NXFISH +Description[csb]=Plugins protokòłu NXFISH +Description[da]=En tdeioslave for NXFISH-protokollen +Description[de]=Ein-/Ausgabemodul für das NXFISH-Protokoll +Description[el]=Ένας tdeioslave για το πρωτόκολλο NXFISH +Description[eo]=K-enel-sklavo por la NXFISH protokolo +Description[es]=Un tdeioslave para el protocolo NXFISH +Description[et]=NXFISH protokolli IO-moodul +Description[eu]=NXFISH protokolorako tdeioslavea +Description[fa]=یک tdeioslave برای قرارداد NXFISH +Description[fi]=Liitäntä NXFISH-yhteyskäytäntö +Description[fr]=Un module d'entrées / sorties pour le protocole NXFISH +Description[fy]=In tdeioslave foar it protokol NXFISH +Description[ga]=tdeioslave le haghaidh an phrótacail NXFISH +Description[gl]=Un tdeioslave para o protocolo NXFISH +Description[he]=ממשק tdeioslave עבור פרוטוקול NXFISH +Description[hr]=Kioslave za NXFISH protokol +Description[hu]=KDE-protokoll az NXFISH protokollhoz +Description[is]=tdeioslave fyrir NXFISH samskiptaregluna +Description[it]=Un tdeioslave per il protocollo NXFISH +Description[ja]=NXFISH プロトコルのための tdeioslave +Description[ka]=tdeioslave NXFISH ოქმისთვის +Description[kk]=NXFISH протоколы үшін tdeioslave +Description[km]=tdeioslave សម្រាប់ពិធីការ 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 tdeioslave voor het protocol NXFISH +Description[nn]=Ein IU-slave for NXFISH-protokollen +Description[pa]=NXFISH ਪਰੋਟੋਕਾਲ ਲਈ tdeioslave +Description[pl]=Wtyczka protokołu NXFISH +Description[pt]=Um 'tdeioslave' para o protocolo NXFISH +Description[pt_BR]=Uma implementação para o protocolo NXFISH +Description[ro]=Un tdeioslave pentru protocolul NXFISH +Description[ru]=Модуль файловой системы для протокола NXFISH +Description[se]=SO-šláva NXFISH-protokolla várás +Description[sk]=tdeioslave pre protokol NXFISH +Description[sl]=tdeioslave 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 tdeioslave +Description[uk]=Підлеглий B/В для протоколу NXFISH +Description[uz]=NXFISH protokoli uchun KCH-sleyvi +Description[uz@cyrillic]=NXFISH протоколи учун КЧ-слейви +Description[vi]=A tdeioslave (đày tớ vào ra KDE) cho giao thức NXFISH +Description[wa]=On tdeioslave pol protocole NXFISH +Description[zh_CN]=NXFISH 协议的 KIO 仆人 +Description[zh_TW]=用於 NXFISH 通訊協定的 tdeioslave +DocPath=tdeioslave/fish.html diff --git a/tdeioslave/floppy/AUTHORS b/tdeioslave/floppy/AUTHORS new file mode 100644 index 000000000..062f9d6cb --- /dev/null +++ b/tdeioslave/floppy/AUTHORS @@ -0,0 +1,2 @@ +Written and maintained by: +Alexander Neundorf, [email protected] diff --git a/tdeioslave/floppy/CMakeLists.txt b/tdeioslave/floppy/CMakeLists.txt new file mode 100644 index 000000000..b328998ab --- /dev/null +++ b/tdeioslave/floppy/CMakeLists.txt @@ -0,0 +1,41 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES floppy.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_floppy (module) ####################### + +set( target kio_floppy ) + +set( ${target}_SRCS + kio_floppy.cpp program.cpp +) + +tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/floppy/Makefile.am b/tdeioslave/floppy/Makefile.am new file mode 100644 index 000000000..ec422f75d --- /dev/null +++ b/tdeioslave/floppy/Makefile.am @@ -0,0 +1,20 @@ +## Makefile.am of tdebase/tdeioslave/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)/tdeio_floppy.pot diff --git a/tdeioslave/floppy/README b/tdeioslave/floppy/README new file mode 100644 index 000000000..99d87bd71 --- /dev/null +++ b/tdeioslave/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 diff --git a/tdeioslave/floppy/TODO b/tdeioslave/floppy/TODO new file mode 100644 index 000000000..f4f1e679e --- /dev/null +++ b/tdeioslave/floppy/TODO @@ -0,0 +1,3 @@ +-error handling (will be done until new year) + +Alex diff --git a/tdeioslave/floppy/floppy.protocol b/tdeioslave/floppy/floppy.protocol new file mode 100644 index 000000000..af861d1be --- /dev/null +++ b/tdeioslave/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=tdeioslave/floppy.html +Class=:local diff --git a/tdeioslave/floppy/kio_floppy.cpp b/tdeioslave/floppy/kio_floppy.cpp new file mode 100644 index 000000000..626a4a1f0 --- /dev/null +++ b/tdeioslave/floppy/kio_floppy.cpp @@ -0,0 +1,1169 @@ +/* This file is part of the KDE project + + Copyright (C) 2000 Alexander Neundorf <[email protected]> + + 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 <tqtextstream.h> +#include <tqcstring.h> +#include <tqfile.h> + +#include "kio_floppy.h" + +#include <kinstance.h> +#include <kdebug.h> +#include <tdeio/global.h> +#include <klocale.h> + +using namespace TDEIO; + +extern "C" { KDE_EXPORT int kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + TDEInstance 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 TQString& path, TQString& drive, TQString& rest) +{ + drive=TQString::null; + rest=TQString::null; + TQStringList list=TQStringList::split("/",path); + for (TQStringList::Iterator it=list.begin(); it!=list.end(); it++) + { + if (it==list.begin()) + drive=(*it)+":"; + else + rest=rest+"/"+(*it); + } +} + +FloppyProtocol::FloppyProtocol (const TQCString &pool, const TQCString &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 TQString& drive) +{ + if (m_stderrSize==0) + return true; + //m_stderrBuffer[m_stderrSize]='\0'; + + TQString outputString(m_stderrBuffer); + TQTextIStream output(&outputString); + TQString line=output.readLine(); + kdDebug(7101)<<"line: -"<<line<<"-"<<endl; + if (line.find("resource busy") > -1) + { + error( TDEIO::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( TDEIO::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( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + //no disk + else if (line.find("not configured") > -1) + { + error( TDEIO::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( TDEIO::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( TDEIO::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( TDEIO::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( TDEIO::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( TDEIO::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( TDEIO::ERR_FILE_ALREADY_EXIST,url.prettyURL()); + //return false; + } + else if (outputString.find("could not read boot sector") > -1) + { + error( TDEIO::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( TDEIO::ERR_UNKNOWN, outputString); + } + return true; +} + +void FloppyProtocol::listDir( const KURL& _url) +{ + kdDebug(7101)<<"Floppy::listDir() "<<_url.path()<<endl; + KURL url(_url); + TQString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + url.setPath("/a/"); + redirection(url); + finished(); + return; + } + TQString drive; + TQString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + TQStringList 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; + + TQString outputString(m_stdoutBuffer); + TQTextIStream output(&outputString); + TQString 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 TQString& name) +{ + error(TDEIO::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 = TDEIO::UDS_NAME; + atom.m_str = info.name; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = info.size; + entry.append(atom); + + atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; + atom.m_long = info.time; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long=info.mode; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long =(info.isDir?S_IFDIR:S_IFREG); + entry.append( atom ); +} + +StatInfo FloppyProtocol::createStatInfo(const TQString line, bool makeStat, const TQString& dirName) +{ + //kdDebug(7101)<<"Floppy::createUDSEntry()"<<endl; + TQString name; + TQString size; + bool isDir(false); + TQString day,month, year; + TQString 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); + TQString 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(); + + TQDateTime date(TQDate(year.toInt(),month.toInt(),day.toInt()),TQTime(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; + + TQString path(url.path()); + TQString drive; + TQString 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; + + TQStringList 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( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; + } + + kdDebug(7101)<<"Floppy::_stat(): parse stuff"<<endl; + TQString outputString(m_stdoutBuffer); + TQTextIStream output(&outputString); + TQString 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( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; + } + if (info.isValid==false) + error( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return info; +} + +int FloppyProtocol::freeSpace(const KURL& url) +{ + TQString path(url.path()); + TQString drive; + TQString floppyPath; + getDriveAndPath(path,drive,floppyPath); + + //kdDebug(7101)<<"Floppy::freeSpace(): delete m_mtool"<<endl; + if (m_mtool!=0) + delete m_mtool; + + TQStringList 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( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL()); + return -1; + } + + kdDebug(7101)<<"Floppy::freeSpace(): parse stuff"<<endl; + TQString outputString(m_stdoutBuffer); + TQTextIStream output(&outputString); + TQString line; + int lineNumber(0); + while (!output.atEnd()) + { + line=output.readLine(); + if (line.find("bytes free")==36) + { + TQString 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); + TQString 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; + TQString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + TQString drive; + TQString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + TQStringList 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; + TQString path(url.path()); + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + TQString drive; + TQString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + TQStringList args; + + bool usingmdel; + + if (isfile) + { + args<<"mdel"<<(drive+floppyPath); + usingmdel=true; + } + else + { + args<<"mrd"<<(drive+floppyPath); + usingmdel=false; + } + + kdDebug(7101)<<"Floppy::del(): executing: " << (usingmdel ? TQString("mdel") : TQString("mrd") ) << "-"<<(drive+floppyPath)<<"-"<<endl; + + m_mtool=new Program(args); + if (!m_mtool->start()) + { + delete m_mtool; + m_mtool=0; + errorMissingMToolsProgram(usingmdel ? TQString("mdel") : TQString("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 ) +{ + TQString srcPath(src.path()); + TQString destPath(dest.path()); + + kdDebug(7101)<<"Floppy::rename() -"<<srcPath<<"- to -"<<destPath<<"-"<<endl; + + if ((srcPath.isEmpty()) || (srcPath=="/")) + srcPath="/a/"; + + if ((destPath.isEmpty()) || (destPath=="/")) + destPath="/a/"; + + TQString srcDrive; + TQString srcFloppyPath; + getDriveAndPath(srcPath,srcDrive,srcFloppyPath); + if (srcFloppyPath.isEmpty()) + { + finished(); + return; + } + + TQString destDrive; + TQString destFloppyPath; + getDriveAndPath(destPath,destDrive,destFloppyPath); + if (destFloppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + TQStringList 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 ) +{ + TQString 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); + + TQString drive; + TQString floppyPath; + getDriveAndPath(path,drive,floppyPath); + if (floppyPath.isEmpty()) + { + finished(); + return; + } + + if (m_mtool!=0) + delete m_mtool; + //kdDebug(7101)<<"Floppy::stat(): create args"<<endl; + TQStringList 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); + TQByteArray 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( TQByteArray() ); + finished(); +} + +void FloppyProtocol::put( const KURL& url, int , bool overwrite, bool ) +{ + TQString path(url.path()); + kdDebug(7101)<<"Floppy::put() -"<<path<<"-"<<endl; + + if ((path.isEmpty()) || (path=="/")) + { + KURL newUrl(url); + newUrl.setPath("/a/"); + redirection(newUrl); + finished(); + return; + } + TQString drive; + TQString 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; + TQStringList 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); + TQByteArray 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 + { + TQByteArray 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( TDEIO::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( TDEIO::ERR_CANNOT_OPEN_FOR_WRITING, url.prettyURL()); + return; + } + + delete m_mtool; + m_mtool=0; + + finished(); +} + diff --git a/tdeioslave/floppy/kio_floppy.h b/tdeioslave/floppy/kio_floppy.h new file mode 100644 index 000000000..d38340729 --- /dev/null +++ b/tdeioslave/floppy/kio_floppy.h @@ -0,0 +1,78 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Alexander Neundorf <[email protected]> + + 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 <tdeio/slavebase.h> +#include <tdeio/global.h> + +#include "program.h" + +#include <tqstring.h> + +struct StatInfo +{ + StatInfo():name(""),time(0),size(0),mode(0),freeSpace(0),isDir(false),isValid(false) {;} + TQString name; + time_t time; + int size; + int mode; + int freeSpace; + bool isDir:1; + bool isValid:1; +}; + + +class FloppyProtocol : public TDEIO::SlaveBase +{ + public: + FloppyProtocol (const TQCString &pool, const TQCString &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 TQString line, bool makeStat=false, const TQString& dirName=""); + void createUDSEntry(const StatInfo& info, TDEIO::UDSEntry& entry); + StatInfo _stat(const KURL& _url); + int freeSpace(const KURL& url); + + bool stopAfterError(const KURL& url, const TQString& drive); + void errorMissingMToolsProgram(const TQString& name); + + void clearBuffers(); + void terminateBuffers(); + char *m_stdoutBuffer; + char *m_stderrBuffer; + int m_stdoutSize; + int m_stderrSize; +}; + +#endif diff --git a/tdeioslave/floppy/program.cpp b/tdeioslave/floppy/program.cpp new file mode 100644 index 000000000..83080c233 --- /dev/null +++ b/tdeioslave/floppy/program.cpp @@ -0,0 +1,201 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 Alexander Neundorf <[email protected]> + + 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 TQStringList &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 (TQStringList::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/tdeioslave/floppy/program.h b/tdeioslave/floppy/program.h new file mode 100644 index 000000000..409603689 --- /dev/null +++ b/tdeioslave/floppy/program.h @@ -0,0 +1,53 @@ +/* This file is part of the KDE project + Copyright (C) 2000-2002 Alexander Neundorf <[email protected]> + + 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 <tqstringlist.h> + +/** + * start programs and write to thieir stdin, stderr, + * and read from stdout + **/ +class Program +{ +public: + Program(const TQStringList &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; + TQStringList mArgs; + bool mStarted; +}; + +#endif + diff --git a/tdeioslave/home/CMakeLists.txt b/tdeioslave/home/CMakeLists.txt new file mode 100644 index 000000000..bfc9e6cff --- /dev/null +++ b/tdeioslave/home/CMakeLists.txt @@ -0,0 +1,48 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +add_subdirectory( kdedmodule ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES home.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kiohome (static) ########################## + +set( target kiohome ) + +tde_add_library( ${target} STATIC_PIC AUTOMOC + SOURCES kio_home.cpp homeimpl.cpp +) + + +##### kio_home (module) ######################### + +set( target kio_home ) + +tde_add_kpart( ${target} + SOURCES dummy.cpp + EMBED kiohome-static + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/home/Makefile.am b/tdeioslave/home/Makefile.am new file mode 100644 index 000000000..38b73e653 --- /dev/null +++ b/tdeioslave/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 = libtdeiohome.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 = libtdeiohome.la +libtdeiohome_la_SOURCES = kio_home.cpp homeimpl.cpp + +check_PROGRAMS = testhome +testhome_SOURCES = testhome.cpp +testhome_LDADD = libtdeiohome.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)/tdeio_home.pot + diff --git a/tdeioslave/home/dummy.cpp b/tdeioslave/home/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tdeioslave/home/dummy.cpp @@ -0,0 +1 @@ + diff --git a/tdeioslave/home/home.protocol b/tdeioslave/home/home.protocol new file mode 100644 index 000000000..da2a556cd --- /dev/null +++ b/tdeioslave/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=tdeioslave/file.html +Class=:local +Parent=system:/ +deleteRecursive=true +fileNameUsedForCopying=Name diff --git a/tdeioslave/home/homeimpl.cpp b/tdeioslave/home/homeimpl.cpp new file mode 100644 index 000000000..ea3dad520 --- /dev/null +++ b/tdeioslave/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 <tqapplication.h> +#include <tqeventloop.h> + +#include <sys/stat.h> + +#define MINIMUM_UID 500 + +HomeImpl::HomeImpl() +{ + KUser user; + m_effectiveUid = user.uid(); +} + +bool HomeImpl::parseURL(const KURL &url, TQString &name, TQString &path) const +{ + TQString 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 = TQString::null; + } + + return name != TQString::null; +} + +bool HomeImpl::realURL(const TQString &name, const TQString &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(TQValueList<TDEIO::UDSEntry> &list) +{ + kdDebug() << "HomeImpl::listHomes" << endl; + + KUser current_user; + TQValueList<KUserGroup> groups = current_user.groups(); + TQValueList<int> uid_list; + + TQValueList<KUserGroup>::iterator groups_it = groups.begin(); + TQValueList<KUserGroup>::iterator groups_end = groups.end(); + + for(; groups_it!=groups_end; ++groups_it) + { + TQValueList<KUser> users = (*groups_it).users(); + + TQValueList<KUser>::iterator it = users.begin(); + TQValueList<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() ); + TDEIO::UDSEntry entry; + createHomeEntry(entry, *it); + list.append(entry); + } + } + } + + return true; +} + +static void addAtom(TDEIO::UDSEntry &entry, unsigned int ID, long l, + const TQString &s = TQString::null) +{ + TDEIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void HomeImpl::createTopLevelEntry(TDEIO::UDSEntry &entry) const +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, "."); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_ACCESS, 0555); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, "kfm_home"); + addAtom(entry, TDEIO::UDS_USER, 0, "root"); + addAtom(entry, TDEIO::UDS_GROUP, 0, "root"); +} + +void HomeImpl::createHomeEntry(TDEIO::UDSEntry &entry, + const KUser &user) +{ + kdDebug() << "HomeImpl::createHomeEntry" << endl; + + entry.clear(); + + TQString full_name = user.loginName(); + + if (!user.fullName().isEmpty()) + { + full_name = user.fullName()+" ("+user.loginName()+")"; + } + + full_name = TDEIO::encodeFileName( full_name ); + + addAtom(entry, TDEIO::UDS_NAME, 0, full_name); + addAtom(entry, TDEIO::UDS_URL, 0, "home:/"+user.loginName()); + + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/directory"); + + TQString icon_name = "folder_home2"; + + if (user.uid()==m_effectiveUid) + { + icon_name = "folder_home"; + } + + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, icon_name); + + KURL url; + url.setPath(user.homeDir()); + entry += extractUrlInfos(url); +} + +bool HomeImpl::statHome(const TQString &name, TDEIO::UDSEntry &entry) +{ + kdDebug() << "HomeImpl::statHome: " << name << endl; + + KUser user(name); + + if (user.isValid()) + { + createHomeEntry(entry, user); + return true; + } + + return false; +} + +void HomeImpl::slotStatResult(TDEIO::Job *job) +{ + if ( job->error() == 0) + { + TDEIO::StatJob *stat_job = static_cast<TDEIO::StatJob *>(job); + m_entryBuffer = stat_job->statResult(); + } + + tqApp->eventLoop()->exitLoop(); +} + +TDEIO::UDSEntry HomeImpl::extractUrlInfos(const KURL &url) +{ + m_entryBuffer.clear(); + + TDEIO::StatJob *job = TDEIO::stat(url, false); + connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), + this, TQT_SLOT( slotStatResult(TDEIO::Job *) ) ); + tqApp->eventLoop()->enterLoop(); + + TDEIO::UDSEntry::iterator it = m_entryBuffer.begin(); + TDEIO::UDSEntry::iterator end = m_entryBuffer.end(); + + TDEIO::UDSEntry infos; + + for(; it!=end; ++it) + { + switch( (*it).m_uds ) + { + case TDEIO::UDS_ACCESS: + case TDEIO::UDS_USER: + case TDEIO::UDS_GROUP: + case TDEIO::UDS_CREATION_TIME: + case TDEIO::UDS_MODIFICATION_TIME: + case TDEIO::UDS_ACCESS_TIME: + infos.append(*it); + break; + default: + break; + } + } + + addAtom(infos, TDEIO::UDS_LOCAL_PATH, 0, url.path()); + + return infos; +} + +#include "homeimpl.moc" + diff --git a/tdeioslave/home/homeimpl.h b/tdeioslave/home/homeimpl.h new file mode 100644 index 000000000..5884d8ab7 --- /dev/null +++ b/tdeioslave/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 <tdeio/global.h> +#include <tdeio/job.h> +#include <kurl.h> +#include <kuser.h> + +#include <tqstring.h> + +class HomeImpl : public QObject +{ +Q_OBJECT + +public: + HomeImpl(); + bool parseURL(const KURL &url, TQString &name, TQString &path) const; + bool realURL(const TQString &name, const TQString &path, KURL &url); + + bool statHome(const TQString &name, TDEIO::UDSEntry &entry); + bool listHomes(TQValueList<TDEIO::UDSEntry> &list); + + void createTopLevelEntry(TDEIO::UDSEntry &entry) const; + +private slots: + void slotStatResult(TDEIO::Job *job); + +private: + void createHomeEntry(TDEIO::UDSEntry& entry, const KUser &user); + + TDEIO::UDSEntry extractUrlInfos(const KURL &url); + TDEIO::UDSEntry m_entryBuffer; + + + long m_effectiveUid; +}; + +#endif diff --git a/tdeioslave/home/kdedmodule/CMakeLists.txt b/tdeioslave/home/kdedmodule/CMakeLists.txt new file mode 100644 index 000000000..64d343f84 --- /dev/null +++ b/tdeioslave/home/kdedmodule/CMakeLists.txt @@ -0,0 +1,41 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES homedirnotify.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded ) + + +##### kded_homedirnotify (module) ############### + +set( target kded_homedirnotify ) + +set( ${target}_SRCS + homedirnotify.cpp homedirnotify.skel + homedirnotifymodule.cpp homedirnotifymodule.skel +) + +tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK tdeinit_kded-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/home/kdedmodule/Makefile.am b/tdeioslave/home/kdedmodule/Makefile.am new file mode 100644 index 000000000..3d7a54f9b --- /dev/null +++ b/tdeioslave/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/tdeioslave/home/kdedmodule/homedirnotify.cpp b/tdeioslave/home/kdedmodule/homedirnotify.cpp new file mode 100644 index 000000000..823027fe2 --- /dev/null +++ b/tdeioslave/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; + TQValueList<KUserGroup> groups = current_user.groups(); + TQValueList<int> uid_list; + + TQValueList<KUserGroup>::iterator groups_it = groups.begin(); + TQValueList<KUserGroup>::iterator groups_end = groups.end(); + + for(; groups_it!=groups_end; ++groups_it) + { + TQValueList<KUser> users = (*groups_it).users(); + + TQValueList<KUser>::iterator it = users.begin(); + TQValueList<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() ); + + TQString 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(); + TQMap<TQString,KURL>::iterator it = m_homeFoldersMap.begin(); + TQMap<TQString,KURL>::iterator end = m_homeFoldersMap.end(); + + for (; it!=end; ++it) + { + TQString name = it.key(); + KURL base = it.data(); + + if ( base.isParentOf(url) ) + { + TQString 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/tdeioslave/home/kdedmodule/homedirnotify.desktop b/tdeioslave/home/kdedmodule/homedirnotify.desktop new file mode 100644 index 000000000..aec11b69f --- /dev/null +++ b/tdeioslave/home/kdedmodule/homedirnotify.desktop @@ -0,0 +1,61 @@ +[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 通知程式 +Comment=Provides change notification for user's home folder ($HOME) +ServiceTypes=KDEDModule +X-TDE-ModuleType=Library +X-TDE-Library=homedirnotify +X-TDE-FactoryName=homedirnotify +X-TDE-Kded-load-on-demand=true +X-TDE-Kded-autoload=true diff --git a/tdeioslave/home/kdedmodule/homedirnotify.h b/tdeioslave/home/kdedmodule/homedirnotify.h new file mode 100644 index 000000000..dbbd0845e --- /dev/null +++ b/tdeioslave/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 <tqmap.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); + + TQMap<TQString,KURL> m_homeFoldersMap; + bool mInited; +}; + +#endif diff --git a/tdeioslave/home/kdedmodule/homedirnotifymodule.cpp b/tdeioslave/home/kdedmodule/homedirnotifymodule.cpp new file mode 100644 index 000000000..4eb71c332 --- /dev/null +++ b/tdeioslave/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 TQCString &obj) + : KDEDModule(obj) +{ +} + +extern "C" { + KDE_EXPORT KDEDModule *create_homedirnotify(const TQCString &obj) + { + TDEGlobal::locale()->insertCatalogue("kio_home"); + return new HomeDirNotifyModule(obj); + } +} + diff --git a/tdeioslave/home/kdedmodule/homedirnotifymodule.h b/tdeioslave/home/kdedmodule/homedirnotifymodule.h new file mode 100644 index 000000000..69d96dfc9 --- /dev/null +++ b/tdeioslave/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 TQCString &obj); +private: + HomeDirNotify notifier; +}; + +#endif diff --git a/tdeioslave/home/kio_home.cpp b/tdeioslave/home/kio_home.cpp new file mode 100644 index 000000000..bd55aee0a --- /dev/null +++ b/tdeioslave/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 ) + { + // TDEApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + TDECmdLineArgs::init(argc, argv, "kio_home", 0, 0, 0, 0); + TDECmdLineArgs::addCmdLineOptions( options ); + TDEApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs(); + HomeProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +HomeProtocol::HomeProtocol(const TQCString &protocol, + const TQCString &pool, const TQCString &app) + : ForwardingSlaveBase(protocol, pool, app) +{ +} + +HomeProtocol::~HomeProtocol() +{ +} + +bool HomeProtocol::rewriteURL(const KURL &url, KURL &newUrl) +{ + TQString name, path; + + if ( !m_impl.parseURL(url, name, path) ) + { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return false; + } + + + if ( !m_impl.realURL(name, path, newUrl) ) + { + error(TDEIO::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; + } + + TQString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + ForwardingSlaveBase::listDir(url); +} + +void HomeProtocol::listRoot() +{ + TDEIO::UDSEntry entry; + + TDEIO::UDSEntryList home_entries; + bool ok = m_impl.listHomes(home_entries); + + if (!ok) // can't happen + { + error(TDEIO::ERR_UNKNOWN, ""); + return; + } + + totalSize(home_entries.count()+1); + + m_impl.createTopLevelEntry(entry); + listEntry(entry, false); + + TDEIO::UDSEntryListIterator it = home_entries.begin(); + TDEIO::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; + + TQString path = url.path(); + if ( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + TDEIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + TQString name; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if( path.isEmpty() ) + { + TDEIO::UDSEntry entry; + + if ( m_impl.statHome(name, entry) ) + { + statEntry(entry); + finished(); + } + else + { + error(TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + } + else + { + ForwardingSlaveBase::stat(url); + } +} diff --git a/tdeioslave/home/kio_home.h b/tdeioslave/home/kio_home.h new file mode 100644 index 000000000..db5d8902c --- /dev/null +++ b/tdeioslave/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 <tdeio/forwardingslavebase.h> +#include "homeimpl.h" + +class HomeProtocol : public TDEIO::ForwardingSlaveBase +{ +public: + HomeProtocol(const TQCString &protocol, const TQCString &pool, + const TQCString &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/tdeioslave/home/testhome.cpp b/tdeioslave/home/testhome.cpp new file mode 100644 index 000000000..66b2df126 --- /dev/null +++ b/tdeioslave/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 TQString& txt, TQString a, TQString b) +{ + if (a.isEmpty()) + a = TQString::null; + if (b.isEmpty()) + b = TQString::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[]) +{ + TDEApplication::disableAutoDcopRegistration(); + TDECmdLineArgs::init(argc,argv,"testhome", 0, 0, 0, 0); + TDEApplication 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/tdeioslave/home/testhome.h b/tdeioslave/home/testhome.h new file mode 100644 index 000000000..dd8b257e3 --- /dev/null +++ b/tdeioslave/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/tdeioslave/info/CMakeLists.txt b/tdeioslave/info/CMakeLists.txt new file mode 100644 index 000000000..ed2a72f61 --- /dev/null +++ b/tdeioslave/info/CMakeLists.txt @@ -0,0 +1,39 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES info.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES kde-info2html.conf DESTINATION ${DATA_INSTALL_DIR}/tdeio_info ) +install( PROGRAMS kde-info2html DESTINATION ${DATA_INSTALL_DIR}/tdeio_info ) + + +##### kio_info (module) ######################### + +set( target kio_info ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES info.cc + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + diff --git a/tdeioslave/info/LICENSE b/tdeioslave/info/LICENSE new file mode 100644 index 000000000..9cb70ac77 --- /dev/null +++ b/tdeioslave/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/tdeioslave/info/Makefile.am b/tdeioslave/info/Makefile.am new file mode 100644 index 000000000..37bbe3083 --- /dev/null +++ b/tdeioslave/info/Makefile.am @@ -0,0 +1,21 @@ +## Makefile.am of tdebase/tdeioslave/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)/tdeio_info diff --git a/tdeioslave/info/info.cc b/tdeioslave/info/info.cc new file mode 100644 index 000000000..71d2dc863 --- /dev/null +++ b/tdeioslave/info/info.cc @@ -0,0 +1,261 @@ +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <tqdir.h> +#include <tqfile.h> +#include <tqtextstream.h> +#include <tqregexp.h> + +#include <kdebug.h> +#include <kprocess.h> +#include <kstandarddirs.h> +#include <kiconloader.h> +#include <kinstance.h> +#include <klocale.h> + +#include "info.h" + +using namespace TDEIO; + +InfoProtocol::InfoProtocol( const TQCString &pool, const TQCString &app ) + : SlaveBase( "info", pool, app ) + , m_page( "" ) + , m_node( "" ) +{ + kdDebug( 7108 ) << "InfoProtocol::InfoProtocol" << endl; + + m_perl = TDEGlobal::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; + TQString errorStr; + if ( m_perl.isNull() ) { + errorStr = "perl."; + } else { + TQString 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( TDEIO::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(TQString::null); + redirection(newURl); + finished(); + return; + } + + if ( url.path().right(1) == "/" ) + { + // Trailing / are not supported, so we need to remove them. + KURL newUrl( url ); + TQString 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 ); + + TQString path = TDEGlobal::iconLoader()->iconPath("up", KIcon::Toolbar, true); + int revindex = path.findRev('/'); + path = path.left(revindex); + + TQString cmd = TDEProcess::quote(m_perl); + cmd += " "; + cmd += TDEProcess::quote(m_infoScript); + cmd += " "; + cmd += TDEProcess::quote(m_infoConf); + cmd += " "; + cmd += TDEProcess::quote(path); + cmd += " "; + cmd += TDEProcess::quote(m_page); + cmd += " "; + cmd += TDEProcess::quote(m_node); + + kdDebug( 7108 ) << "cmd: " << cmd << endl; + + FILE *file = popen( TQFile::encodeName(cmd), "r" ); + if ( !file ) { + kdDebug( 7108 ) << "InfoProtocol::get popen failed" << endl; + error( ERR_CANNOT_LAUNCH_PROCESS, cmd ); + return; + } + + char buffer[ 4096 ]; + TQByteArray 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( TQString 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 = TDEIO::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 ) +{ + TDEInstance 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/tdeioslave/info/info.h b/tdeioslave/info/info.h new file mode 100644 index 000000000..c77240613 --- /dev/null +++ b/tdeioslave/info/info.h @@ -0,0 +1,36 @@ +#ifndef __info_h__ +#define __info_h__ + +#include <tqobject.h> + +#include <tdeio/slavebase.h> + +class TDEProcess; + +class InfoProtocol : public TDEIO::SlaveBase +{ +public: + + InfoProtocol( const TQCString &pool, const TQCString &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( TQString path ); + +private: + + TQString m_page; + TQString m_node; + + TQString m_perl; + TQString m_infoScript; + TQString m_infoConf; +}; + +#endif // __info_h__ diff --git a/tdeioslave/info/info.protocol b/tdeioslave/info/info.protocol new file mode 100644 index 000000000..491511ed2 --- /dev/null +++ b/tdeioslave/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=tdeioslave/info.html +Icon=help_index +Class=:local diff --git a/tdeioslave/info/kde-info2html b/tdeioslave/info/kde-info2html new file mode 100755 index 000000000..b84d984f4 --- /dev/null +++ b/tdeioslave/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 <[email protected]> +# +# Changes for the KDE Help Center (c) 1999 Matthias ELter +# ([email protected]) +# +# 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 +# <[email protected]> +# 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 <[email protected]> +# January 30 2003 Ported Hisham's work to HEAD +# by David Pashley <[email protected]> +# 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 <[email protected]> +# +#------------------------------------------------------- + +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=\"TDE - The Trinity 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 tdeioslave 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/tdeioslave/info/kde-info2html.conf b/tdeioslave/info/kde-info2html.conf new file mode 100644 index 000000000..260d2b336 --- /dev/null +++ b/tdeioslave/info/kde-info2html.conf @@ -0,0 +1,43 @@ +# -*- perl -*- +package info2html::config; +#----------------------------------------------------------------- +# info2html.conf +#----------------------------------------------------------------- +# PURPOSE +# configuration settings for the 'info2html' script. +# +# AUTHOR +# Karl Guggisberg <[email protected]> +# +# 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/tdeioslave/ldap/CMakeLists.txt b/tdeioslave/ldap/CMakeLists.txt new file mode 100644 index 000000000..ebf162205 --- /dev/null +++ b/tdeioslave/ldap/CMakeLists.txt @@ -0,0 +1,37 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include( ConfigureChecks.cmake ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES ldap.protocol ldaps.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_ldap (module) ######################### + +tde_add_kpart( kio_ldap AUTOMOC + SOURCES kio_ldap.cpp + LINK kabc-shared ${LDAP_LIBRARIES} + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/ldap/ConfigureChecks.cmake b/tdeioslave/ldap/ConfigureChecks.cmake new file mode 100644 index 000000000..9515a3d69 --- /dev/null +++ b/tdeioslave/ldap/ConfigureChecks.cmake @@ -0,0 +1,21 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +check_include_file( ldap.h HAVE_LDAP_H ) + +if( HAVE_LDAP_H ) + set( LDAP_LIBRARIES ldap ) + check_library_exists( ${LDAP_LIBRARIES} ldap_initialize "" HAVE_LDAP ) +endif( ) + +if( NOT HAVE_LDAP_H OR NOT HAVE_LDAP ) + tde_message_fatal( "ldap is requested, but was not found on your system." ) +endif( ) diff --git a/tdeioslave/ldap/LICENSE b/tdeioslave/ldap/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/tdeioslave/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/tdeioslave/ldap/Makefile.am b/tdeioslave/ldap/Makefile.am new file mode 100644 index 000000000..ca66e1b17 --- /dev/null +++ b/tdeioslave/ldap/Makefile.am @@ -0,0 +1,22 @@ +## Makefile.am of tdebase/tdeioslave/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)/tdeio_ldap.pot diff --git a/tdeioslave/ldap/configure.in.in b/tdeioslave/ldap/configure.in.in new file mode 100644 index 000000000..a4257d3db --- /dev/null +++ b/tdeioslave/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_tdeioslave_ldap, test -n "$LDAP_SUBDIR") diff --git a/tdeioslave/ldap/kio_ldap.cpp b/tdeioslave/ldap/kio_ldap.cpp new file mode 100644 index 000000000..d4a29f1d4 --- /dev/null +++ b/tdeioslave/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 TDEIO; +using namespace KABC; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +/** + * The main program. + */ +int kdemain( int argc, char **argv ) +{ + TDEInstance 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 TQCString &protocol, const TQCString &pool, + const TQCString &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; + TQString msg; + TQString extraMsg; + if ( errmsg ) { + if ( errmsg[0] ) + extraMsg = i18n("\nAdditional info: ") + TQString::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 ) +{ + TQString oid; bool critical; TQByteArray value; + int i = 0; + while ( hasMetaData( TQString::fromLatin1("SERVER_CTRL%1").arg(i) ) ) { + TQCString val = metaData( TQString::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: " << + TQString(TQString::fromUtf8( value, value.size() )) << endl; + addControlOp( serverctrls, oid, value, critical ); + i++; + } + i = 0; + while ( hasMetaData( TQString::fromLatin1("CLIENT_CTRL%1").arg(i) ) ) { + TQCString val = metaData( TQString::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: " << + TQString(TQString::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() ? TQCString() : 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; +} + +TQCString LDAPProtocol::LDAPEntryAsLDIF( LDAPMessage *message ) +{ + TQCString result; + char *name; + struct berval **bvals; + BerElement *entry; + TQByteArray tmp; + + char *dn = ldap_get_dn( mLDAP, message ); + if ( dn == NULL ) return TQCString( "" ); + 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( TQString::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 TQString &oid, + const TQByteArray &value, bool critical ) +{ + LDAPControl **ctrls; + LDAPControl *ctrl = (LDAPControl *) malloc( sizeof( LDAPControl ) ); + + ctrls = *pctrls; + + kdDebug(7125) << "addControlOp: oid:'" << oid << "' val: '" << + TQString(TQString::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 TQString &attr, + const TQByteArray &value ) +{ +// kdDebug(7125) << "type: " << mod_type << " attr: " << attr << +// " value: " << TQString::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 TQString &dn, UDSEntry &entry, + const LDAPUrl &usrc, bool dir ) +{ + UDSAtom atom; + + int pos; + entry.clear(); + atom.m_uds = UDS_NAME; + atom.m_long = 0; + TQString 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" ); + TQString mech; + if ( url.hasExtension( "x-mech" ) ) + mech = url.extension( "x-mech", critical).upper(); + TQString realm; + if ( url.hasExtension( "x-realm" ) ) + mech = url.extension( "x-realm", critical).upper(); + TQString 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 TQString& host, int port, + const TQString& user, const TQString& 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 = TQString::fromLatin1( mProtocol ) + "://" + mHost + ":" + + TQString::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; + TQString 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; + TQString 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, TQString::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, TQString::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 + TQCString result; + filesize_t processed_size = 0; + TQByteArray 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; + + TQStringList 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; + TQByteArray 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() ? TQCString() : 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; + TQStringList 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( TQString::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( TQString::fromUtf8( dn ) ); + usrc2.setScope( LDAPUrl::One ); + usrc2.setAttributes( att ); + usrc2.setFilter( TQString::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( TQString::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/tdeioslave/ldap/kio_ldap.h b/tdeioslave/ldap/kio_ldap.h new file mode 100644 index 000000000..f3bd61fe5 --- /dev/null +++ b/tdeioslave/ldap/kio_ldap.h @@ -0,0 +1,65 @@ +#ifndef __LDAP_H__ +#define __LDAP_H__ + +#include <tqstring.h> +#include <tqvaluelist.h> + +#include <tdeio/slavebase.h> +#include <tdeio/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 TDEIO::SlaveBase +{ + public: + LDAPProtocol( const TQCString &protocol, const TQCString &pool, const TQCString &app ); + virtual ~LDAPProtocol(); + + virtual void setHost( const TQString& host, int port, + const TQString& user, const TQString& 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: + + TQString mHost; + int mPort; + TQString mUser; + TQString mPassword; + LDAP *mLDAP; + int mVer, mSizeLimit, mTimeLimit; + bool mTLS; + bool mAuthSASL; + TQString mMech,mRealm,mBindName; + bool mCancel, mFirstAuth; + + void controlsFromMetaData( LDAPControl ***serverctrls, + LDAPControl ***clientctrls ); + void addControlOp( LDAPControl ***pctrls, const TQString &oid, + const TQByteArray &value, bool critical ); + void addModOp( LDAPMod ***pmods, int mod_type, + const TQString &attr, const TQByteArray &value ); + void LDAPEntry2UDSEntry( const TQString &dn, TDEIO::UDSEntry &entry, + const KABC::LDAPUrl &usrc, bool dir=false ); + int asyncSearch( KABC::LDAPUrl &usrc ); + + TQCString LDAPEntryAsLDIF( LDAPMessage *msg ); + void LDAPErr( const KURL &url, int err = LDAP_SUCCESS ); + void changeCheck( KABC::LDAPUrl &url ); + + void fillAuthInfo( TDEIO::AuthInfo &info ); +}; + +#endif diff --git a/tdeioslave/ldap/ldap.protocol b/tdeioslave/ldap/ldap.protocol new file mode 100644 index 000000000..3e1e77b4b --- /dev/null +++ b/tdeioslave/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=tdeioslave/ldap.html +Icon=kaddressbook diff --git a/tdeioslave/ldap/ldaps.protocol b/tdeioslave/ldap/ldaps.protocol new file mode 100644 index 000000000..1dd927506 --- /dev/null +++ b/tdeioslave/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=tdeioslave/ldap.html +Icon=kaddressbook diff --git a/tdeioslave/mac/AUTHORS b/tdeioslave/mac/AUTHORS new file mode 100644 index 000000000..78b940be4 --- /dev/null +++ b/tdeioslave/mac/AUTHORS @@ -0,0 +1 @@ +Jonathan Riddell, [email protected] diff --git a/tdeioslave/mac/CMakeLists.txt b/tdeioslave/mac/CMakeLists.txt new file mode 100644 index 000000000..6b1b92f6e --- /dev/null +++ b/tdeioslave/mac/CMakeLists.txt @@ -0,0 +1,38 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES mac.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) +tde_install_icons( ) + + +##### kio_mac (module) ########################## + +set( target kio_mac ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kio_mac.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + diff --git a/tdeioslave/mac/ChangeLog b/tdeioslave/mac/ChangeLog new file mode 100644 index 000000000..6c8f647ce --- /dev/null +++ b/tdeioslave/mac/ChangeLog @@ -0,0 +1,39 @@ +10 Feb 2002 - Jonathan Riddell <[email protected]> + - 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 <[email protected]> + - 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 <[email protected]> + - 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 <[email protected]> + - 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 <[email protected]> + - v0.5 + - Initial release + - talks to hfs+ partitions using hptools + - surprisingly successful + diff --git a/tdeioslave/mac/Makefile.am b/tdeioslave/mac/Makefile.am new file mode 100644 index 000000000..dfd79d822 --- /dev/null +++ b/tdeioslave/mac/Makefile.am @@ -0,0 +1,23 @@ +## Makfile.am for kio_mac + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +####### 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)/tdeio_mac.pot diff --git a/tdeioslave/mac/README b/tdeioslave/mac/README new file mode 100644 index 000000000..bb907dd9c --- /dev/null +++ b/tdeioslave/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, [email protected] diff --git a/tdeioslave/mac/TODO b/tdeioslave/mac/TODO new file mode 100644 index 000000000..e94d88254 --- /dev/null +++ b/tdeioslave/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/tdeioslave/mac/cr16-app-mac.png b/tdeioslave/mac/cr16-app-mac.png Binary files differnew file mode 100644 index 000000000..930694eab --- /dev/null +++ b/tdeioslave/mac/cr16-app-mac.png diff --git a/tdeioslave/mac/cr32-app-mac.png b/tdeioslave/mac/cr32-app-mac.png Binary files differnew file mode 100644 index 000000000..87df6fb85 --- /dev/null +++ b/tdeioslave/mac/cr32-app-mac.png diff --git a/tdeioslave/mac/kio_mac.cpp b/tdeioslave/mac/kio_mac.cpp new file mode 100644 index 000000000..d26cc47e5 --- /dev/null +++ b/tdeioslave/mac/kio_mac.cpp @@ -0,0 +1,561 @@ +/*************************************************************************** + kio_mac.cpp + ------------------- + copyright : (C) 2002 Jonathan Riddell + email : [email protected] + 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 <tdeconfig.h> +#include <tqstring.h> +#include <tqregexp.h> + +#include <sys/stat.h> +#include <stdlib.h> +#include <iostream> +#include <time.h> + +#include "kio_mac.moc" + +using namespace TDEIO; + +extern "C" { + int KDE_EXPORT kdemain(int, char **argv) { + TDEInstance instance("kio_mac"); + MacProtocol slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; + } +} + +MacProtocol::MacProtocol(const TQCString &pool, const TQCString &app) + : TQObject(), SlaveBase("mac", pool, app) { +/* logFile = new TQFile("/home/jr/logfile"); + logFile->open(IO_ReadWrite | IO_Append); + logStream = new TQTextStream(logFile); + *logStream << "Start Macprotocol()" << endl; + */ +} + +MacProtocol::~MacProtocol() { +/* *logStream << "destructor ~MacProtocol()" << endl; + logFile->close(); + delete logFile; + logFile = 0; + delete logStream; + logStream = 0; +*/ + delete myTDEProcess; + myTDEProcess = 0L; +} + +//get() called when a file is to be read +void MacProtocol::get(const KURL& url) { + TQString path = prepareHP(url); //mount and change to correct directory - return the filename + TQString query = url.query(); + TQString mode("-"); + TQString 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 == TDEIO::UDS_MIME_TYPE) { + mime = (*it).m_str; + } + if ((*it).m_uds == TDEIO::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 + myTDEProcess = new TDEProcess(); + + *myTDEProcess << "hpcopy" << mode << path << "-"; + + //data is now sent directly from the slot + connect(myTDEProcess, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), + this, TQT_SLOT(slotSetDataStdOutput(TDEProcess *, char *, int))); + + myTDEProcess->start(TDEProcess::Block, TDEProcess::All); + + if (!myTDEProcess->normalExit() || !(myTDEProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("There was an error with hpcopy - please ensure it is installed")); + return; + } + + //clean up + delete myTDEProcess; myTDEProcess = 0; + //finish + data(TQByteArray()); + finished(); +} + +//listDir() called when the user is looking at a directory +void MacProtocol::listDir(const KURL& url) { + TQString filename = prepareHP(url); + + if (filename.isNull()) { + error(ERR_CANNOT_LAUNCH_PROCESS, i18n("No filename was found")); + } else { + myTDEProcess = new TDEProcess(); + *myTDEProcess << "hpls" << "-la" << filename; + + standardOutputStream = TQString::null; + connect(myTDEProcess, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + + myTDEProcess->start(TDEProcess::Block, TDEProcess::All); + + if ((!myTDEProcess->normalExit()) || (!myTDEProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("There was an error with hpls - please ensure it is installed")); + } + + //clean up + delete myTDEProcess; myTDEProcess = 0; + disconnect(myTDEProcess, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + + UDSEntry entry; + if (!standardOutputStream.isEmpty()) { + TQTextStream in(&standardOutputStream, IO_ReadOnly); + TQString 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 +TQValueList<TDEIO::UDSAtom> MacProtocol::doStat(const KURL& url) { + TQString filename = prepareHP(url); + + if (filename.isNull()) { + error(ERR_SLAVE_DEFINED, i18n("No filename was found in the URL")); + } else if (! filename.isEmpty()) { + myTDEProcess = new KShellProcess(); + + *myTDEProcess << "hpls" << "-ld" << filename; + + standardOutputStream = TQString::null; + connect(myTDEProcess, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + + myTDEProcess->start(TDEProcess::Block, TDEProcess::All); + + if ((!myTDEProcess->normalExit()) || (!myTDEProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("hpls did not exit normally - please ensure you have installed the hfsplus tools")); + } + + //clean up + delete myTDEProcess; myTDEProcess = 0; + disconnect(myTDEProcess, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, 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 + TQString 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 TQValueList<TDEIO::UDSAtom>(); +} + +//prepareHP() called from get() listDir() and stat() +//(re)mounts the partition and changes to the appropriate directory +TQString MacProtocol::prepareHP(const KURL& url) { + TQString 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) + TQString device; + TDEConfig* config = new TDEConfig("macrc"); + + TQString 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 + myTDEProcess = new TDEProcess(); + *myTDEProcess << "hpmount"; + standardOutputStream = TQString::null; + connect(myTDEProcess, TQT_SIGNAL(receivedStderr(TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + + myTDEProcess->start(TDEProcess::Block, TDEProcess::All); + + bool version102 = true; + + if (standardOutputStream.contains("options") != 0) { + version102 = false; + } + + delete myTDEProcess; myTDEProcess = 0; + disconnect(myTDEProcess, TQT_SIGNAL(receivedStderr(TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + + //now mount the drive + myTDEProcess = new TDEProcess(); + if (version102) { + *myTDEProcess << "hpmount" << device; + } else { + *myTDEProcess << "hpmount" << "-r" << device; + } + + myTDEProcess->start(TDEProcess::Block, TDEProcess::All); + + if ((!myTDEProcess->normalExit()) || (!myTDEProcess->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 myTDEProcess; myTDEProcess = 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; TQString dir; + s = path.find('/'); + while (s != -1) { + dir = path.left(s); + path = path.mid(s+1); + + myTDEProcess = new TDEProcess(); + *myTDEProcess << "hpcd" << dir; + + myTDEProcess->start(TDEProcess::Block, TDEProcess::All); + + if ((!myTDEProcess->normalExit()) || (!myTDEProcess->exitStatus() == 0)) { + error(ERR_SLAVE_DEFINED, + i18n("hpcd did not exit normally - please ensure it is installed")); + return NULL; + } + + //clean up + delete myTDEProcess; myTDEProcess = 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() +TQValueList<TDEIO::UDSAtom> MacProtocol::makeUDS(const TQString& _line) { + TQString line(_line); + UDSEntry entry; + + //is it a file or a directory + TQRegExp dirRE("^d. +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +(.*)"); + TQRegExp fileRE("^([f|F]). +(....)/(....) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +([^ ]+) +(.*)"); + if (dirRE.exactMatch(line)) { + UDSAtom atom; + atom.m_uds = TDEIO::UDS_NAME; + atom.m_str = dirRE.cap(6); + entry.append(atom); + + atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; + atom.m_long = makeTime(dirRE.cap(4), dirRE.cap(3), dirRE.cap(5)); + entry.append(atom); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append(atom); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = 0755; + entry.append(atom); + + } else if (fileRE.exactMatch(line)) { + UDSAtom atom; + atom.m_uds = TDEIO::UDS_NAME; + atom.m_str = fileRE.cap(9); + entry.append(atom); + + atom.m_uds = TDEIO::UDS_SIZE; + TQString theSize(fileRE.cap(4)); //TODO: this is data size, what about resource size? + atom.m_long = theSize.toLong(); + entry.append(atom); + + atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; + atom.m_long = makeTime(fileRE.cap(7), fileRE.cap(6), fileRE.cap(8)); + entry.append(atom); + + atom.m_uds = TDEIO::UDS_ACCESS; + if (TQString(fileRE.cap(1)) == TQString("F")) { //if locked then read only + atom.m_long = 0444; + } else { + atom.m_long = 0644; + } + entry.append(atom); + + atom.m_uds = TDEIO::UDS_MIME_TYPE; + TQString 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 (TQString(fileRE.cap(2)) == TQString("adrp") || + TQString(fileRE.cap(2)) == TQString("fdrp")) { + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFREG; + entry.append(atom); + + atom.m_uds = TDEIO::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 tdeioslaves. A font thing? + entry.append(atom); + } else { + atom.m_uds = TDEIO::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(TDEProcess*, char *s, int len) { + standardOutputStream += TQString::fromLocal8Bit(s, len); +} + +//slotSetDataStdOutput() is used during hpcopy to give +//standard output to KDE +void MacProtocol::slotSetDataStdOutput(TDEProcess*, char *s, int len) { + processedBytes += len; + processedSize(processedBytes); + TQByteArray 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(TQString mday, TQString mon, TQString 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 + TQRegExp hourMin("(..):(..)"); + if (hourMin.exactMatch(third)) { + TQDate currentDate(TQDate::currentDate()); + + if (month > currentDate.month()) { + year = currentDate.year() - 1; + } else { + year = currentDate.year(); + } + TQString h(hourMin.cap(1)); + TQString 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 ( (!TQDate::isValid(year, month, day)) || (!TQTime::isValid(hour, minute, 0) ) ) { + error(ERR_INTERNAL, i18n("Could not parse a valid date from hpls")); + } + + //put it together and work it out + TQDate fileDate(year, month, day); + TQTime fileTime(hour, minute); + TQDateTime fileDateTime(fileDate, fileTime); + + return fileDateTime.toTime_t(); +} + +TQString MacProtocol::getMimetype(TQString type, TQString app) { + if (type == TQString("TEXT") && app == TQString("ttxt")) { + return TQString("text/plain"); + } else if (type == TQString("TEXT") && app == TQString("udog")) { + return TQString("text/html"); + } else if (type == TQString("svgs")) { + return TQString("text/xml"); + } else if (type == TQString("ZIP ")) { + return TQString("application/zip"); + } else if (type == TQString("pZip")) { + return TQString("application/zip"); + } else if (type == TQString("APPL")) { + return TQString("application/x-executable"); + } else if (type == TQString("MooV")) { + return TQString("video/quicktime"); + } else if (type == TQString("TEXT") && app == TQString("MSWD")) { + return TQString("application/vnd.ms-word"); + } else if (type == TQString("PDF ")) { + return TQString("application/pdf"); + } else if (app == TQString("CARO")) { + return TQString("application/pdf"); + } else if (type == TQString("SIT5")) { + return TQString("application/x-stuffit"); + } else if (type == TQString("SITD")) { + return TQString("application/x-stuffit"); + } else if (type == TQString("SIT!")) { + return TQString("application/x-stuffit"); + } else if (app == TQString("SIT!")) { + return TQString("application/x-stuffit"); + } else if (type == TQString("RTFf")) { + return TQString("text/rtf"); + } else if (type == TQString("GIFf")) { + return TQString("image/gif"); + } else if (type == TQString("JPEG")) { + return TQString("image/jpeg"); + } else if (type == TQString("PNGf")) { + return TQString("image/png"); + } else if (type == TQString("XBMm")) { + return TQString("image/x-xbm"); + } else if (type == TQString("EPSF")) { + return TQString("image/x-epsf"); + } else if (type == TQString("TIFF")) { + return TQString("image/tiff"); + } else if (type == TQString("PICT")) { + return TQString("image/pict"); + } else if (type == TQString("TPIC")) { + return TQString("image/x-targa"); + } else if (type == TQString("ULAW")) { + return TQString("audio/basic"); + } else if (type == TQString("AIFF")) { + return TQString("audio/x-aiff"); + } else if (type == TQString("WAVE")) { + return TQString("audio/x-wav"); + } else if (type == TQString("FFIL") && app == TQString("DMOV")) { + return TQString("application/x-font"); + } else if (type == TQString("XLS3")) { + return TQString("application/vnd.ms-excel"); + } else if (type == TQString("XLS4")) { + return TQString("application/vnd.ms-excel"); + } else if (type == TQString("XLS5")) { + return TQString("application/vnd.ms-excel"); + } else if (app == TQString("MSWD")) { + return TQString("application/vnd.ms-word"); + } else if (type == TQString("TEXT")) { + return TQString("text/plain"); + } else if (app == TQString("ttxt")) { + return TQString("text/plain"); + } + return TQString("application/octet-stream"); +} + + diff --git a/tdeioslave/mac/kio_mac.h b/tdeioslave/mac/kio_mac.h new file mode 100644 index 000000000..e497e9918 --- /dev/null +++ b/tdeioslave/mac/kio_mac.h @@ -0,0 +1,55 @@ +/*************************************************************************** + mac.cpp + ------------------- + copyright : (C) 2002 Jonathan Riddell + email : [email protected] + 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 <tdeio/slavebase.h> +#include <tdeio/global.h> +#include <kurl.h> +#include <kprocess.h> + +#include <tqstring.h> +#include <tqcstring.h> +#include <tqfile.h> +#include <tqtextstream.h> + +class MacProtocol : public TQObject, public TDEIO::SlaveBase +{ + Q_OBJECT +public: + MacProtocol(const TQCString &pool, const TQCString &app); + ~MacProtocol(); + virtual void get(const KURL& url ); + virtual void listDir(const KURL& url); + virtual void stat(const KURL& url); +protected slots: + void slotGetStdOutput(TDEProcess*, char*, int); + void slotSetDataStdOutput(TDEProcess*, char *s, int len); +protected: + TQString prepareHP(const KURL& _url); + TQValueList<TDEIO::UDSAtom> makeUDS(const TQString& _line); + int makeTime(TQString mday, TQString mon, TQString third); + TQString getMimetype(TQString type, TQString app); + TQValueList<TDEIO::UDSAtom> doStat(const KURL& url); + + TDEIO::filesize_t processedBytes; + TQString standardOutputStream; + TDEProcess* myTDEProcess; + + //for debugging + //TQFile* logFile; + //TQTextStream* logStream; +}; diff --git a/tdeioslave/mac/mac.protocol b/tdeioslave/mac/mac.protocol new file mode 100644 index 000000000..9e94132bb --- /dev/null +++ b/tdeioslave/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 tdeioslave for MacOS HFS+ partitions +Description[af]='n Kioslave vir MacOS HFS+ partisies +Description[be]=Kioslave для раздзелаў MacOS HFS+ +Description[bn]=ম্যাক-ও-এস HFS+ পার্টিশন-এর জন্য একটি tdeioslave +Description[br]=Ur c'hioslave evit ar parzhadurioù MacOS HFS+ +Description[bs]=tdeioslave za MacOS HFS+ particije +Description[ca]=Un tdeioslave 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 tdeioslave for MacOS HFS+ partitioner +Description[de]=Ein-/Ausgabemodul für MacOS HFS+ Partitionen +Description[el]=Ένας tdeioslave για κατατμήσεις MacOS HFS+ +Description[eo]=K-enel-sklavo por MacOS HFS+ subdiskoj +Description[es]=Un tdeioslave para particiones MacOS HFS+ +Description[et]=MacOS-i HFS+-partitsioonide IO-moodul +Description[eu]=MacOS HFS+ zatiketetarako tdeioslavea +Description[fa]=یک tdeioslave برای افرازهای HFS+ سیستم عامل مکینتاش +Description[fi]=Liitäntä MacOS HFS+ osioinneille +Description[fr]=Un module d'entrées / sorties pour les partitions MacOS HFS+ +Description[fy]=In tdeioslave foar MacOS HFS+-partities +Description[ga]=tdeioslave le haghaidh deighiltí MacOS HFS+ +Description[gl]=Un tdeioslave para particións MacOS HFS+ +Description[he]=ממשק tdeioslave עבור מחיצות 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]=tdeioslave fyrir MacOS HFS+ disksneiðar +Description[it]=Un tdeioslave per partizioni MacOS HFS+ +Description[ja]=MacOS HFS+ パーティションのための tdeioslave +Description[ka]=tdeioslave MacOS HFS+ პარტიციებისთვის +Description[kk]=MacOS HFS+ файл жүйесінің енгізу-шығару модулі +Description[km]=tdeioslave សម្រាប់ភាគ 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 tdeioslave voor MacOS HFS+-partities +Description[nn]=Ein IU-slave for MacOS HFS+-partisjonar +Description[pa]=MacOS HFS+ ਭਾਗਾਂ ਲਈ tdeioslave +Description[pl]=Wtyczka protokołu dla partycji HFS+ systemu MacOS +Description[pt]=Um 'tdeioslave' 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]=tdeioslave pre MacOS HFS+ +Description[sl]=tdeioslave 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 tdeioslave +Description[tt]=MacOS HFS+ bülemnäre öçen birem sistemeneñ modulı +Description[uk]=Підлеглий B/В для розділів MacOS HFS+ +Description[vi]=A tdeioslave (đày tớ vào ra KDE) cho MacOS HFS và các phân vùng +Description[wa]=On tdeioslave po MacOS HFS + pårticions +Description[zh_CN]=MacOS HFS+ 分区的 KIO 仆人 +Description[zh_TW]=用於 MacOS HFS+ 分割區的 tdeioslave +Icon=mac +DocPath=tdeioslave/mac.html diff --git a/tdeioslave/man/CMakeLists.txt b/tdeioslave/man/CMakeLists.txt new file mode 100644 index 000000000..fca109df0 --- /dev/null +++ b/tdeioslave/man/CMakeLists.txt @@ -0,0 +1,60 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +# FIXME not built: man2html, kio_man_test + + +##### system checks ############################# + +check_include_file( "unistd.h" HAVE_UNISTD_H ) +check_include_file( "string.h" HAVE_STRING_H ) + + +##### compiler/linker settings ################## + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES man.protocol kmanpart.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES kio_man.css DESTINATION ${DATA_INSTALL_DIR}/tdeio_man ) + + +##### kio_man (module) ########################## + +set( target kio_man ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES man2html.cpp kio_man.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### libkmanpart (module) ###################### + +set( target libkmanpart ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kmanpart.cpp + LINK tdehtml-shared tdeparts-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/man/LICENSE b/tdeioslave/man/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/tdeioslave/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/tdeioslave/man/Makefile.am b/tdeioslave/man/Makefile.am new file mode 100644 index 000000000..d43edb74d --- /dev/null +++ b/tdeioslave/man/Makefile.am @@ -0,0 +1,51 @@ +## Makefile.am of tdebase/tdeioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +EXTRA_PROGRAMS = kio_man_test man2html + +####### just for testing ([email protected], 15.02.2001) + +kio_man_test_SOURCES = kio_man_test.cpp +kio_man_test_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor +kio_man_test_LDADD = man2html.lo kio_man.lo $(LIB_KIO) $(LIB_TDEUI) $(LIB_TDECORE) $(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 = -ltdehtml $(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)/tdeio_man +EXTRA_DIST=$(kio_man_data_DATA) + +METASOURCES = AUTO + +messages: + $(XGETTEXT) *.cpp *.h -o $(podir)/tdeio_man.pot + +man2html_SOURCES = dummy.cpp +man2html_LDADD = man2html_simple.o $(LIB_QT) +man2html_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +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/tdeioslave/man/kio_man.cpp b/tdeioslave/man/kio_man.cpp new file mode 100644 index 000000000..d2ef8cb94 --- /dev/null +++ b/tdeioslave/man/kio_man.cpp @@ -0,0 +1,1533 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Matthias Hoelzer-Kluepfel <[email protected]> + + 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 <tqdir.h> +#include <tqfile.h> +#include <tqtextstream.h> +#include <tqdatastream.h> +#include <tqcstring.h> +#include <tqptrlist.h> +#include <tqmap.h> +#include <tqregexp.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 TDEIO; + +MANProtocol *MANProtocol::_self = 0; + +#define SGML2ROFF_DIRS "/usr/lib/sgml" + +/* + * Drop trailing ".section[.gz]" from name + */ +static +void stripExtension( TQString *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 TQString& _url, TQString &title, TQString §ion) +{ + section = TQString::null; + + TQString 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 TQCString &pool_socket, const TQCString &app_socket) + : TQObject(), SlaveBase("man", pool_socket, app_socket) +{ + assert(!_self); + _self = this; + const TQString common_dir = TDEGlobal::dirs()->findResourceDir( "html", "en/common/kde-common.css" ); + const TQString strPath=TQString( "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/tdeio_man.css" ); +} + +MANProtocol *MANProtocol::self() { return _self; } + +MANProtocol::~MANProtocol() +{ + _self = 0; +} + +void MANProtocol::parseWhatIs( TQMap<TQString, TQString> &i, TQTextStream &t, const TQString &mark ) +{ + TQRegExp re( mark ); + TQString l; + while ( !t.atEnd() ) + { + l = t.readLine(); + int pos = re.search( l ); + if (pos != -1) + { + TQString names = l.left(pos); + TQString 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(TQMap<TQString, TQString> &i, const TQString &name, const TQString &mark) +{ + TQFile f(name); + if (!f.open(IO_ReadOnly)) + return false; + TQTextStream t(&f); + parseWhatIs( i, t, mark ); + return true; +} + +TQMap<TQString, TQString> MANProtocol::buildIndexMap(const TQString §ion) +{ + TQMap<TQString, TQString> i; + TQStringList 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"; + + TQStringList names; + names << "whatis.db" << "whatis"; + TQString mark = "\\s+\\(" + section + "[a-z]*\\)\\s+-\\s+"; + + for ( TQStringList::ConstIterator it_dir = man_dirs.begin(); + it_dir != man_dirs.end(); + ++it_dir ) + { + if ( TQFile::exists( *it_dir ) ) { + TQStringList::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() ) { + TDEProcess proc; + proc << "whatis" << "-M" << (*it_dir) << "-w" << "*"; + myStdStream = TQString::null; + connect( &proc, TQT_SIGNAL( receivedStdout(TDEProcess *, char *, int ) ), + TQT_SLOT( slotGetStdOutput( TDEProcess *, char *, int ) ) ); + proc.start( TDEProcess::Block, TDEProcess::Stdout ); + TQTextStream t( &myStdStream, IO_ReadOnly ); + parseWhatIs( i, t, mark ); + } + } + } + return i; +} + +TQStringList MANProtocol::manDirectories() +{ + checkManPaths(); + // + // Build a list of man directories including translations + // + TQStringList man_dirs; + + for ( TQStringList::ConstIterator it_dir = m_manpath.begin(); + it_dir != m_manpath.end(); + it_dir++ ) + { + // Translated pages in "<mandir>/<lang>" if the directory + // exists + TQStringList languages = TDEGlobal::locale()->languageList(); + + for (TQStringList::ConstIterator it_lang = languages.begin(); + it_lang != languages.end(); + it_lang++ ) + { + if ( !(*it_lang).isEmpty() && (*it_lang) != TQString("C") ) { + TQString dir = (*it_dir) + '/' + (*it_lang); + + struct stat sbuf; + + if ( ::stat( TQFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + const TQString p = TQDir(dir).canonicalPath(); + if (!man_dirs.contains(p)) man_dirs += p; + } + } + } + + // Untranslated pages in "<mandir>" + const TQString p = TQDir(*it_dir).canonicalPath(); + if (!man_dirs.contains(p)) man_dirs += p; + } + return man_dirs; +} + +TQStringList MANProtocol::findPages(const TQString &_section, + const TQString &title, + bool full_path) +{ + TQString section = _section; + + TQStringList list; + + // kdDebug() << "findPages '" << section << "' '" << title << "'\n"; + if (title.at(0) == '/') { + list.append(title); + return list; + } + + const TQString star( "*" ); + + // + // Find man sections in this directory + // + TQStringList 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; + } + + TQStringList man_dirs = manDirectories(); + + // + // Find man pages in the sections listed above + // + for ( TQStringList::ConstIterator it_sect = sect_list.begin(); + it_sect != sect_list.end(); + it_sect++ ) + { + TQString it_real = (*it_sect).lower(); + // + // Find pages + // + for ( TQStringList::ConstIterator it_dir = man_dirs.begin(); + it_dir != man_dirs.end(); + it_dir++ ) + { + TQString man_dir = (*it_dir); + + // + // Sections = all sub directories "man*" and "sman*" + // + DIR *dp = ::opendir( TQFile::encodeName( man_dir ) ); + + if ( !dp ) + continue; + + struct dirent *ep; + + const TQString man = TQString("man"); + const TQString sman = TQString("sman"); + + while ( (ep = ::readdir( dp )) != 0L ) { + const TQString file = TQFile::decodeName( ep->d_name ); + TQString sect = TQString::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 TQString dir = man_dir + TQString("/man") + (it_real) + '/'; + const TQString sdir = man_dir + TQString("/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 TQString &dir, const TQString &title, bool full_path, TQStringList &list) +{ + kdDebug() << "findManPagesInSection " << dir << " " << title << endl; + bool title_given = !title.isEmpty(); + + DIR *dp = ::opendir( TQFile::encodeName( dir ) ); + + if ( !dp ) + return; + + struct dirent *ep; + + while ( (ep = ::readdir( dp )) != 0L ) { + if ( ep->d_name[0] != '.' ) { + + TQString name = TQFile::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... + TQString 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(TQByteArray()); + 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 TQString text2html(const TQString& txt) +{ + TQString 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; + + TQString 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 TQStringList 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 TQCString filename=TQFile::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(TQByteArray()); + // tell we are done + data(TQByteArray()); + } + finished(); +} + +void MANProtocol::slotGetStdOutput(TDEProcess* /* p */, char *s, int len) +{ + myStdStream += TQString::fromLocal8Bit(s, len); +} + +void MANProtocol::slotGetStdOutputUtf8(TDEProcess* /* p */, char *s, int len) +{ + myStdStream += TQString::fromUtf8(s, len); +} + +char *MANProtocol::readManPage(const char *_filename) +{ + TQCString 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). */ + //TQString file_mimetype = KMimeType::findByPath(TQString(filename), 0, false)->name(); + if (filename.contains("sman", false)) //file_mimetype == "text/html" || ) + { + myStdStream =TQString::null; + TDEProcess proc; + + /* Determine path to sgml2roff, if not already done. */ + getProgramPath(); + proc << mySgml2RoffPath << filename; + + TQApplication::connect(&proc, TQT_SIGNAL(receivedStdout (TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutput(TDEProcess *, char *, int))); + proc.start(TDEProcess::Block, TDEProcess::All); + + const TQCString cstr=myStdStream.latin1(); + const int len = cstr.size()-1; + buf = new char[len + 4]; + tqmemmove(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 (TQDir::isRelativePath(filename)) { + kdDebug(7107) << "relative " << filename << endl; + filename = TQDir::cleanDirPath(lastdir + "/" + filename).utf8(); + if (!KStandardDirs::exists(filename)) { // exists perhaps with suffix + lastdir = filename.left(filename.findRev('/')); + TQDir mandir(lastdir); + mandir.setNameFilter(filename.mid(filename.findRev('/') + 1) + ".*"); + filename = lastdir + "/" + TQFile::encodeName(mandir.entryList().first()); + } + kdDebug(7107) << "resolved to " << filename << endl; + } + lastdir = filename.left(filename.findRev('/')); + + myStdStream = TQString::null; + TDEProcess proc; + /* TODO: detect availability of 'man --recode' so that this can go + * upstream */ + proc << "man" << "--recode" << "UTF-8" << filename; + + TQApplication::connect(&proc, TQT_SIGNAL(receivedStdout (TDEProcess *, char *, int)), + this, TQT_SLOT(slotGetStdOutputUtf8(TDEProcess *, char *, int))); + proc.start(TDEProcess::Block, TDEProcess::All); + + const TQCString cstr=myStdStream.utf8(); + const int len = cstr.size()-1; + buf = new char[len + 4]; + tqmemmove(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 NUL characters at end + } + return buf; +} + + +void MANProtocol::outputError(const TQString& errmsg) +{ + TQByteArray array; + TQTextStream os(array, IO_WriteOnly); + os.setEncoding(TQTextStream::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>TDE Man Viewer Error</h1>") << errmsg << "</body>" << endl; + os << "</html>" << endl; + + data(array); +} + +void MANProtocol::outputMatchingPages(const TQStringList &matchingPages) +{ + TQByteArray array; + TQTextStream os(array, IO_WriteOnly); + os.setEncoding(TQTextStream::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 (TQStringList::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; + + TQString title, section; + + if (!parseUrl(url.path(), title, section)) + { + error(TDEIO::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; + TQString newUrl = "man:"+title; + if (!section.isEmpty()) + newUrl += TQString("(%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 ) { + + TDEInstance 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 TQString sectionName(const TQString& 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 TQString::null; +} + +TQStringList MANProtocol::buildSectionList(const TQStringList& dirs) const +{ + TQStringList l; + + for (TQStringList::ConstIterator it = section_names.begin(); + it != section_names.end(); ++it) + { + for (TQStringList::ConstIterator dir = dirs.begin(); + dir != dirs.end(); ++dir) + { + TQDir d((*dir)+"/man"+(*it)); + if (d.exists()) + { + l << *it; + break; + } + } + } + return l; +} + +void MANProtocol::showMainIndex() +{ + TQByteArray array; + TQTextStream os(array, IO_WriteOnly); + os.setEncoding(TQTextStream::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 TQString sectList = getenv("MANSECT"); + TQStringList sections; + if (sectList.isEmpty()) + sections = buildSectionList(manDirectories()); + else + sections = TQStringList::split(':', sectList); + + os << "<table>" << endl; + + TQStringList::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(TQStringList& constr_path, TQStringList constr_catmanpath) +{ + TQMap<TQString, TQString> manpath_map; + TQMap<TQString, TQString> 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" + + TQRegExp manpath_regex( "^MANPATH\\s" ); + TQRegExp mandatory_regex( "^MANDATORY_MANPATH\\s" ); + TQRegExp manpath_map_regex( "^MANPATH_MAP\\s" ); + TQRegExp mandb_map_regex( "^MANDB_MAP\\s" ); + //TQRegExp section_regex( "^SECTION\\s" ); + TQRegExp space_regex( "\\s+" ); // for parsing manpath map + + TQFile 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)) + { + TQTextStream is(&mc); + is.setEncoding(TQTextStream::Locale); + + while (!is.eof()) + { + const TQString line = is.readLine(); + if ( manpath_regex.search(line, 0) == 0 ) + { + const TQString path = line.mid(8).stripWhiteSpace(); + constr_path += path; + } + else if ( mandatory_regex.search(line, 0) == 0 ) + { + const TQString 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 TQStringList mapping = + TQStringList::split(space_regex, line); + + if ( mapping.count() == 3 ) + { + const TQString dir = TQDir::cleanDirPath( mapping[1] ); + const TQString mandir = TQDir::cleanDirPath( mapping[2] ); + + manpath_map[ dir ] = mandir; + } + } + else if ( mandb_map_regex.search(line, 0) == 0 ) + { + // The entry is "MANDB_MAP <manpath> <catmanpath>" + const TQStringList mapping = + TQStringList::split(space_regex, line); + + if ( mapping.count() == 3 ) + { + const TQString mandir = TQDir::cleanDirPath( mapping[1] ); + const TQString catmandir = TQDir::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( TQString( manpaths[i] ) ) == -1 ) + constr_path += TQString( 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 TQStringList path = + TQStringList::split( ":", + TQString::fromLocal8Bit( ::getenv("PATH") ) ); + + for ( TQStringList::const_iterator it = path.begin(); + it != path.end(); + ++it ) + { + const TQString dir = TQDir::cleanDirPath( *it ); + TQString 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 + TQString( "/man" ); + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + + int pos = dir.findRev( '/' ); + if ( pos > 0 ) { + mandir = dir.left( pos ) + TQString("/man"); + if ( constr_path.findIndex( mandir ) == -1 ) + constr_path += mandir; + } + } + TQString 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 TQString manpath_env = TQString::fromLocal8Bit( ::getenv("MANPATH") ); + //TQString mansect_env = TQString::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 + TQStringList constr_path; + TQStringList constr_catmanpath; // catmanpath + + TQString 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 TQStringList path_list_env = TQStringList::split( ':', manpath_env , true ); + + for ( TQStringList::const_iterator it = path_list_env.begin(); + it != path_list_env.end(); + ++it ) + { + struct stat sbuf; + + TQString dir = (*it); + + if ( !dir.isEmpty() ) { + // Add dir to the man path if it exists + if ( m_manpath.findIndex( dir ) == -1 ) { + if ( ::stat( TQFile::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 ( TQStringList::Iterator it2 = constr_path.begin(); + it2 != constr_path.end(); + it2++ ) + { + dir = (*it2); + + if ( !dir.isEmpty() ) { + if ( m_manpath.findIndex( dir ) == -1 ) { + if ( ::stat( TQFile::encodeName( dir ), &sbuf ) == 0 + && S_ISDIR( sbuf.st_mode ) ) + { + m_manpath += dir; + } + } + } + } + } + } + +/* sections are not used + // Sections + TQStringList m_mansect = TQStringList::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( TQString( default_sect[i] ) ) == -1 ) + m_mansect += TQString( 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 = tqstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m2->manpage_len); + if (!i) + return 1; + return i; + } + + if ( m1->manpage_len < m2->manpage_len) + { + i = tqstrnicmp( m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + if (!i) + return -1; + return i; + } + + return tqstrnicmp( 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 TQPtrList<struct man_index_t> QManIndexListBase; +typedef TQPtrListIterator<struct man_index_t> QManIndexListIterator; + +class QManIndexList : public QManIndexListBase +{ +public: +private: + int compareItems( TQPtrCollection::Item s1, TQPtrCollection::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 = tqstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m2->manpage_len); + if (!i) + return 1; + return i; + } + + if (m1->manpage_len > m2->manpage_len) + { + + i = tqstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + if (!i) + return -1; + return i; + } + + return tqstrnicmp(m1->manpage_begin, + m2->manpage_begin, + m1->manpage_len); + } +}; + +#endif /* !_USE_QSORT */ +#endif /* !_USE_OLD_CODE */ + + + + +void MANProtocol::showIndex(const TQString& section) +{ + TQByteArray array; + TQTextStream os(array, IO_WriteOnly); + os.setEncoding(TQTextStream::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 + TQStringList pages = findPages( section, TQString::null ); + + TQMap<TQString, TQString> indexmap = buildIndexMap(section); + + // print out the list + os << "<table>" << endl; + +#ifdef _USE_OLD_CODE + pages.sort(); + + TQMap<TQString, TQString> pagemap; + + TQStringList::ConstIterator page; + for (page = pages.begin(); page != pages.end(); ++page) + { + TQString fileName = *page; + + stripExtension( &fileName ); + + pos = fileName.findRev('/'); + if (pos > 0) + fileName = fileName.mid(pos+1); + + if (!fileName.isEmpty()) + pagemap[fileName] = *page; + + } + + for (TQMap<TQString,TQString>::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 */ + + TQStringList::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); + + TQChar firstchar, tmp; + TQString indexLine="<div class=\"secidxshort\">\n"; + if (indexlist[0]->manpage_len>0) + { + firstchar=TQChar((indexlist[0]->manpage_begin)[0]).lower(); + + const TQString appendixstr = TQString( + " [<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]; + + // tqstrncmp(): + // "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 && + !tqstrncmp(last_index->manpage_begin, + manindex->manpage_begin, + manindex->manpage_len) + ) + { + continue; + } + + tmp=TQChar((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 TQString appendixstr = TQString( + " [<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(); + + // tqstrncmp(): + // "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 && + !tqstrncmp(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(TQString::null); + mimeType("text/html"); + data(array); + finished(); +} + +void MANProtocol::listDir(const KURL &url) +{ + kdDebug( 7107 ) << "ENTER listDir: " << url.prettyURL() << endl; + + TQString title; + TQString section; + + if ( !parseUrl(url.path(), title, section) ) { + error( TDEIO::ERR_MALFORMED_URL, url.url() ); + return; + } + + TQStringList list = findPages( section, TQString::null, false ); + + UDSEntryList uds_entry_list; + UDSEntry uds_entry; + UDSAtom uds_atom; + + uds_atom.m_uds = TDEIO::UDS_NAME; // we only do names... + uds_entry.append( uds_atom ); + + TQStringList::Iterator it = list.begin(); + TQStringList::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 = TDEGlobal::dirs()->findExe("sgml2roff"); + if (!mySgml2RoffPath.isEmpty()) + return; + + /* sgml2roff isn't found in PATH. Check some possible locations where it may be found. */ + mySgml2RoffPath = TDEGlobal::dirs()->findExe("sgml2roff", TQString(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 TDE.")); + finished(); + exit(); +} diff --git a/tdeioslave/man/kio_man.css b/tdeioslave/man/kio_man.css new file mode 100644 index 000000000..8a9f378bc --- /dev/null +++ b/tdeioslave/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/tdeioslave/man/kio_man.h b/tdeioslave/man/kio_man.h new file mode 100644 index 000000000..829a07964 --- /dev/null +++ b/tdeioslave/man/kio_man.h @@ -0,0 +1,100 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Matthias Hoelzer-Kluepfel <[email protected]> + + + 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 <tqstring.h> +#include <tqcstring.h> +#include <tqstringlist.h> +#include <tqdict.h> +#include <tqbuffer.h> + + +#include <tdeio/global.h> +#include <tdeio/slavebase.h> + + +class MANProtocol : public TQObject, public TDEIO::SlaveBase +{ + Q_OBJECT + +public: + + MANProtocol(const TQCString &pool_socket, const TQCString &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 TQString& errmsg); + void outputMatchingPages(const TQStringList &matchingPages); + + void showMainIndex(); + void showIndex(const TQString& 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(TDEProcess*, char*, int); + void slotGetStdOutputUtf8(TDEProcess*, char*, int); + +private: + void checkManPaths(); + TQStringList manDirectories(); + TQMap<TQString, TQString> buildIndexMap(const TQString& section); + bool addWhatIs(TQMap<TQString, TQString>& i, const TQString& f, const TQString& mark); + void parseWhatIs( TQMap<TQString, TQString> &i, TQTextStream &t, const TQString &mark ); + TQStringList findPages(const TQString& section, + const TQString &title, + bool full_path = true); + + void addToBuffer(const char *buffer, int buflen); + TQString pageName(const TQString& page) const; + TQStringList buildSectionList(const TQStringList& dirs) const; + void constructPath(TQStringList& constr_path, TQStringList constr_catmanpath); +private: + static MANProtocol *_self; + TQCString lastdir; + + void findManPagesInSection(const TQString &dir, const TQString &title, bool full_path, TQStringList &list); + TQStringList m_manpath; ///< Path of man directories + TQStringList m_mandbpath; ///< Path of catman directories + TQStringList section_names; + + TQString myStdStream; + TQString mySgml2RoffPath; + void getProgramPath(); + + TQCString m_htmlPath; ///< Path to TDE resources, encoded for HTML + TQCString m_cssPath; ///< Path to TDE resources, encoded for CSS + TQBuffer m_outputBuffer; ///< Buffer for the output + TQString m_manCSSFile; ///< Path to kio_man.css +}; + + +#endif diff --git a/tdeioslave/man/kio_man_test.cpp b/tdeioslave/man/kio_man_test.cpp new file mode 100644 index 000000000..a181c47b3 --- /dev/null +++ b/tdeioslave/man/kio_man_test.cpp @@ -0,0 +1,38 @@ + + +#include <tqobject.h> + +#include "kio_man.h" + + +#include <kapplication.h> +#include <klocale.h> + + +class kio_man_test : public MANProtocol +{ + Q_OBJECT + +public: + kio_man_test(const TQCString &pool_socket, const TQCString &app_socket); + +protected: + virtual void data(int); + +}; + + + + + +int main(int argc, char **argv) +{ + TDEApplication a( argc, argv , "p2"); + + MANProtocol testproto("/tmp/tdeiotest.in", "/tmp/tdeiotest.out"); + testproto.showIndex("3"); + + return 0; +} + + diff --git a/tdeioslave/man/kmanpart.cpp b/tdeioslave/man/kmanpart.cpp new file mode 100644 index 000000000..01768e33d --- /dev/null +++ b/tdeioslave/man/kmanpart.cpp @@ -0,0 +1,115 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Alexander Neundorf <[email protected]> + + 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 <tqstring.h> + +#include <kinstance.h> +#include <kglobal.h> +#include <kdebug.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kaboutdata.h> +#include <tdeversion.h> + +extern "C" +{ + KDE_EXPORT void* init_libkmanpart() + { + return new KManPartFactory; + } +} + +TDEInstance* KManPartFactory::s_instance = 0L; +TDEAboutData* KManPartFactory::s_about = 0L; + +KManPartFactory::KManPartFactory( TQObject* parent, const char* name ) + : KParts::Factory( parent, name ) +{} + +KManPartFactory::~KManPartFactory() +{ + delete s_instance; + s_instance = 0L; + delete s_about; +} + +KParts::Part* KManPartFactory::createPartObject( TQWidget * parentWidget, const char* /*widgetName*/, TQObject *, + const char* name, const char* /*className*/,const TQStringList & ) +{ + KManPart* part = new KManPart(parentWidget, name ); + return part; +} + +TDEInstance* KManPartFactory::instance() +{ + if( !s_instance ) + { + s_about = new TDEAboutData( "kmanpart", + I18N_NOOP( "KMan" ), TDE_VERSION_STRING ); + s_instance = new TDEInstance( s_about ); + } + return s_instance; +} + + +KManPart::KManPart( TQWidget * parent, const char * name ) +: KHTMLPart( parent, name ) +,m_job(0) +{ + TDEInstance * instance = new TDEInstance( "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 = TDEIO::get( url, true, false ); + connect( m_job, TQT_SIGNAL( data( TDEIO::Job *, const TQByteArray &) ), TQT_SLOT( readData( TDEIO::Job *, const TQByteArray &) ) ); + connect( m_job, TQT_SIGNAL( result( TDEIO::Job * ) ), TQT_SLOT( jobDone( TDEIO::Job * ) ) ); + return true; +} + +void KManPart::readData(TDEIO::Job * , const TQByteArray & data) +{ + write(data,data.size()); +} + +void KManPart::jobDone( TDEIO::Job *) +{ + m_job=0; + end(); +} + +#include "kmanpart.moc" + diff --git a/tdeioslave/man/kmanpart.desktop b/tdeioslave/man/kmanpart.desktop new file mode 100644 index 000000000..220e19b84 --- /dev/null +++ b/tdeioslave/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-TDE-Library=libkmanpart diff --git a/tdeioslave/man/kmanpart.h b/tdeioslave/man/kmanpart.h new file mode 100644 index 000000000..20a451c91 --- /dev/null +++ b/tdeioslave/man/kmanpart.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Alexander Neundorf <[email protected]> + + 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 <tdeparts/factory.h> +#include <tdeparts/part.h> +#include <tdeparts/browserextension.h> +#include <tdehtml_part.h> +#include <tdeio/job.h> +#include <tdeio/jobclasses.h> + +#include <tqcstring.h> + +class TDEInstance; +class TDEAboutData; + +/** + * Man Page Viewer + * \todo: Why is it needed? Why is KHTML alone not possible? + */ +class KManPartFactory: public KParts::Factory +{ + Q_OBJECT + public: + KManPartFactory( TQObject * parent = 0, const char * name = 0 ); + virtual ~KManPartFactory(); + + virtual KParts::Part* createPartObject( TQWidget * parentWidget, const char * widgetName , + TQObject* parent, const char* name, const char * classname, + const TQStringList &args); + + static TDEInstance * instance(); + + private: + static TDEInstance * s_instance; + static TDEAboutData * s_about; + +}; + +class KManPart : public KHTMLPart +{ + Q_OBJECT + public: + KManPart( TQWidget * parent, const char * name = 0L ); + KParts::BrowserExtension * extension() {return m_extension;} + + public slots: + virtual bool openURL( const KURL &url ); + protected slots: + void readData(TDEIO::Job * , const TQByteArray & data); + void jobDone( TDEIO::Job *); + protected: + virtual bool openFile(); + TDEInstance *m_instance; + KParts::BrowserExtension * m_extension; + TDEIO::TransferJob *m_job; +}; + +#endif + diff --git a/tdeioslave/man/man.protocol b/tdeioslave/man/man.protocol new file mode 100644 index 000000000..555970fdc --- /dev/null +++ b/tdeioslave/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=tdeioslave/man.html +Icon=man +Class=:local diff --git a/tdeioslave/man/man2html.cpp b/tdeioslave/man/man2html.cpp new file mode 100644 index 000000000..ec46b2d3d --- /dev/null +++ b/tdeioslave/man/man2html.cpp @@ -0,0 +1,5685 @@ +/* + This file is part of the KDE libraries + + Copyright (C) 2005 Nicolas GOUTTE <[email protected]> + + ### TODO: who else? +*/ + +// Start of verbatim comment + +/* +** This program was written by Richard Verhoeven (NL:5482ZX35) +** at the Eindhoven University of Technology. Email: [email protected] +** +** 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 <[email protected]>. + * + * 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 <[email protected]>. + * + * 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 ([email protected]) 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 <tqvaluestack.h> +#include <tqstring.h> +#include <tqptrlist.h> +#include <tqmap.h> +#include <tqdatetime.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 <tqtextcodec.h> +# include <kdebug.h> +# include <tdeversion.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; + tqstrncpy(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 + TQCString 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 TQMap<TQCString,StringDefinition> s_characterDefinitionMap; + +/** + * Map of string variable and macro definitions + * \note String variables and macros are the same thing! + */ +static TQMap<TQCString,StringDefinition> s_stringDefinitionMap; + +/** + * Map of number registers + * \note Intern number registers (starting with a dot are not handled here) + */ +static TQMap<TQCString,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 TQCString tdeversion(TDE_VERSION_STRING); + s_stringDefinitionMap.insert( ".TDE_VERSION_STRING", StringDefinition( tdeversion.length(), tdeversion ) ); +#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 + TQDate today( TQDate::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 TQValueList<char*> s_argumentList; + +static TQCString htmlPath, cssPath; + +static TQCString s_dollarZero; // Value of $0 + +void setResourcePath(const TQCString& _htmlPath, const TQCString& _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 }; + TQCString 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, *h; + char *g; + 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) { + + TQCString dir; + TQCString file(g, h - g + 1); + file = file.stripWhiteSpace(); + for (int index = 0; includedirs[index]; index++) { + TQCString str = TQCString(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; + + TQCString 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; + TQString subsec; // ### TODO avoid using TQString, as we do not know the encoding + TQString fstr(f); // ### TODO avoid using TQString, 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'; + TQCString 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'; + TQCString 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 (!tqstrncmp("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'; + TQCString 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'; + TQCString 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 TQCString 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 = tqstrdup(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 TQCString set_font( const TQCString& name ) +{ + // Every font but R (Regular) creates <span> elements + TQCString 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 TQCString set_font( const char ch ) +#ifndef SIMPLE_MAN2HTML + KDE_DEPRECATED; + +static TQCString set_font( const char ch ) +#endif +{ + const TQCString name = &ch; + return set_font( name ); +} + +static TQCString 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 TQCString font ( current_font ); + TQCString 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, TQCString& cstr ); + +/** + * scan a named character + * param c position +*/ +static TQCString scan_named_character( char*& c ) +{ + TQCString name; + if ( *c == '(' ) + { + // \*(ab Name of two characters + if ( c[1] == escapesym ) + { + TQCString 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 ) + { + TQCString 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 ) + { + TQCString 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 + TQMap<TQCString,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 TQCString scan_named_string(char*& c) +{ + TQCString name; + if ( *c == '(' ) + { + // \*(ab Name of two characters + if ( c[1] == escapesym ) + { + TQCString 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 ) + { + TQCString 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 + TQMap<TQCString,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 TQCString 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 == '@' ); + TQValueList<char*>::const_iterator it = s_argumentList.begin(); + TQCString 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 TQCString& 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 == ".TDE_VERSION_MAJOR" ) + return TDE_VERSION_MAJOR; + else if ( name == ".TDE_VERSION_MINOR" ) + return TDE_VERSION_MINOR; + else if ( name == ".TDE_VERSION_RELEASE" ) + return TDE_VERSION_RELEASE; + else if ( name == ".TDE_VERSION" ) + return TDE_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; + } + TQCString 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 + { + TQMap< TQCString, 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 TQCString scan_named_font( char*& c ) +{ + TQCString name; + if ( *c == '(' ) + { + // \f(ab Name of two characters + if ( c[1] == escapesym ) + { + TQCString 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 ) + { + TQCString 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 TQCString scan_number_code( char*& c ) +{ + TQCString 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, TQCString& 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 = TQCString( 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; + TQCString 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) +{ + TQCString 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 = tqstrdup(_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: + TQPtrList<TABLEITEM> items; +}; + +TABLEITEM::TABLEITEM(TABLEROW *row) : contents(0), _parent(row) { + init(); + _parent->addItem(this); +} + +TABLEROW *TABLEROW::copyLayout() const { + TABLEROW *newrow = new TABLEROW(); + + TQPtrListIterator<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; + TQCString 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] && tqstrncmp(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 || tqstrncmp(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 || tqstrncmp(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 <[email protected]>: +// 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 TQCString as if it would be a macro + const TQCString 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 = tqstrdup( 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] && tqstrncmp(req, requests[r], len)) r++; + return requests[r] ? r : REQ_UNKNOWN; +} + +// &%(#@ c programs !!! +//static int ifelseval=0; +// If/else can be nested! +static TQValueStack<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 TQCString 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 TQCString 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; + TQCString 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 */ + TQMap<TQCString,StringDefinition>::iterator it=s_stringDefinitionMap.find(macroName); + if (it!=s_stringDefinitionMap.end()) + { + kdDebug(7107) << "CALLING MACRO: " << macroName << endl; + const TQCString 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] = tqstrdup(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 + tqstrncpy(work+1,(*it).m_output.data(),length+1); + const TQValueList<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 TQCString name ( scan_identifier( c ) ); + while (*c && *c!='\n') c++; + c++; + h=c; + while (*c && tqstrncmp(c,".di",3)) while (*c && *c++!='\n'); + *c='\0'; + char* result=0; + scan_troff(h,0,&result); + TQMap<TQCString,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 TQCString 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); + TQMap<TQCString,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 && tqstrncmp(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 TQCString 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 && tqstrncmp(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 = tqstrncmp(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][tqstrlen(wordlist[i])-1] == '\007') + wordlist[i][tqstrlen(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(TQTextCodec::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+=tqstrlen(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 TQCString name( scan_identifier( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl; + break; + } + TQCString 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); + TQMap<TQCString,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 TQCString 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 ); + TQMap <TQCString, 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 TQCString name(nameStart); + + TQCString 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=tqstrlen(endmacro); + while (*c && tqstrncmp(c,endmacro,length)) + c=skip_till_newline(c); + + TQCString macro; + while (sl!=c) + { + if (sl[0]=='\\' && sl[1]=='\\') + { + macro+='\\'; + sl++; + } + else + macro+=*sl; + sl++; + } + + TQMap<TQCString,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') + if (*((*it).m_output.right(1).data())!='\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 (tqstrncmp(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) + { + char *nextbreak = strchr(c, '\n'); + 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 TQCString 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 TQCString 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) + TQMap<TQCString,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 TQCString name ( scan_identifier( c ) ); + if ( name.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty origin string to remove/rename: " << endl; + break; + } + c = skip_till_newline( c ); + TQMap <TQCString, 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 TQCString 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 TQCString name2 ( scan_identifier ( c ) ); + if ( name2.isEmpty() ) + { + kdDebug(7107) << "EXCEPTION: empty destination to rename number register " << endl; + break; + } + c = skip_till_newline( c ); + TQMap<TQCString,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 TQCString 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 TQCString 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) + TQMap<TQCString,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 TQCString 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=tqstrlen(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 = tqstrlen(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/tdeioslave/man/man2html.h b/tdeioslave/man/man2html.h new file mode 100644 index 000000000..68b01efe9 --- /dev/null +++ b/tdeioslave/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 <tqcstring.h> + +/** call this with the buffer you have */ +void scan_man_page(const char *man_page); + +/** + * Set the paths to TDE resources + * + * \param htmlPath Path to the TDE resources, encoded for HTML + * \param cssPath Path to the TDE resources, encoded for CSS + * \since 3.5 + * + */ +extern void setResourcePath(const TQCString& _htmlPath, const TQCString& _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/tdeioslave/media/CMakeLists.txt b/tdeioslave/media/CMakeLists.txt new file mode 100644 index 000000000..68d4bf9d6 --- /dev/null +++ b/tdeioslave/media/CMakeLists.txt @@ -0,0 +1,59 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +add_subdirectory( libmediacommon ) +add_subdirectory( mediamanager ) +add_subdirectory( medianotifier ) +add_subdirectory( mounthelper ) +add_subdirectory( tdefile-plugin ) +add_subdirectory( tdecmodule ) +add_subdirectory( mimetypes ) +add_subdirectory( services ) +add_subdirectory( propsdlgplugin ) + + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/libmediacommon + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES media.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kiomedia (static) ######################### + +set( target kiomedia ) + +tde_add_library( ${target} STATIC_PIC AUTOMOC + SOURCES kio_media.cpp mediaimpl.cpp mediaimpl.skel +) + + +##### kio_media (module) ######################## + +set( target kio_media ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES dummy.cpp + EMBED kiomedia-static + LINK mediacommon-static tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/media/Makefile.am b/tdeioslave/media/Makefile.am new file mode 100644 index 000000000..b82e48557 --- /dev/null +++ b/tdeioslave/media/Makefile.am @@ -0,0 +1,36 @@ +if include_media_halbackend +PROPSDLGPLUGINDIR = propsdlgplugin +endif + +SUBDIRS = libmediacommon . mediamanager medianotifier mounthelper \ + tdefile-plugin tdecmodule 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 = libtdeiomedia.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 = libtdeiomedia.la +libtdeiomedia_la_SOURCES = kio_media.cpp mediaimpl.cpp mediaimpl.skel + +check_PROGRAMS = testmedia +testmedia_SOURCES = testmedia.cpp +testmedia_LDADD = libtdeiomedia.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 tdefile-plugin/*.cpp libmediacommon/*.cpp mediamanager/*.cpp mounthelper/*.cpp propsdlgplugin/*.cpp -o $(podir)/tdeio_media.pot diff --git a/tdeioslave/media/configure.in.in b/tdeioslave/media/configure.in.in new file mode 100644 index 000000000..1f66f3b37 --- /dev/null +++ b/tdeioslave/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$tdelibsuff /usr/local/lib$tdelibsuff" + 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$tdelibsuff/dbus-1.0/include /usr/local/lib$tdelibsuff/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$tdelibsuff /usr/local/lib$tdelibsuff" + 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$tdelibsuff /usr/local/lib$tdelibsuff" + 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/tdeioslave/media/contrib/README b/tdeioslave/media/contrib/README new file mode 100644 index 000000000..8f6041578 --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/contrib/mediamanager_usbstorage.dev b/tdeioslave/media/contrib/mediamanager_usbstorage.dev new file mode 100755 index 000000000..d12a3e687 --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/contrib/usbcam b/tdeioslave/media/contrib/usbcam new file mode 100755 index 000000000..85158d96b --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/dummy.cpp b/tdeioslave/media/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tdeioslave/media/dummy.cpp @@ -0,0 +1 @@ + diff --git a/tdeioslave/media/kio_media.cpp b/tdeioslave/media/kio_media.cpp new file mode 100644 index 000000000..d1647c6fc --- /dev/null +++ b/tdeioslave/media/kio_media.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 <stdlib.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kapplication.h> +#include <dcopclient.h> +#include <kcmdlineargs.h> + +#include <tqeventloop.h> + +#include "mediaimpl.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 ) + { + // TDEApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + TDECmdLineArgs::init(argc, argv, "kio_media", 0, 0, 0, 0); + TDECmdLineArgs::addCmdLineOptions( options ); + TDEApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs(); + MediaProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +MediaProtocol::MediaProtocol(const TQCString &protocol, + const TQCString &pool, const TQCString &app) + : ForwardingSlaveBase(protocol, pool, app) +{ + connect( &m_impl, TQT_SIGNAL( warning( const TQString & ) ), + this, TQT_SLOT( slotWarning( const TQString & ) ) ); +} + +MediaProtocol::~MediaProtocol() +{ +} + +bool MediaProtocol::rewriteURL(const KURL &url, KURL &newUrl) +{ + TQString name, path; + + if ( !m_impl.parseURL(url, name, path) ) + { + error(TDEIO::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; + + TQString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( ok && path.isEmpty() ) + { + error(TDEIO::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; + + TQString src_name, src_path; + bool ok = m_impl.parseURL(src, src_name, src_path); + TQString 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; + + TQString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( ok && path.isEmpty() ) + { + error(TDEIO::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; + + TQString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( ok && path.isEmpty() ) + { + error(TDEIO::ERR_CANNOT_DELETE, url.prettyURL()); + } + else + { + ForwardingSlaveBase::del(url, isFile); + } +} + +void MediaProtocol::stat(const KURL &url) +{ + kdDebug(1219) << "MediaProtocol::stat: " << url << endl; + TQString path = url.path(); + if( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + TDEIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + TQString name; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if( path.isEmpty() ) + { + TDEIO::UDSEntry entry; + + if ( m_impl.statMedium(name, entry) + || m_impl.statMediumByLabel(name, entry) ) + { + statEntry(entry); + finished(); + } + else + { + error(TDEIO::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; + } + + TQString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + ForwardingSlaveBase::listDir(url); +} + +void MediaProtocol::listRoot() +{ + TDEIO::UDSEntry entry; + + TDEIO::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); + + TDEIO::UDSEntryListIterator it = media_entries.begin(); + TDEIO::UDSEntryListIterator end = media_entries.end(); + + for(; it!=end; ++it) + { + listEntry(*it, false); + } + + entry.clear(); + listEntry(entry, true); + + finished(); +} + +void MediaProtocol::slotWarning( const TQString &msg ) +{ + warning( msg ); +} + +#include "kio_media.moc" diff --git a/tdeioslave/media/kio_media.h b/tdeioslave/media/kio_media.h new file mode 100644 index 000000000..bf1325475 --- /dev/null +++ b/tdeioslave/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 <tdeio/forwardingslavebase.h> + +#include "mediaimpl.h" + +class MediaProtocol : public TDEIO::ForwardingSlaveBase +{ +Q_OBJECT +public: + MediaProtocol(const TQCString &protocol, const TQCString &pool, + const TQCString &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 TQString &msg ); + +private: + void listRoot(); + + MediaImpl m_impl; +}; + +#endif diff --git a/tdeioslave/media/libmediacommon/CMakeLists.txt b/tdeioslave/media/libmediacommon/CMakeLists.txt new file mode 100644 index 000000000..2059e35a8 --- /dev/null +++ b/tdeioslave/media/libmediacommon/CMakeLists.txt @@ -0,0 +1,41 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES mediamanagersettings.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) + + +##### mediacommon (static) ###################### + +set( target mediacommon ) + +set( ${target}_SRCS + medium.cpp actionlistboxitem.cpp notifieraction.cpp + notifierserviceaction.cpp notifiernothingaction.cpp + notifieropenaction.cpp notifiersettings.cpp + mediamanagersettings.kcfgc +) + +tde_add_library( ${target} STATIC_PIC AUTOMOC + SOURCES ${${target}_SRCS} +) diff --git a/tdeioslave/media/libmediacommon/Makefile.am b/tdeioslave/media/libmediacommon/Makefile.am new file mode 100644 index 000000000..dd27d265a --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/libmediacommon/actionlistboxitem.cpp b/tdeioslave/media/libmediacommon/actionlistboxitem.cpp new file mode 100644 index 000000000..271d2e487 --- /dev/null +++ b/tdeioslave/media/libmediacommon/actionlistboxitem.cpp @@ -0,0 +1,47 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqpixmap.h> + +ActionListBoxItem::ActionListBoxItem(NotifierAction *action, TQString mimetype, TQListBox *parent) + : TQListBoxPixmap(parent, action->pixmap()), + m_action(action) +{ + TQString 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/tdeioslave/media/libmediacommon/actionlistboxitem.h b/tdeioslave/media/libmediacommon/actionlistboxitem.h new file mode 100644 index 000000000..254f065de --- /dev/null +++ b/tdeioslave/media/libmediacommon/actionlistboxitem.h @@ -0,0 +1,40 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqlistbox.h> +#include <tqstring.h> + +#include "notifieraction.h" + +class ActionListBoxItem : public TQListBoxPixmap +{ +public: + ActionListBoxItem(NotifierAction *action, TQString mimetype, TQListBox *parent); + ~ActionListBoxItem(); + + NotifierAction *action() const; + +private: + NotifierAction *m_action; +}; + +#endif diff --git a/tdeioslave/media/libmediacommon/mediamanagersettings.kcfg b/tdeioslave/media/libmediacommon/mediamanagersettings.kcfg new file mode 100644 index 000000000..2994680cd --- /dev/null +++ b/tdeioslave/media/libmediacommon/mediamanagersettings.kcfg @@ -0,0 +1,28 @@ +<?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, TDE will use it to gather information on the storage media available in your system.</whatsthis> + <default>true</default> + </entry> + <entry name="TdeHardwareBackendEnabled" type="Bool"> + <label>Enable TDE hardware library backend</label> + <whatsthis>When TDE hardware library support is enabled, TDE 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 TDE 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 TDE 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/tdeioslave/media/libmediacommon/mediamanagersettings.kcfgc b/tdeioslave/media/libmediacommon/mediamanagersettings.kcfgc new file mode 100644 index 000000000..bbc58d92e --- /dev/null +++ b/tdeioslave/media/libmediacommon/mediamanagersettings.kcfgc @@ -0,0 +1,4 @@ +File=mediamanagersettings.kcfg +ClassName=MediaManagerSettings +Singleton=true +Mutators=true diff --git a/tdeioslave/media/libmediacommon/medium.cpp b/tdeioslave/media/libmediacommon/medium.cpp new file mode 100644 index 000000000..7b97fb1a0 --- /dev/null +++ b/tdeioslave/media/libmediacommon/medium.cpp @@ -0,0 +1,268 @@ +/* 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 <tdeconfig.h> +#include <klocale.h> + +const TQString Medium::SEPARATOR = "---"; + +Medium::Medium(const TQString id, TQString uuid, const TQString name) +{ + m_properties+= id; /* ID */ + m_properties+= uuid; /* UUID */ + m_properties+= name; /* NAME */ + m_properties+= name; /* LABEL */ + m_properties+= TQString::null; /* USER_LABEL */ + + m_properties+= "false"; /* MOUNTABLE */ + m_properties+= TQString::null; /* DEVICE_NODE */ + m_properties+= TQString::null; /* MOUNT_POINT */ + m_properties+= TQString::null; /* FS_TYPE */ + m_properties+= "false"; /* MOUNTED */ + m_properties+= TQString::null; /* BASE_URL */ + m_properties+= TQString::null; /* MIME_TYPE */ + m_properties+= TQString::null; /* ICON_NAME */ + m_properties+= "false"; /* ENCRYPTED */ + m_properties+= TQString::null; /* CLEAR_DEVICE_UDI */ + m_properties+= "false"; /* HIDDEN */ + + loadUserLabel(); + + m_halmounted = false; +} + +Medium::Medium() +{ + m_properties+= TQString::null; /* ID */ + m_properties+= TQString::null; /* UUID */ + m_properties+= TQString::null; /* NAME */ + m_properties+= TQString::null; /* LABEL */ + m_properties+= TQString::null; /* USER_LABEL */ + + m_properties+= TQString::null; /* MOUNTABLE */ + m_properties+= TQString::null; /* DEVICE_NODE */ + m_properties+= TQString::null; /* MOUNT_POINT */ + m_properties+= TQString::null; /* FS_TYPE */ + m_properties+= TQString::null; /* MOUNTED */ + m_properties+= TQString::null; /* BASE_URL */ + m_properties+= TQString::null; /* MIME_TYPE */ + m_properties+= TQString::null; /* ICON_NAME */ + m_properties+= TQString::null; /* ENCRYPTED */ + m_properties+= TQString::null; /* CLEAR_DEVICE_UDI */ + m_properties+= "false"; /* HIDDEN */ + + m_halmounted = false; +} + +const Medium Medium::create(const TQStringList &properties) +{ + Medium m; + + if ( properties.size() >= PROPERTIES_COUNT ) + { + m.m_properties[ID] = properties[ID]; + m.m_properties[UUID] = properties[UUID]; + 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]; + m.m_properties[ENCRYPTED] = properties[ENCRYPTED]; + m.m_properties[CLEAR_DEVICE_UDI] = properties[CLEAR_DEVICE_UDI]; + m.m_properties[HIDDEN] = properties[HIDDEN]; + } + + return m; +} + +Medium::MList Medium::createList(const TQStringList &properties) +{ + MList l; + + if ( properties.size() % (PROPERTIES_COUNT+1) == 0) + { + int media_count = properties.size()/(PROPERTIES_COUNT+1); + + TQStringList props = properties; + + for(int i=0; i<media_count; i++) + { + const Medium m = create(props); + l.append(m); + + TQStringList::iterator first = props.begin(); + TQStringList::iterator last = props.find(SEPARATOR); + ++last; + props.erase(first, last); + } + } + + return l; +} + + +void Medium::setName(const TQString &name) +{ + m_properties[NAME] = name; +} + +void Medium::setLabel(const TQString &label) +{ + m_properties[LABEL] = label; +} + +void Medium::setEncrypted(bool state) +{ + m_properties[ENCRYPTED] = ( state ? "true" : "false" ); +} + +void Medium::setHidden(bool state) +{ + m_properties[HIDDEN] = ( state ? "true" : "false" ); +} + +void Medium::setUserLabel(const TQString &label) +{ + TDEConfig cfg("mediamanagerrc"); + cfg.setGroup("UserLabels"); + + TQString entry_name = m_properties[UUID]; + + if ( label.isNull() ) + { + cfg.deleteEntry(entry_name); + } + else + { + cfg.writeEntry(entry_name, label); + } + + m_properties[USER_LABEL] = label; +} + +void Medium::loadUserLabel() +{ + TDEConfig cfg("mediamanagerrc"); + cfg.setGroup("UserLabels"); + + TQString entry_name = m_properties[UUID]; + + if ( cfg.hasKey(entry_name) ) + { + m_properties[USER_LABEL] = cfg.readEntry(entry_name); + } + else + { + m_properties[USER_LABEL] = TQString::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 TQString &deviceNode, + const TQString &mountPoint, + const TQString &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::mountableState(const TQString &deviceNode, + const TQString &clearDeviceUdi, + const TQString &mountPoint, + const TQString &fsType, bool mounted) +{ + m_properties[MOUNTABLE] = "true"; + m_properties[DEVICE_NODE] = deviceNode; + m_properties[CLEAR_DEVICE_UDI] = clearDeviceUdi; + m_properties[MOUNT_POINT] = mountPoint; + m_properties[FS_TYPE] = fsType; + m_properties[MOUNTED] = ( mounted ? "true" : "false" ); +} + +void Medium::unmountableState(const TQString &baseURL) +{ + m_properties[MOUNTABLE] = "false"; + m_properties[BASE_URL] = baseURL; +} + +void Medium::setMimeType(const TQString &mimeType) +{ + m_properties[MIME_TYPE] = mimeType; +} + +void Medium::setIconName(const TQString &iconName) +{ + m_properties[ICON_NAME] = iconName; +} + +bool Medium::needMounting() const +{ + return isMountable() && !isMounted(); +} + +bool Medium::needDecryption() const +{ + return isEncrypted() && clearDeviceUdi().isEmpty(); +} + +KURL Medium::prettyBaseURL() const +{ + if ( !baseURL().isEmpty() ) + return baseURL(); + + return KURL( mountPoint() ); +} + +TQString Medium::prettyLabel() const +{ + if ( !userLabel().isEmpty() ) + { + return userLabel(); + } + else + { + return label(); + } +} + diff --git a/tdeioslave/media/libmediacommon/medium.h b/tdeioslave/media/libmediacommon/medium.h new file mode 100644 index 000000000..e45dccbfd --- /dev/null +++ b/tdeioslave/media/libmediacommon/medium.h @@ -0,0 +1,127 @@ +/* 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 <tqstring.h> +#include <tqstringlist.h> +#include <kurl.h> +#include <tqmap.h> + +class Medium +{ +public: + typedef TQValueList<Medium> MList; + + static const uint ID = 0; + static const uint UUID = 1; + static const uint NAME = 2; + static const uint LABEL = 3; + static const uint USER_LABEL = 4; + static const uint MOUNTABLE = 5; + static const uint DEVICE_NODE = 6; + static const uint MOUNT_POINT = 7; + static const uint FS_TYPE = 8; + static const uint MOUNTED = 9; + static const uint BASE_URL = 10; + static const uint MIME_TYPE = 11; + static const uint ICON_NAME = 12; + static const uint ENCRYPTED = 13; + static const uint CLEAR_DEVICE_UDI = 14; + static const uint HIDDEN = 15; + static const uint PROPERTIES_COUNT = 16; + static const TQString SEPARATOR; + + Medium(const TQString id, TQString uuid, const TQString name); + static const Medium create(const TQStringList &properties); + static MList createList(const TQStringList &properties); + + const TQStringList &properties() const { return m_properties; } + + TQString id() const { return m_properties[ID]; } + TQString uuid() const { return m_properties[UUID]; } + TQString name() const { return m_properties[NAME]; } + TQString label() const { return m_properties[LABEL]; } + TQString userLabel() const { return m_properties[USER_LABEL]; } + bool isMountable() const { return m_properties[MOUNTABLE]=="true"; } + TQString deviceNode() const { return m_properties[DEVICE_NODE]; } + TQString mountPoint() const { return m_properties[MOUNT_POINT]; } + TQString fsType() const { return m_properties[FS_TYPE]; } + bool isMounted() const { return m_properties[MOUNTED]=="true"; } + TQString baseURL() const { return m_properties[BASE_URL]; } + TQString mimeType() const { return m_properties[MIME_TYPE]; } + TQString iconName() const { return m_properties[ICON_NAME]; } + bool isEncrypted() const { return m_properties[ENCRYPTED]=="true"; }; + TQString clearDeviceUdi() const { return m_properties[CLEAR_DEVICE_UDI]; }; + bool hidden() const { return m_properties[HIDDEN]=="true"; }; + + bool needMounting() const; + bool needDecryption() const; + KURL prettyBaseURL() const; + TQString prettyLabel() const; + + void setName(const TQString &name); + void setLabel(const TQString &label); + void setUserLabel(const TQString &label); + void setEncrypted(bool state); + void setHidden(bool state); + + bool mountableState(bool mounted); + void mountableState(const TQString &deviceNode, + const TQString &mountPoint, + const TQString &fsType, bool mounted); + void mountableState(const TQString &deviceNode, + const TQString &clearDeviceUdi, + const TQString &mountPoint, + const TQString &fsType, bool mounted); + void unmountableState(const TQString &baseURL = TQString::null); + + void setMimeType(const TQString &mimeType); + void setIconName(const TQString &iconName); + void setHalMounted(bool flag) const { m_halmounted = flag; } + bool halMounted() const { return m_halmounted; } + +//private: + Medium(); + +private: + void loadUserLabel(); + + TQStringList m_properties; + mutable bool m_halmounted; + +friend class TQValueListNode<const Medium>; +}; + +namespace MediaManagerUtils { + static inline TQMap<TQString,TQString> splitOptions(const TQStringList & options) + { + TQMap<TQString,TQString> valids; + + for (TQStringList::ConstIterator it = options.begin(); it != options.end(); ++it) + { + TQString key = (*it).left((*it).find('=')); + TQString value = (*it).mid((*it).find('=') + 1); + valids[key] = value; + } + return valids; + } +} + +#endif diff --git a/tdeioslave/media/libmediacommon/notifieraction.cpp b/tdeioslave/media/libmediacommon/notifieraction.cpp new file mode 100644 index 000000000..0dfc4110e --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifieraction.cpp @@ -0,0 +1,97 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqfile.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kicontheme.h> + +NotifierAction::NotifierAction() +{ +} + +NotifierAction::~NotifierAction() +{ +} + +void NotifierAction::setIconName(const TQString &iconName) +{ + m_iconName = iconName; +} + +void NotifierAction::setLabel(const TQString &label) +{ + m_label = label; +} + +TQString NotifierAction::iconName() const +{ + return m_iconName; +} + +TQPixmap NotifierAction::pixmap() const +{ + TQFile f( m_iconName ); + + if ( f.exists() ) + { + return TQPixmap( m_iconName ); + } + else + { + TQString path = TDEGlobal::iconLoader()->iconPath( m_iconName, -32 ); + return TQPixmap( path ); + } +} + +TQString NotifierAction::label() const +{ + return m_label; +} + +void NotifierAction::addAutoMimetype( const TQString &mimetype ) +{ + if ( !m_autoMimetypes.contains( mimetype ) ) + { + m_autoMimetypes.append( mimetype ); + } +} + +void NotifierAction::removeAutoMimetype( const TQString &mimetype ) +{ + m_autoMimetypes.remove( mimetype ); +} + +TQStringList NotifierAction::autoMimetypes() +{ + return m_autoMimetypes; +} + +bool NotifierAction::isWritable() const +{ + return false; +} + +bool NotifierAction::supportsMimetype(const TQString &/*mimetype*/) const +{ + return true; +} + diff --git a/tdeioslave/media/libmediacommon/notifieraction.h b/tdeioslave/media/libmediacommon/notifieraction.h new file mode 100644 index 000000000..0b8a87cac --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifieraction.h @@ -0,0 +1,60 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tdefileitem.h> +#include <tqstring.h> +#include <tqpixmap.h> + +class NotifierSettings; + +class NotifierAction +{ +public: + NotifierAction(); + virtual ~NotifierAction(); + + virtual TQString label() const; + virtual TQString iconName() const; + virtual void setLabel( const TQString &label ); + virtual void setIconName( const TQString &icon ); + + TQPixmap pixmap() const; + + TQStringList autoMimetypes(); + + virtual TQString id() const = 0; + virtual bool isWritable() const; + virtual bool supportsMimetype( const TQString &mimetype ) const; + virtual void execute( KFileItem &medium ) = 0; + +private: + void addAutoMimetype( const TQString &mimetype ); + void removeAutoMimetype( const TQString &mimetype ); + + TQString m_label; + TQString m_iconName; + TQStringList m_autoMimetypes; + + friend class NotifierSettings; +}; + +#endif diff --git a/tdeioslave/media/libmediacommon/notifiernothingaction.cpp b/tdeioslave/media/libmediacommon/notifiernothingaction.cpp new file mode 100644 index 000000000..6a86ad92f --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifiernothingaction.cpp @@ -0,0 +1,39 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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")); +} + +TQString NotifierNothingAction::id() const +{ + return "#NothinAction"; +} + +void NotifierNothingAction::execute(KFileItem &/*medium*/) +{ +} + diff --git a/tdeioslave/media/libmediacommon/notifiernothingaction.h b/tdeioslave/media/libmediacommon/notifiernothingaction.h new file mode 100644 index 000000000..2c0d22cfb --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifiernothingaction.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 TQString id() const; + virtual void execute(KFileItem &medium); +}; + +#endif + diff --git a/tdeioslave/media/libmediacommon/notifieropenaction.cpp b/tdeioslave/media/libmediacommon/notifieropenaction.cpp new file mode 100644 index 000000000..4d6d8025c --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifieropenaction.cpp @@ -0,0 +1,45 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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")); +} + +TQString NotifierOpenAction::id() const +{ + return "#OpenAction"; +} + +void NotifierOpenAction::execute(KFileItem &medium) +{ + medium.run(); +} + +bool NotifierOpenAction::supportsMimetype( const TQString &mimetype ) const +{ + return !mimetype.contains( "blank" ) && !mimetype.contains( "encrypted" ); +} + diff --git a/tdeioslave/media/libmediacommon/notifieropenaction.h b/tdeioslave/media/libmediacommon/notifieropenaction.h new file mode 100644 index 000000000..4fe8a614e --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifieropenaction.h @@ -0,0 +1,34 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 TQString id() const; + virtual void execute(KFileItem &medium); + bool supportsMimetype( const TQString &mimetype ) const; +}; + +#endif diff --git a/tdeioslave/media/libmediacommon/notifierserviceaction.cpp b/tdeioslave/media/libmediacommon/notifierserviceaction.cpp new file mode 100644 index 000000000..c7bece4f8 --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifierserviceaction.cpp @@ -0,0 +1,166 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqdir.h> +#include <tqfile.h> +#include <tqfileinfo.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"; +} + +TQString NotifierServiceAction::id() const +{ + if (m_filePath.isEmpty() || m_service.m_strName.isEmpty()) + { + return TQString(); + } + else + { + return "#Service:"+m_filePath; + } +} + +void NotifierServiceAction::setIconName( const TQString &icon ) +{ + m_service.m_strIcon = icon; + NotifierAction::setIconName( icon ); +} + +void NotifierServiceAction::setLabel( const TQString &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 TQString &filePath) +{ + m_filePath = filePath; +} + +TQString NotifierServiceAction::filePath() const +{ + return m_filePath; +} + +void NotifierServiceAction::updateFilePath() +{ + if ( !m_filePath.isEmpty() ) return; + + TQString action_name = m_service.m_strName; + action_name.replace( " ", "_" ); + + TQDir actions_dir( locateLocal( "data", "konqueror/servicemenus/", true ) ); + + TQString filename = actions_dir.absFilePath( action_name + ".desktop" ); + + int counter = 1; + while ( TQFile::exists( filename ) ) + { + filename = actions_dir.absFilePath( action_name + + TQString::number( counter ) + + ".desktop" ); + counter++; + } + + m_filePath = filename; +} + +void NotifierServiceAction::setMimetypes(const TQStringList &mimetypes) +{ + m_mimetypes = mimetypes; +} + +TQStringList NotifierServiceAction::mimetypes() +{ + return m_mimetypes; +} + +bool NotifierServiceAction::isWritable() const +{ + TQFileInfo info( m_filePath ); + + if ( info.exists() ) + { + return info.isWritable(); + } + else + { + info = TQFileInfo( info.dirPath() ); + return info.isWritable(); + } +} + +bool NotifierServiceAction::supportsMimetype(const TQString &mimetype) const +{ + return m_mimetypes.contains(mimetype); +} + +void NotifierServiceAction::save() const +{ + TQFile::remove( m_filePath ); + KDesktopFile desktopFile(m_filePath); + + desktopFile.setGroup(TQString("Desktop Action ") + m_service.m_strName); + desktopFile.writeEntry(TQString("Icon"), m_service.m_strIcon); + desktopFile.writeEntry(TQString("Name"), m_service.m_strName); + desktopFile.writeEntry(TQString("Exec"), m_service.m_strExec); + + desktopFile.setDesktopGroup(); + + desktopFile.writeEntry(TQString("ServiceTypes"), m_mimetypes, ","); + desktopFile.writeEntry(TQString("Actions"), + TQStringList(m_service.m_strName),";"); +} + diff --git a/tdeioslave/media/libmediacommon/notifierserviceaction.h b/tdeioslave/media/libmediacommon/notifierserviceaction.h new file mode 100644 index 000000000..829193fba --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifierserviceaction.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqstring.h> + +#include "notifieraction.h" + +class NotifierServiceAction : public NotifierAction +{ +public: + NotifierServiceAction(); + virtual TQString id() const; + virtual void execute(KFileItem &item); + + virtual void setIconName( const TQString &icon ); + virtual void setLabel( const TQString &label ); + + void setService(KDEDesktopMimeType::Service service); + KDEDesktopMimeType::Service service() const; + + void setFilePath(const TQString &filePath); + TQString filePath() const; + + void setMimetypes(const TQStringList &mimetypes); + TQStringList mimetypes(); + + virtual bool isWritable() const; + virtual bool supportsMimetype(const TQString &mimetype) const; + + void save() const; + +private: + void updateFilePath(); + + KDEDesktopMimeType::Service m_service; + TQString m_filePath; + TQStringList m_mimetypes; +}; + +#endif + diff --git a/tdeioslave/media/libmediacommon/notifiersettings.cpp b/tdeioslave/media/libmediacommon/notifiersettings.cpp new file mode 100644 index 000000000..254e591cb --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifiersettings.cpp @@ -0,0 +1,391 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqdir.h> +#include <tqfile.h> + +#include "notifieropenaction.h" +#include "notifiernothingaction.h" + + +NotifierSettings::NotifierSettings() +{ + m_supportedMimetypes.append( "media/removable_unmounted" ); + m_supportedMimetypes.append( "media/removable_unmounted_encrypted" ); + m_supportedMimetypes.append( "media/removable_unmounted_decrypted" ); + m_supportedMimetypes.append( "media/removable_mounted" ); + m_supportedMimetypes.append( "media/removable_mounted_decrypted" ); + 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_unmounted_encrypted" ); + m_supportedMimetypes.append( "media/cdrom_unmounted_decrypted" ); + m_supportedMimetypes.append( "media/cdrom_mounted" ); + m_supportedMimetypes.append( "media/cdrom_mounted_decrypted" ); + m_supportedMimetypes.append( "media/dvd_unmounted" ); + m_supportedMimetypes.append( "media/dvd_unmounted_encrypted" ); + m_supportedMimetypes.append( "media/dvd_unmounted_decrypted" ); + m_supportedMimetypes.append( "media/dvd_mounted" ); + m_supportedMimetypes.append( "media/dvd_mounted_decrypted" ); + m_supportedMimetypes.append( "media/cdwriter_unmounted" ); + m_supportedMimetypes.append( "media/cdwriter_unmounted_encrypted" ); + m_supportedMimetypes.append( "media/cdwriter_unmounted_decrypted" ); + m_supportedMimetypes.append( "media/cdwriter_mounted" ); + m_supportedMimetypes.append( "media/cdwriter_mounted_decrypted" ); + 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; + } +} + +TQValueList<NotifierAction*> NotifierSettings::actions() +{ + return m_actions; +} + +const TQStringList &NotifierSettings::supportedMimetypes() +{ + return m_supportedMimetypes; +} + +TQValueList<NotifierAction*> NotifierSettings::actionsForMimetype( const TQString &mimetype ) +{ + TQValueList<NotifierAction*> result; + + TQValueList<NotifierAction*>::iterator it = m_actions.begin(); + TQValueList<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 ); + + TQStringList auto_mimetypes = action->autoMimetypes(); + TQStringList::iterator it = auto_mimetypes.begin(); + TQStringList::iterator end = auto_mimetypes.end(); + + for ( ; it!=end; ++it ) + { + action->removeAutoMimetype( *it ); + m_autoMimetypesMap.remove( *it ); + } + + return true; + } + return false; +} + +void NotifierSettings::setAutoAction( const TQString &mimetype, NotifierAction *action ) +{ + resetAutoAction( mimetype ); + m_autoMimetypesMap[mimetype] = action; + action->addAutoMimetype( mimetype ); +} + + +void NotifierSettings::resetAutoAction( const TQString &mimetype ) +{ + if ( m_autoMimetypesMap.contains( mimetype ) ) + { + NotifierAction *action = m_autoMimetypesMap[mimetype]; + action->removeAutoMimetype( mimetype ); + m_autoMimetypesMap.remove(mimetype); + } +} + +void NotifierSettings::clearAutoActions() +{ + TQMap<TQString,NotifierAction*>::iterator it = m_autoMimetypesMap.begin(); + TQMap<TQString,NotifierAction*>::iterator end = m_autoMimetypesMap.end(); + + for ( ; it!=end; ++it ) + { + NotifierAction *action = it.data(); + TQString mimetype = it.key(); + + if ( action ) + action->removeAutoMimetype( mimetype ); + m_autoMimetypesMap[mimetype] = 0L; + } +} + +NotifierAction *NotifierSettings::autoActionForMimetype( const TQString &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; + + TQValueList<NotifierServiceAction*> services = listServices(); + + TQValueList<NotifierServiceAction*>::iterator serv_it = services.begin(); + TQValueList<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; + + TDEConfig config( "medianotifierrc", true ); + TQMap<TQString,TQString> auto_actions_map = config.entryMap( "Auto Actions" ); + + TQMap<TQString,TQString>::iterator auto_it = auto_actions_map.begin(); + TQMap<TQString,TQString>::iterator auto_end = auto_actions_map.end(); + + for ( ; auto_it!=auto_end; ++auto_it ) + { + TQString mime = auto_it.key(); + TQString action_id = auto_it.data(); + + if ( m_idMap.contains( action_id ) ) + { + setAutoAction( mime, m_idMap[action_id] ); + } + else + { + config.deleteEntry( mime ); + } + } +} +void NotifierSettings::save() +{ + TQValueList<NotifierAction*>::iterator act_it = m_actions.begin(); + TQValueList<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 ); + TQFile::remove( a->filePath() ); + delete a; + } + + KSimpleConfig config( "medianotifierrc" ); + config.setGroup( "Auto Actions" ); + + TQMap<TQString,NotifierAction*>::iterator auto_it = m_autoMimetypesMap.begin(); + TQMap<TQString,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() ); + } + } +} + +TQValueList<NotifierServiceAction*> NotifierSettings::loadActions( KDesktopFile &desktop ) const +{ + desktop.setDesktopGroup(); + + TQValueList<NotifierServiceAction*> services; + + const TQString filename = desktop.fileName(); + const TQStringList mimetypes = desktop.readListEntry( "ServiceTypes" ); + + TQValueList<KDEDesktopMimeType::Service> type_services + = KDEDesktopMimeType::userDefinedServices(filename, true); + + TQValueList<KDEDesktopMimeType::Service>::iterator service_it = type_services.begin(); + TQValueList<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 TQString &mimetype ) const +{ + desktop.setDesktopGroup(); + + if ( desktop.hasKey( "Actions" ) + && desktop.hasKey( "ServiceTypes" ) + && !desktop.readBoolEntry( "X-TDE-MediaNotifierHide", false ) ) + { + const TQStringList actions = desktop.readListEntry( "Actions" ); + + if ( actions.size()!=1 ) + { + return false; + } + + const TQStringList types = desktop.readListEntry( "ServiceTypes" ); + + if ( mimetype.isEmpty() ) + { + TQStringList::ConstIterator type_it = types.begin(); + TQStringList::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; +} + +TQValueList<NotifierServiceAction*> NotifierSettings::listServices( const TQString &mimetype ) const +{ + TQValueList<NotifierServiceAction*> services; + TQStringList dirs = TDEGlobal::dirs()->findDirs("data", "konqueror/servicemenus/"); + + TQStringList::ConstIterator dir_it = dirs.begin(); + TQStringList::ConstIterator dir_end = dirs.end(); + for (; dir_it != dir_end; ++dir_it) + { + TQDir dir( *dir_it ); + + TQStringList entries = dir.entryList( "*.desktop", TQDir::Files ); + + TQStringList::ConstIterator entry_it = entries.begin(); + TQStringList::ConstIterator entry_end = entries.end(); + + for (; entry_it != entry_end; ++entry_it ) + { + TQString filename = *dir_it + *entry_it; + + KDesktopFile desktop( filename, true ); + + if ( shouldLoadActions(desktop, mimetype) ) + { + services+=loadActions(desktop); + } + } + } + + return services; +} diff --git a/tdeioslave/media/libmediacommon/notifiersettings.h b/tdeioslave/media/libmediacommon/notifiersettings.h new file mode 100644 index 000000000..dee5c440b --- /dev/null +++ b/tdeioslave/media/libmediacommon/notifiersettings.h @@ -0,0 +1,63 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqvaluelist.h> +#include <tqmap.h> + +#include "notifieraction.h" +#include "notifierserviceaction.h" + + +class NotifierSettings +{ +public: + NotifierSettings(); + ~NotifierSettings(); + + TQValueList<NotifierAction*> actions(); + TQValueList<NotifierAction*> actionsForMimetype( const TQString &mimetype ); + + bool addAction( NotifierServiceAction *action ); + bool deleteAction( NotifierServiceAction *action ); + + void setAutoAction( const TQString &mimetype, NotifierAction *action ); + void resetAutoAction( const TQString &mimetype ); + void clearAutoActions(); + NotifierAction *autoActionForMimetype( const TQString &mimetype ); + + const TQStringList &supportedMimetypes(); + + void reload(); + void save(); + +private: + TQValueList<NotifierServiceAction*> listServices( const TQString &mimetype = TQString() ) const; + bool shouldLoadActions( KDesktopFile &desktop, const TQString &mimetype ) const; + TQValueList<NotifierServiceAction*> loadActions( KDesktopFile &desktop ) const; + + TQStringList m_supportedMimetypes; + TQValueList<NotifierAction*> m_actions; + TQValueList<NotifierServiceAction*> m_deletedActions; + TQMap<TQString,NotifierAction*> m_idMap; + TQMap<TQString,NotifierAction*> m_autoMimetypesMap; +}; +#endif diff --git a/tdeioslave/media/media.protocol b/tdeioslave/media/media.protocol new file mode 100644 index 000000000..f535e1a41 --- /dev/null +++ b/tdeioslave/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=tdeioslave/file.html +Class=:local +deleteRecursive=true +fileNameUsedForCopying=Name diff --git a/tdeioslave/media/mediaimpl.cpp b/tdeioslave/media/mediaimpl.cpp new file mode 100644 index 000000000..a6ed957ae --- /dev/null +++ b/tdeioslave/media/mediaimpl.cpp @@ -0,0 +1,449 @@ +/* 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 <tdeio/netaccess.h> + +#include <kmimetype.h> + +#include <kapplication.h> +#include <tqeventloop.h> + +#include <sys/stat.h> + +#include "medium.h" + +#include <config.h> + +MediaImpl::MediaImpl() : TQObject(), DCOPObject("mediaimpl"), mp_mounting(0L) +{ + +} + +bool MediaImpl::parseURL(const KURL &url, TQString &name, TQString &path) const +{ + TQString 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 = TQString::null; + } + + return name != TQString::null; +} + +bool MediaImpl::realURL(const TQString &name, const TQString &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 TQString &name, TDEIO::UDSEntry &entry) +{ + kdDebug(1219) << "MediaImpl::statMedium: " << name << endl; + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "properties", name ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = TDEIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The TDE 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 TQString &label, TDEIO::UDSEntry &entry) +{ + kdDebug(1219) << "MediaImpl::statMediumByLabel: " << label << endl; + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "nameForLabel", label ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = TDEIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The TDE mediamanager is not running."); + return false; + } + + TQString name = reply; + + if (name.isEmpty()) + { + entry.clear(); + return false; + } + + return statMedium(name, entry); +} + + +bool MediaImpl::listMedia(TQValueList<TDEIO::UDSEntry> &list) +{ + kdDebug(1219) << "MediaImpl::listMedia" << endl; + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "fullList" ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = TDEIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The TDE mediamanager is not running."); + return false; + } + + const Medium::MList media = Medium::createList(reply); + + TDEIO::UDSEntry entry; + + Medium::MList::const_iterator it = media.begin(); + Medium::MList::const_iterator end = media.end(); + + for(; it!=end; ++it) + { + if (!(*it).hidden()) { + entry.clear(); + + createMediumEntry(entry, *it); + + list.append(entry); + } + } + + return true; +} + +bool MediaImpl::setUserLabel(const TQString &name, const TQString &label) +{ + kdDebug(1219) << "MediaImpl::setUserLabel: " << name << ", " << label << endl; + + + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "nameForLabel", label ); + + if ( !reply.isValid() ) + { + m_lastErrorCode = TDEIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The TDE mediamanager is not running."); + return false; + } + else + { + TQString returned_name = reply; + if (!returned_name.isEmpty() + && returned_name!=name) + { + m_lastErrorCode = TDEIO::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 = TDEIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The TDE mediamanager is not running."); + return false; + } + else + { + return true; + } +} + +const Medium MediaImpl::findMediumByName(const TQString &name, bool &ok) +{ + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "properties", name ); + + if ( reply.isValid() ) + { + ok = true; + } + else + { + m_lastErrorCode = TDEIO::ERR_SLAVE_DEFINED; + m_lastErrorMessage = i18n("The TDE mediamanager is not running."); + ok = false; + } + + return Medium::create(reply); +} + +bool MediaImpl::ensureMediumMounted(Medium &medium) +{ + if (medium.id().isEmpty()) + { + m_lastErrorCode = TDEIO::ERR_COULD_NOT_MOUNT; + m_lastErrorMessage = i18n("No such medium."); + return false; + } + +#ifdef COMPILE_HALBACKEND + if ( medium.isEncrypted() && medium.clearDeviceUdi().isEmpty() ) + { + m_lastErrorCode = TDEIO::ERR_COULD_NOT_MOUNT; + m_lastErrorMessage = i18n("The drive is encrypted."); + return false; + } +#endif // COMPILE_HALBACKEND + + if ( medium.needMounting() ) + { + m_lastErrorCode = 0; + + mp_mounting = &medium; + + + /* + TDEIO::Job* job = TDEIO::mount(false, 0, + medium.deviceNode(), + medium.mountPoint()); + job->setAutoWarningHandlingEnabled(false); + connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), + this, TQT_SLOT( slotMountResult( TDEIO::Job * ) ) ); + connect( job, TQT_SIGNAL( warning( TDEIO::Job *, const TQString & ) ), + this, TQT_SLOT( slotWarning( TDEIO::Job *, const TQString & ) ) ); + */ + kapp->dcopClient() + ->connectDCOPSignal("kded", "mediamanager", + "mediumChanged(TQString, bool)", + "mediaimpl", + "slotMediumChanged(TQString)", + 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 = TDEIO::ERR_SLAVE_DEFINED; + else { + tqApp->eventLoop()->enterLoop(); + } + + mp_mounting = 0L; + + kapp->dcopClient() + ->disconnectDCOPSignal("kded", "mediamanager", + "mediumChanged(TQString, bool)", + "mediaimpl", + "slotMediumChanged(TQString)"); + + return m_lastErrorCode==0; + } + + if (medium.id().isEmpty()) + { + m_lastErrorCode = TDEIO::ERR_COULD_NOT_MOUNT; + m_lastErrorMessage = i18n("No such medium."); + return false; + } + + return true; +} + +void MediaImpl::slotWarning( TDEIO::Job * /*job*/, const TQString &msg ) +{ + emit warning( msg ); +} + +void MediaImpl::slotMountResult(TDEIO::Job *job) +{ + kdDebug(1219) << "MediaImpl::slotMountResult" << endl; + + if ( job->error() != 0) + { + m_lastErrorCode = job->error(); + m_lastErrorMessage = job->errorText(); + tqApp->eventLoop()->exitLoop(); + } +} + +void MediaImpl::slotMediumChanged(const TQString &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); + tqApp->eventLoop()->exitLoop(); + } +} + +static void addAtom(TDEIO::UDSEntry &entry, unsigned int ID, long l, + const TQString &s = TQString::null) +{ + TDEIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void MediaImpl::createTopLevelEntry(TDEIO::UDSEntry& entry) const +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_URL, 0, "media:/"); + addAtom(entry, TDEIO::UDS_NAME, 0, "."); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_ACCESS, 0555); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, "blockdevice"); +} + +void MediaImpl::slotStatResult(TDEIO::Job *job) +{ + if ( job->error() == 0) + { + TDEIO::StatJob *stat_job = static_cast<TDEIO::StatJob *>(job); + m_entryBuffer = stat_job->statResult(); + } + + tqApp->eventLoop()->exitLoop(); +} + +TDEIO::UDSEntry MediaImpl::extractUrlInfos(const KURL &url) +{ + m_entryBuffer.clear(); + + TDEIO::StatJob *job = TDEIO::stat(url, false); + job->setAutoWarningHandlingEnabled( false ); + connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), + this, TQT_SLOT( slotStatResult(TDEIO::Job *) ) ); + connect( job, TQT_SIGNAL( warning( TDEIO::Job *, const TQString & ) ), + this, TQT_SLOT( slotWarning( TDEIO::Job *, const TQString & ) ) ); + tqApp->eventLoop()->enterLoop(); + + TDEIO::UDSEntry::iterator it = m_entryBuffer.begin(); + TDEIO::UDSEntry::iterator end = m_entryBuffer.end(); + + TDEIO::UDSEntry infos; + + for(; it!=end; ++it) + { + switch( (*it).m_uds ) + { + case TDEIO::UDS_ACCESS: + case TDEIO::UDS_USER: + case TDEIO::UDS_GROUP: + case TDEIO::UDS_CREATION_TIME: + case TDEIO::UDS_MODIFICATION_TIME: + case TDEIO::UDS_ACCESS_TIME: + infos.append(*it); + break; + default: + break; + } + } + + if (url.isLocalFile()) + { + addAtom(infos, TDEIO::UDS_LOCAL_PATH, 0, url.path()); + } + + return infos; +} + + +void MediaImpl::createMediumEntry(TDEIO::UDSEntry& entry, + const Medium &medium) +{ + kdDebug(1219) << "MediaProtocol::createMedium" << endl; + + TQString url = "media:/"+medium.name(); + + kdDebug(1219) << "url = " << url << ", mime = " << medium.mimeType() << endl; + + entry.clear(); + + addAtom(entry, TDEIO::UDS_URL, 0, url); + + TQString label = TDEIO::encodeFileName( medium.prettyLabel() ); + addAtom(entry, TDEIO::UDS_NAME, 0, label); + + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, medium.mimeType()); + addAtom(entry, TDEIO::UDS_GUESSED_MIME_TYPE, 0, "inode/directory"); + + if (!medium.iconName().isEmpty()) + { + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, medium.iconName()); + } + else + { + TQString mime = medium.mimeType(); + TQString icon = KMimeType::mimeType(mime)->icon(mime, false); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, icon); + } + + if (medium.needMounting()) + { + addAtom(entry, TDEIO::UDS_ACCESS, 0400); + } + else + { + KURL url = medium.prettyBaseURL(); + entry+= extractUrlInfos(url); + } +} + +#include "mediaimpl.moc" diff --git a/tdeioslave/media/mediaimpl.h b/tdeioslave/media/mediaimpl.h new file mode 100644 index 000000000..1080d6783 --- /dev/null +++ b/tdeioslave/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 <tdeio/global.h> +#include <tdeio/job.h> +#include <kurl.h> +#include <dcopobject.h> + +#include <tqobject.h> +#include <tqstring.h> + +#include "medium.h" + +class MediaImpl : public TQObject, public DCOPObject +{ +Q_OBJECT +K_DCOP +public: + MediaImpl(); + bool parseURL(const KURL &url, TQString &name, TQString &path) const; + bool realURL(const TQString &name, const TQString &path, KURL &url); + + bool statMedium(const TQString &name, TDEIO::UDSEntry &entry); + bool statMediumByLabel(const TQString &label, TDEIO::UDSEntry &entry); + bool listMedia(TQValueList<TDEIO::UDSEntry> &list); + bool setUserLabel(const TQString &name, const TQString &label); + + void createTopLevelEntry(TDEIO::UDSEntry& entry) const; + + int lastErrorCode() const { return m_lastErrorCode; } + TQString lastErrorMessage() const { return m_lastErrorMessage; } + +k_dcop: + void slotMediumChanged(const TQString &name); + +signals: + void warning(const TQString &msg); + +private slots: + void slotWarning(TDEIO::Job *job, const TQString &msg); + void slotMountResult(TDEIO::Job *job); + void slotStatResult(TDEIO::Job *job); + +private: + const Medium findMediumByName(const TQString &name, bool &ok); + bool ensureMediumMounted(Medium &medium); + + TDEIO::UDSEntry extractUrlInfos(const KURL &url); + TDEIO::UDSEntry m_entryBuffer; + + void createMediumEntry(TDEIO::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; + TQString m_lastErrorMessage; +}; + +#endif diff --git a/tdeioslave/media/mediamanager/CMakeLists.txt b/tdeioslave/media/mediamanager/CMakeLists.txt new file mode 100644 index 000000000..5a73f3708 --- /dev/null +++ b/tdeioslave/media/mediamanager/CMakeLists.txt @@ -0,0 +1,65 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR}/tdeioslave/media/libmediacommon + ${CMAKE_SOURCE_DIR}/tdeioslave/media/libmediacommon + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} + ${HAL_INCLUDE_DIRS} + ${DBUS_TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} + ${DBUS_TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES mediamanager.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded ) + + +##### kded_mediamanager (module) ################ + +set( target kded_mediamanager ) + +if( WITH_HAL ) + set( ${target}_SRCS + mediamanager.cpp mediamanager.skel medialist.cpp + backendbase.cpp fstabbackend.cpp removablebackend.cpp + mediadirnotify.cpp mediadirnotify.skel + decryptdialog.ui dialog.cpp + halbackend.cpp linuxcdpolling.cpp tdehardwarebackend.cpp + ) + + tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK mediacommon-static tdeinit_kded-shared ${HAL_LIBRARIES} -lhal-storage ${DBUS_TQT_LIBRARIES} + DESTINATION ${PLUGIN_INSTALL_DIR} + ) +else( ) + set( ${target}_SRCS + mediamanager.cpp mediamanager.skel medialist.cpp + backendbase.cpp fstabbackend.cpp removablebackend.cpp + decryptdialog.ui dialog.cpp + mediadirnotify.cpp mediadirnotify.skel tdehardwarebackend.cpp + ) + + tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK mediacommon-static tdeinit_kded-shared + DESTINATION ${PLUGIN_INSTALL_DIR} + ) +endif( ) diff --git a/tdeioslave/media/mediamanager/Makefile.am b/tdeioslave/media/mediamanager/Makefile.am new file mode 100644 index 000000000..45289a9eb --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mediamanager/backendbase.cpp b/tdeioslave/media/mediamanager/backendbase.cpp new file mode 100644 index 000000000..157acfca5 --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mediamanager/backendbase.h b/tdeioslave/media/mediamanager/backendbase.h new file mode 100644 index 000000000..689522d1f --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mediamanager/decryptdialog.ui b/tdeioslave/media/mediamanager/decryptdialog.ui new file mode 100644 index 000000000..939f0a36a --- /dev/null +++ b/tdeioslave/media/mediamanager/decryptdialog.ui @@ -0,0 +1,201 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>DecryptDialog</class> +<widget class="TQWidget"> + <property name="name"> + <cstring>DecryptDialog</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>207</width> + <height>172</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Decrypting Storage Device</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <property name="name"> + <cstring>encryptedIcon</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>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>AlignTop</set> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>31</width> + <height>41</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="TQLabel"> + <property name="name"> + <cstring>descLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><p><b>%1</b> is an encrypted storage device.</p> +<p>Please enter the password to decrypt the storage device.</p></string> + </property> + <property name="alignment"> + <set>WordBreak|AlignTop</set> + </property> + </widget> + </hbox> + </widget> + <widget class="TQLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>&Password:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>passwordEdit</cstring> + </property> + </widget> + <widget class="TQLineEdit"> + <property name="name"> + <cstring>passwordEdit</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="echoMode"> + <enum>Password</enum> + </property> + </widget> + </hbox> + </widget> + <widget class="TQGroupBox" row="2" column="0"> + <property name="name"> + <cstring>errorBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Error</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel" row="0" column="0"> + <property name="name"> + <cstring>errorLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>WordBreak|AlignTop</set> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/tdeioslave/media/mediamanager/dialog.cpp b/tdeioslave/media/mediamanager/dialog.cpp new file mode 100644 index 000000000..40e99a84d --- /dev/null +++ b/tdeioslave/media/mediamanager/dialog.cpp @@ -0,0 +1,68 @@ +/* This file is part of the KDE project + * Copyright (C) 2007 Jan Klötzke <jan kloetzke at freenet de> + * + * Based on kryptomedia- Another KDE cryto media application. + * Copyright (C) 2006 Daniel Gollub <[email protected]> + * + * 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 "dialog.h" + +Dialog::Dialog(TQString url, TQString iconName) : + KDialogBase(NULL, "Dialog", true, "Decrypt Storage Device", (Cancel|User1), User1, false, KGuiItem(i18n("Decrypt"), "decrypted" )) +{ + decryptDialog = new DecryptDialog(this); + + decryptDialog->errorBox->hide(); + decryptDialog->descLabel->setText(decryptDialog->descLabel->text().arg(url)); + decryptDialog->descLabel->adjustSize(); + decryptDialog->adjustSize(); + + enableButton( User1, false ); + + TQPixmap pixmap = TDEGlobal::iconLoader()->loadIcon(iconName, KIcon::NoGroup, KIcon::SizeLarge); + decryptDialog->encryptedIcon->setPixmap( pixmap ); + + connect(decryptDialog->passwordEdit, TQT_SIGNAL (textChanged(const TQString &)), this, TQT_SLOT (slotPasswordChanged(const TQString &))); + + setMainWidget(decryptDialog); +} + +Dialog::~Dialog() +{ + delete decryptDialog; +} + +TQString Dialog::getPassword() +{ + return decryptDialog->passwordEdit->text(); +} + +void Dialog::slotDialogError(TQString errorMsg) +{ + kdDebug() << __func__ << "(" << errorMsg << " )" << endl; + + decryptDialog->errorLabel->setText(TQString("<b>%1</b>").arg(errorMsg)); + decryptDialog->errorBox->show(); +} + +void Dialog::slotPasswordChanged(const TQString &text) +{ + enableButton( User1, !text.isEmpty() ); +} + +#include "dialog.moc" diff --git a/tdeioslave/media/mediamanager/dialog.h b/tdeioslave/media/mediamanager/dialog.h new file mode 100644 index 000000000..dc1d552d8 --- /dev/null +++ b/tdeioslave/media/mediamanager/dialog.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + * Copyright (C) 2007 Jan Klötzke <jan kloetzke at freenet de> + * + * Based on kryptomedia- Another KDE cryto media application. + * Copyright (C) 2006 Daniel Gollub <[email protected]> + * + * 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 DIALOG_H_ +#define DIALOG_H_ + +#include <kmessagebox.h> +#include <klocale.h> +#include <tdeconfig.h> +#include <kdebug.h> +#include <kdialogbase.h> +#include <kiconloader.h> + +#include <tqlineedit.h> +#include <tqlabel.h> +#include <tqgroupbox.h> + +#include "decryptdialog.h" + +class KryptoMedia; + +class Dialog : public KDialogBase +{ + +Q_OBJECT + +public: + Dialog(TQString url, TQString iconName); + ~Dialog(); + + TQString getPassword(); + +public slots: + void slotDialogError(TQString errorMsg); + void slotPasswordChanged(const TQString &text); + +private: + DecryptDialog *decryptDialog; +}; + +#endif // DIALOG_H_ + diff --git a/tdeioslave/media/mediamanager/fstabbackend.cpp b/tdeioslave/media/mediamanager/fstabbackend.cpp new file mode 100644 index 000000000..0069a8e8e --- /dev/null +++ b/tdeioslave/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 <tqfile.h> +#endif + +#include <klocale.h> +#include <tdeio/job.h> +#include <tdeio/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) + : TQObject(), BackendBase(list), m_networkSharesOnly(networkSharesOnly) +{ + KDirWatch::self()->addFile(MTAB); + KDirWatch::self()->addFile(FSTAB); + + connect( KDirWatch::self(), TQT_SIGNAL( dirty(const TQString&) ), + this, TQT_SLOT( slotDirty(const TQString&) ) ); + + handleFstabChange(false); + handleMtabChange(false); + + KDirWatch::self()->startScan(); + +#ifdef Q_OS_FREEBSD + connect( &m_mtabTimer, TQT_SIGNAL( timeout() ), + this, TQT_SLOT( handleMtabChange() ) ); + m_mtabTimer.start(250); +#endif +} + +FstabBackend::~FstabBackend() +{ + TQStringList::iterator it = m_mtabIds.begin(); + TQStringList::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); +} + +TQString FstabBackend::mount( const TQString &_udi ) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + TDEIO::Job* job = TDEIO::mount( false, 0, medium->deviceNode(), medium->mountPoint()); + TDEIO::NetAccess::synchronousRun( job, 0 ); + return TQString::null; +} + +TQString FstabBackend::unmount( const TQString &_udi ) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + TDEIO::Job* job = TDEIO::unmount( medium->mountPoint(), false); + TDEIO::NetAccess::synchronousRun( job, 0 ); + return TQString::null; +} + +void FstabBackend::slotDirty(const TQString &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) +{ + TQStringList new_mtabIds; + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + + KMountPoint::List::iterator it = mtab.begin(); + KMountPoint::List::iterator end = mtab.end(); + + for (; it!=end; ++it) + { + TQString dev = (*it)->mountedFrom(); + TQString mp = (*it)->mountPoint(); + TQString 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 */ + TQString mtabEntry = dev + "*" + mp + "*" + fs; + if(m_mtabEntries.contains(mtabEntry)) { + new_mtabIds += m_mtabEntries[mtabEntry]; + continue; + } + + TQString id = generateId(dev, mp); + new_mtabIds+=id; + m_mtabEntries[mtabEntry] = id; + + if ( !m_mtabIds.contains(id) && m_fstabIds.contains(id) ) + { + TQString 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) ) + { + TQString name = generateName(dev, fs); + + Medium *m = new Medium(id, name); + + m->mountableState(dev, mp, fs, true); + + TQString 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 + } + + TQStringList::iterator it2 = m_mtabIds.begin(); + TQStringList::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); + + TQString dev = medium->deviceNode(); + TQString mp = medium->mountPoint(); + TQString fs = medium->fsType(); + + + TQString mtabEntry = dev + "*" + mp + "*" + fs; + m_mtabEntries.remove(mtabEntry); + + TQString 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) +{ + TQStringList new_fstabIds; + KMountPoint::List fstab = KMountPoint::possibleMountPoints(); + + KMountPoint::List::iterator it = fstab.begin(); + KMountPoint::List::iterator end = fstab.end(); + + for (; it!=end; ++it) + { + TQString dev = (*it)->mountedFrom(); + TQString mp = (*it)->mountPoint(); + TQString fs = (*it)->mountType(); + + if ( ::inExclusionPattern(*it, m_networkSharesOnly) ) continue; + + TQString id = generateId(dev, mp); + new_fstabIds+=id; + + if ( !m_fstabIds.contains(id) ) + { + TQString name = generateName(dev, fs); + + Medium *m = new Medium(id, id, name); + + m->mountableState(dev, mp, fs, false); + + TQString 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); + } + } + + TQStringList::iterator it2 = m_fstabIds.begin(); + TQStringList::iterator end2 = m_fstabIds.end(); + + for (; it2!=end2; ++it2) + { + if ( !new_fstabIds.contains(*it2) ) + { + m_mediaList.removeMedium(*it2, allowNotification); + } + } + + m_fstabIds = new_fstabIds; +} + +TQString FstabBackend::generateId(const TQString &devNode, + const TQString &mountPoint) +{ + TQString d = KStandardDirs::realFilePath(devNode); + TQString m = KStandardDirs::realPath(mountPoint); + + return "/org/kde/mediamanager/fstab/" + +d.replace("/", "") + +m.replace("/", ""); +} + +TQString FstabBackend::generateName(const TQString &devNode, const TQString &fsType) +{ + KURL url( devNode ); + + if ( url.isValid() ) + { + return url.fileName(); + } + else // surely something nfs or samba based + { + return fsType; + } +} + +void FstabBackend::guess(const TQString &devNode, const TQString &mountPoint, + const TQString &fsType, bool mounted, + TQString &mimeType, TQString &iconName, TQString &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(TQFile::encodeName(devNode), O_RDONLY|O_NONBLOCK); + if(device>=0) + { + bool isCd=false; + TQString 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 + TQFile m(TQString("/proc/ide/") + devname + "/media"); + if(m.open(IO_ReadOnly)) + { + TQString 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 + { + TQString tmp = devNode; + if ( tmp.startsWith("/dev/") ) + { + tmp = tmp.mid(5); + } + label+= " (" + tmp + ")"; + } + mimeType+= (mounted ? "_mounted" : "_unmounted"); + iconName = TQString::null; +} + +#include "fstabbackend.moc" diff --git a/tdeioslave/media/mediamanager/fstabbackend.h b/tdeioslave/media/mediamanager/fstabbackend.h new file mode 100644 index 000000000..e43ed48c0 --- /dev/null +++ b/tdeioslave/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 <tqobject.h> +#include <tqstringlist.h> +#include <tqmap.h> + +#ifdef Q_OS_FREEBSD +#include <tqtimer.h> +#endif + +class FstabBackend : public TQObject, public BackendBase +{ +Q_OBJECT + +public: + FstabBackend(MediaList &list, bool networkSharesOnly = false); + virtual ~FstabBackend(); + + static void guess(const TQString &devNode, const TQString &mountPoint, + const TQString &fsType, bool mounted, + TQString &mimeType, TQString &iconName, + TQString &label); + + TQString mount(const TQString &id); + TQString unmount(const TQString &id); + +private slots: + void slotDirty(const TQString &path); + void handleFstabChange(bool allowNotification = true); + void handleMtabChange(bool allowNotification = true); + +private: + static TQString generateId(const TQString &devNode, + const TQString &mountPoint); + static TQString generateName(const TQString &devNode, + const TQString &fsType); + + bool m_networkSharesOnly; + TQStringList m_mtabIds; + TQMap<TQString, TQString> m_mtabEntries; + TQStringList m_fstabIds; +#ifdef Q_OS_FREEBSD + TQTimer m_mtabTimer; +#endif +}; + +#endif diff --git a/tdeioslave/media/mediamanager/halbackend.cpp b/tdeioslave/media/mediamanager/halbackend.cpp new file mode 100644 index 000000000..aba2b0175 --- /dev/null +++ b/tdeioslave/media/mediamanager/halbackend.cpp @@ -0,0 +1,1858 @@ +/* This file is part of the KDE Project + Copyright (c) 2004-2005 Jérôme Lodewyck <jerome dot lodewyck at normalesup dot org> + Copyright (c) 2006 Valentine Sinitsyn <[email protected]> + + 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 <locale.h> + +#include <kapplication.h> +#include <kmessagebox.h> +#include <tqeventloop.h> +#include <tqfile.h> +#include <klocale.h> +#include <kurl.h> +#include <kdebug.h> +#include <kprocess.h> +#include <tdeconfig.h> +#include <tqstylesheet.h> +#include <kmountpoint.h> +#include <kmessagebox.h> +#include <tdeio/job.h> +#include <kprotocolinfo.h> +#include <kstandarddirs.h> +#include <kprocess.h> + +#define MOUNT_SUFFIX ( \ + (medium->isMounted() ? TQString("_mounted") : TQString("_unmounted")) + \ + (medium->isEncrypted() ? (halClearVolume ? "_decrypted" : "_encrypted") : "" ) \ + ) +#define MOUNT_ICON_SUFFIX ( \ + (medium->isMounted() ? TQString("_mount") : TQString("_unmount")) + \ + (medium->isEncrypted() ? (halClearVolume ? "_decrypt" : "_encrypt") : "" ) \ + ) + +/* Static instance of this class, for static HAL callbacks */ +static HALBackend* s_HALBackend; + +/* A macro function to convert HAL string properties to TQString */ +TQString libhal_device_get_property_QString(LibHalContext *ctx, const char* udi, const char *key) +{ + char* _ppt_string; + TQString _ppt_QString; + _ppt_string = libhal_device_get_property_string(ctx, udi, key, NULL); + if ( _ppt_string ) + _ppt_QString = _ppt_string; + libhal_free_string(_ppt_string); + return _ppt_QString; +} + +/* Constructor */ +HALBackend::HALBackend(MediaList &list, TQObject* parent) + : TQObject() + , BackendBase(list) + , m_halContext(NULL) + , m_halStoragePolicy(NULL) + , m_parent(parent) +{ + s_HALBackend = this; +} + +/* Destructor */ +HALBackend::~HALBackend() +{ + /* Close HAL connection */ + if (m_halContext) + { + const TQPtrList<Medium> medlist = m_mediaList.list(); + TQPtrListIterator<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 volumes that... + * - are encrypted with LUKS or + * - have a filesystem or + * - have an audio track + */ + if ( ( libhal_device_get_property_QString(m_halContext, udi, "volume.fsusage") != "crypto" || + libhal_device_get_property_QString(m_halContext, udi, "volume.fstype") != "crypto_LUKS" + ) && + 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 */ + TQString 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 **/ + + /* Special handling for clear crypto volumes */ + LibHalVolume* halVolume = libhal_volume_from_udi(m_halContext, udi); + if (!halVolume) + return; + const char* backingVolumeUdi = libhal_volume_crypto_get_backing_volume_udi(halVolume); + if ( backingVolumeUdi != NULL ) + { + /* The crypto drive was unlocked and may now be mounted... */ + kdDebug(1219) << "HALBackend::AddDevice : ClearVolume appeared for " << backingVolumeUdi << endl; + ResetProperties(backingVolumeUdi, allowNotification); + libhal_volume_free(halVolume); + return; + } + libhal_volume_free(halVolume); + + /* Create medium */ + Medium* medium = new Medium(udi, 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; + } + } + + // instert medium into list + m_mediaList.addMedium(medium, allowNotification); + + // finally check for automount + TQMap<TQString,TQString> options = MediaManagerUtils::splitOptions(mountoptions(udi)); + kdDebug() << "automount " << options["automount"] << endl; + if (options["automount"] == "true" && allowNotification ) { + TQString error = mount(medium); + if (!error.isEmpty()) + kdDebug() << "error " << error << endl; + } + + 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, 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, udi, ""); + setCameraProperties(medium); + m_mediaList.addMedium(medium, allowNotification); + return; + } +} + +void HALBackend::RemoveDevice(const char *udi) +{ + const Medium *medium = m_mediaList.findByClearUdi(udi); + if (medium) { + ResetProperties(medium->id().ascii()); + } else { + m_mediaList.removeMedium(udi, true); + } +} + +void HALBackend::ModifyDevice(const char *udi, const char* key) +{ + kdDebug(1219) << "HALBackend::ModifyDevice for '" << udi << "' on '" << key << "'\n"; + + 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) +{ + TQString conditionName = TQString(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 TQPtrList<Medium> medlist = m_mediaList.list(); + TQPtrListIterator<Medium> it (medlist); + for ( const Medium *current_medium = it.current(); current_medium; current_medium = ++it) + { + if( current_medium->id().startsWith( "/org/kde" )) + continue; + TQString driveUdi = libhal_device_get_property_QString(m_halContext, current_medium->id().latin1(), "block.storage_device"); + if (driveUdi == udi) + { + medium = current_medium; + break; + } + } + } + if (medium) { + TDEProcess p; + p << "kio_media_mounthelper" << "-e" << medium->name(); + p.start(TDEProcess::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)) + { + /* check if this belongs to an encrypted volume */ + LibHalVolume* halVolume = libhal_volume_from_udi(m_halContext, udi); + if (!halVolume) return NULL; + const char* backingUdi = libhal_volume_crypto_get_backing_volume_udi(halVolume); + if (backingUdi != NULL) { + const char* result = findMediumUdiFromUdi(backingUdi); + libhal_volume_free(halVolume); + return result; + } + libhal_volume_free(halVolume); + + /* this is a volume whose drive is registered */ + TQString 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 ( TQString::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, 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; + TQString 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)) ); + + LibHalVolume* halClearVolume = NULL; + if ( libhal_device_get_property_QString(m_halContext, udi, "volume.fsusage") == "crypto" ) + { + kdDebug(1219) << "HALBackend::setVolumeProperties : crypto volume" << endl; + + medium->setEncrypted(true); + char* clearUdi = libhal_volume_crypto_get_clear_volume_udi(m_halContext, halVolume); + TQString clearUdiString; + if (clearUdi != NULL) { + kdDebug(1219) << "HALBackend::setVolumeProperties : crypto clear volume avail - " << clearUdi << endl; + halClearVolume = libhal_volume_from_udi(m_halContext, clearUdi); + // ignore if halClearVolume is NULL -> just not decrypted in this case + clearUdiString = clearUdi; + libhal_free_string(clearUdi); + } + + if (halClearVolume) + medium->mountableState( + libhal_volume_get_device_file(halVolume), /* Device node */ + clearUdiString, + libhal_volume_get_mount_point(halClearVolume), /* Mount point */ + libhal_volume_get_fstype(halClearVolume), /* Filesystem type */ + libhal_volume_is_mounted(halClearVolume) ); /* Mounted ? */ + else + medium->mountableState( + libhal_volume_get_device_file(halVolume), /* Device node */ + TQString::null, + TQString::null, /* Mount point */ + TQString::null, /* Filesystem type */ + false ); /* Mounted ? */ + } + else + { + kdDebug(1219) << "HALBackend::setVolumeProperties : normal volume" << endl; + 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); + TQString volume_name = TQString::fromUtf8(name); + TQString media_name = volume_name; + medium->setLabel(media_name); + free(name); + + TQString 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=" + TQString(libhal_volume_get_device_file(halVolume)) ); + } + + medium->setIconName(TQString::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(TQString::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); + + if (libhal_device_get_property_QString(m_halContext, driveUdi.latin1(), "info.product") == "iPod" && + KProtocolInfo::isKnownProtocol( TQString("ipod") ) ) + { + medium->unmountableState( "ipod:/" ); + medium->mountableState( libhal_volume_is_mounted(halVolume) ); + } + 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(TQString::null); //FIXME need icon + break; + default: + medium->setIconName(TQString::null); + } + + if (medium->isMounted() && TQFile::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 ) +{ + TQString 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; + TQString 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; + + TQString 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), TQString::null, TQString::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(TQString::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(); + + TQString fstype; + TQString 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"); + + TQString 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(TQString::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")); +} + +TQString HALBackend::generateName(const TQString &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); +} + +TQStringList HALBackend::getHALmountoptions(TQString udi) +{ + const char* _ppt_string; + LibHalVolume* volume; + LibHalDrive* drive; + + TQString _ppt_TQString; + + volume = libhal_volume_from_udi( m_halContext, udi.latin1() ); + if( volume ) + drive = libhal_drive_from_udi( m_halContext, libhal_volume_get_storage_device_udi( volume ) ); + else + drive = libhal_drive_from_udi( m_halContext, udi.latin1() ); + + if( !drive ) + return TQString::null; + + if( volume ) + _ppt_string = libhal_volume_policy_get_mount_options ( drive, volume, NULL ); + else + _ppt_string = libhal_drive_policy_get_mount_options ( drive, NULL ); + + _ppt_TQString = TQString(_ppt_string ? _ppt_string : ""); + + return TQStringList::split(",",_ppt_TQString); +} + +TQStringList HALBackend::mountoptions(const TQString &name) +{ + const Medium* medium = m_mediaList.findById(name); + if (!medium) + return TQStringList(); // we don't know about that one + if (!isInFstab(medium).isNull()) + return TQStringList(); // not handled by HAL - fstab entry + + TQString volume_udi = name; + if (medium->isEncrypted()) { + // see if we have a clear volume + LibHalVolume* halVolume = libhal_volume_from_udi(m_halContext, medium->id().latin1()); + if (halVolume) { + char* clearUdi = libhal_volume_crypto_get_clear_volume_udi(m_halContext, halVolume); + if (clearUdi != NULL) { + volume_udi = clearUdi; + libhal_free_string(clearUdi); + } else { + // if not decrypted yet then no mountoptions + return TQStringList(); + } + libhal_volume_free(halVolume); + } else { + // strange... + return TQStringList(); + } + } + + TDEConfig config("mediamanagerrc"); + + bool use_defaults = true; + if (config.hasGroup(name)) + { + config.setGroup(name); + use_defaults = config.readBoolEntry("use_defaults", false); + } + + if (use_defaults) + config.setGroup("DefaultOptions"); + + char ** array = libhal_device_get_property_strlist(m_halContext, volume_udi.latin1(), "volume.mount.valid_options", NULL); + TQMap<TQString,bool> valids; + + for (int index = 0; array && array[index]; ++index) { + TQString t = array[index]; + if (t.endsWith("=")) + t = t.left(t.length() - 1); + valids[t] = true; + kdDebug() << "valid " << t << endl; + } + libhal_free_string_array(array); + TQStringList result; + TQString tmp; + + result << TQString("use_defaults=%1").arg(use_defaults ? "true" : "false"); + + TQString fstype = libhal_device_get_property_QString(m_halContext, volume_udi.latin1(), "volume.fstype"); + if (fstype.isNull()) + fstype = libhal_device_get_property_QString(m_halContext, volume_udi.latin1(), "volume.policy.mount_filesystem"); + + TQString drive_udi = libhal_device_get_property_QString(m_halContext, volume_udi.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); + + bool value; + if (use_defaults) + { + value = config.readBoolEntry("automount", false); + } + else + { + QString current_group = config.group(); + config.setGroup(drive_udi); + value = config.readBoolEntry("automount", false); + config.setGroup(current_group); + } + + if (libhal_device_get_property_bool(m_halContext, volume_udi.latin1(), "volume.disc.is_blank", NULL) + || libhal_device_get_property_bool(m_halContext, volume_udi.latin1(), "volume.disc.is_vcd", NULL) + || libhal_device_get_property_bool(m_halContext, volume_udi.latin1(), "volume.disc.is_svcd", NULL) + || libhal_device_get_property_bool(m_halContext, volume_udi.latin1(), "volume.disc.is_videodvd", NULL) + || libhal_device_get_property_bool(m_halContext, volume_udi.latin1(), "volume.disc.has_audio", NULL)) + value = false; + + result << TQString("automount=%1").arg(value ? "true" : "false"); + + if (valids.contains("ro")) + { + value = config.readBoolEntry("ro", false); + tmp = TQString("ro=%1").arg(value ? "true" : "false"); + if (fstype != "iso9660") // makes no sense + result << tmp; + } + + if (valids.contains("quiet")) + { + value = config.readBoolEntry("quiet", false); + tmp = TQString("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 = TQString("flush=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("uid")) + { + value = config.readBoolEntry("uid", true); + tmp = TQString("uid=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("utf8")) + { + value = config.readBoolEntry("utf8", true); + tmp = TQString("utf8=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("shortname")) + { + TQString 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 << TQString("locale=%1").arg(cType); + } + } + + if (valids.contains("sync")) + { + value = config.readBoolEntry("sync", ( valids.contains("flush") && !fstype.endsWith("fat") ) && removable); + tmp = TQString("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 = TQString("atime=%1").arg(value ? "true" : "false"); + if (fstype != "iso9660") // makes no sense + result << tmp; + } + + TQString mount_point = libhal_device_get_property_QString(m_halContext, volume_udi.latin1(), "volume.mount_point"); + if (mount_point.isEmpty()) + mount_point = libhal_device_get_property_QString(m_halContext, volume_udi.latin1(), "volume.policy.desired_mount_point"); + + mount_point = config.readEntry("mountpoint", mount_point); + + if (!mount_point.startsWith("/")) + mount_point = "/media/" + mount_point; + + result << TQString("mountpoint=%1").arg(mount_point); + result << TQString("filesystem=%1").arg(fstype); + + if (valids.contains("data")) + { + TQString 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 TQString &name, const TQStringList &options ) +{ + kdDebug() << "setMountoptions " << name << " " << options << endl; + + TDEConfig config("mediamanagerrc"); + config.setGroup(name); + + TQMap<TQString,TQString> valids = MediaManagerUtils::splitOptions(options); + + const char *names[] = { "use_defaults", "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(TQString("mountpoint=%1").arg(valids["mountpoint"]))) + config.writeEntry("mountpoint", valids["mountpoint"]); + + if (valids.contains("automount")) { + TQString 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; +} + +TQString startKdeSudoProcess(const TQString& tdesudoPath, const TQString& command, + const TQString& dialogCaption, const TQString& dialogComment) +{ + TDEProcess tdesudoProcess; + + tdesudoProcess << tdesudoPath + << "-d" + << "--noignorebutton" + << "--caption" << dialogCaption + << "--comment" << dialogComment + << "-c" << command; + + // @todo handle tdesudo output + tdesudoProcess.start(TDEProcess::Block); + + return TQString(); +} + +TQString startKdeSuProcess(const TQString& tdesuPath, const TQString& command, + const TQString& dialogCaption) +{ + TDEProcess tdesuProcess; + + tdesuProcess << tdesuPath + << "-d" + << "--noignorebutton" + << "--caption" << dialogCaption + << "-c" << command; + + // @todo handle tdesu output + tdesuProcess.start(TDEProcess::Block); + + return TQString(); +} + +TQString startPrivilegedProcess(const TQString& command, const TQString& dialogCaption, const TQString& dialogComment) +{ + TQString error; + + TQString tdesudoPath = KStandardDirs::findExe("tdesudo"); + + if (!tdesudoPath.isEmpty()) + error = startKdeSudoProcess(tdesudoPath, command, dialogCaption, dialogComment); + else { + TQString tdesuPath = KStandardDirs::findExe("tdesu"); + + if (!tdesuPath.isEmpty()) + error = startKdeSuProcess(tdesuPath, command, dialogCaption); + } + + return error; +} + +TQString privilegedMount(const char* udi, const char* mountPoint, const char** options, int numberOfOptions) +{ + TQString error; + + kdDebug() << "run privileged mount for " << udi << endl; + + TQString dbusSendPath = KStandardDirs::findExe("dbus-send"); + + // @todo return error message + if (dbusSendPath.isEmpty()) + return TQString(); + + TQString mountOptions; + TQTextOStream optionsStream(&mountOptions); + for (int optionIndex = 0; optionIndex < numberOfOptions; optionIndex++) { + optionsStream << options[optionIndex]; + if (optionIndex < numberOfOptions - 1) + optionsStream << ","; + } + + TQString command; + TQTextOStream(&command) << dbusSendPath + << " --system --print-reply --dest=org.freedesktop.Hal " << udi + << " org.freedesktop.Hal.Device.Volume.Mount string:" << mountPoint + << " string: array:string:" << mountOptions; + + kdDebug() << "command: " << command << endl; + + error = startPrivilegedProcess(command, + i18n("Authenticate"), + i18n("<big><b>System policy prevents mounting internal media</b></big><br/>Authentication is required to perform this action. Please enter your password to verify.")); + + return error; +} + +TQString privilegedUnmount(const char* udi) +{ + TQString error; + + kdDebug() << "run privileged unmount for " << udi << endl; + + TQString dbusSendPath = KStandardDirs::findExe("dbus-send"); + + // @todo return error message + if (dbusSendPath.isEmpty()) + return TQString(); + + TQString command; + TQTextOStream(&command) << dbusSendPath + << " --system --print-reply --dest=org.freedesktop.Hal " << udi + << " org.freedesktop.Hal.Device.Volume.Unmount array:string:force"; + + kdDebug() << "command: " << command << endl; + + error = startPrivilegedProcess(command, + i18n("Authenticate"), + i18n("<big><b>System policy prevents unmounting media mounted by other users</b></big><br/>Authentication is required to perform this action. Please enter your password to verify.")); + + return error; +} + +static TQString 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"); + } + + TQString qerror; + + dbus_error_init (&error); + if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, dmesg, -1, &error))) + { + TQString 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("Permission denied<p>Please ensure that:<br>1. You have permission to access this device.<br>2. This device node is not listed in /etc/fstab.</p>"); + else if ( !strcmp(error.name, "org.freedesktop.Hal.Device.PermissionDeniedByPolicy")) + qerror = privilegedMount(udi, mount_point, poptions, noptions); + 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; + +} + +TQString HALBackend::listUsingProcesses(const Medium* medium) +{ + TQString proclist, fullmsg; + TQString cmdline = TQString("/usr/bin/env fuser -vm %1 2>&1").arg(TDEProcess::quote(medium->mountPoint())); + FILE *fuser = popen(cmdline.latin1(), "r"); + + uint counter = 0; + if (fuser) { + proclist += "<pre>"; + TQTextIStream is(fuser); + TQString tmp; + while (!is.atEnd()) { + tmp = is.readLine(); + tmp = TQStyleSheet::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 TQString::null; + } +} + +TQString HALBackend::killUsingProcesses(const Medium* medium) +{ + TQString proclist, fullmsg; + TQString cmdline = TQString("/usr/bin/env fuser -vmk %1 2>&1").arg(TDEProcess::quote(medium->mountPoint())); + FILE *fuser = popen(cmdline.latin1(), "r"); + + uint counter = 0; + if (fuser) { + proclist += "<pre>"; + TQTextIStream is(fuser); + TQString tmp; + while (!is.atEnd()) { + tmp = is.readLine(); + tmp = TQStyleSheet::escape(tmp) + "\n"; + + proclist += tmp; + if (counter++ > 10) + { + proclist += "..."; + break; + } + } + proclist += "</pre>"; + (void)pclose( fuser ); + } + if (counter) { + fullmsg = i18n("Programs that were still using the device " + "have been forcibly terminated. They are listed below."); + fullmsg += "<br>" + proclist; + return fullmsg; + } else { + return TQString::null; + } +} + +void HALBackend::slotResult(TDEIO::Job *job) +{ + kdDebug() << "slotResult " << mount_jobs[job] << endl; + + struct mount_job_data *data = mount_jobs[job]; + TQString& qerror = data->errorMessage; + const Medium* medium = data->medium; + + if (job->error() == TDEIO::ERR_COULD_NOT_UNMOUNT) { + TQString 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(); +} + +TQString 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) + { + TQString 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() ) ) + { + TQStringList opts = (*it)->mountOptions(); + if (opts.contains("user") || opts.contains("users")) + return (*it)->mountPoint(); + } + } + + return TQString::null; +} + +TQString HALBackend::mount(const Medium *medium) +{ + if (medium->isMounted()) + return TQString(); // that was easy + + TQString 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; + TDEIO::Job *job = TDEIO::mount( false, 0, medium->deviceNode(), mountPoint ); + connect(job, TQT_SIGNAL( result (TDEIO::Job *)), + TQT_SLOT( slotResult( TDEIO::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 : TQString::null; + + } else if (medium->id().startsWith("/org/kde/") ) + return i18n("Permission denied"); + + TQStringList soptions; + + kdDebug() << "mounting " << medium->id() << "..." << endl; + + TQMap<TQString,TQString> valids = MediaManagerUtils::splitOptions(mountoptions(medium->id())); + if (valids["flush"] == "true") + soptions << "flush"; + + if ((valids["uid"] == "true") && (medium->fsType() != "ntfs")) + { + soptions << TQString("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"; + + if (medium->fsType() == "ntfs") { + TQString fsLocale("locale="); + fsLocale += setlocale(LC_ALL, ""); + + soptions << fsLocale; + } + + TQString mount_point = valids["mountpoint"]; + if (mount_point.startsWith("/media/")) + mount_point = mount_point.mid(7); + + if (valids.contains("shortname")) + { + soptions << TQString("shortname=%1").arg(valids["shortname"]); + } + + if (valids.contains("locale")) + { + soptions << TQString("locale=%1").arg(valids["locale"]); + } + + if (valids.contains("journaling")) + { + TQString option = valids["journaling"]; + if (option == "data") + soptions << TQString("data=journal"); + else if (option == "writeback") + soptions << TQString("data=writeback"); + else + soptions << TQString("data=ordered"); + } + + TQStringList hal_mount_options = getHALmountoptions(medium->id()); + for (TQValueListIterator<TQString> it=hal_mount_options.begin();it!=hal_mount_options.end();it++) + { + soptions << *it; + kdDebug()<<"HALOption: "<<*it<<endl; + if ((*it).startsWith("iocharset=")) + { + soptions.remove("utf8"); + kdDebug()<<"\"iocharset=\" found. Removing \"utf8\" from options."<<endl; + } + } + + + const char **options = new const char*[soptions.size() + 1]; + uint noptions = 0; + for (TQStringList::ConstIterator it = soptions.begin(); it != soptions.end(); ++it, ++noptions) + { + options[noptions] = (*it).latin1(); + kdDebug()<<"Option: "<<*it<<endl; + } + options[noptions] = NULL; + + TQString qerror = i18n("Cannot mount encrypted drives!"); + + if (!medium->isEncrypted()) { + // normal volume + qerror = mount_priv(medium->id().latin1(), mount_point.utf8(), options, noptions, dbus_connection); + } else { + // see if we have a clear volume + LibHalVolume* halVolume = libhal_volume_from_udi(m_halContext, medium->id().latin1()); + if (halVolume) { + char* clearUdi = libhal_volume_crypto_get_clear_volume_udi(m_halContext, halVolume); + if (clearUdi != NULL) { + qerror = mount_priv(clearUdi, mount_point.utf8(), options, noptions, dbus_connection); + libhal_free_string(clearUdi); + } + libhal_volume_free(halVolume); + } + } + + if (!qerror.isEmpty()) { + kdError() << "mounting " << medium->id() << " returned " << qerror << endl; + return qerror; + } + + medium->setHalMounted(true); + ResetProperties(medium->id().latin1()); + + return TQString(); +} + +TQString HALBackend::mount(const TQString &_udi) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + + return mount(medium); +} + +TQString HALBackend::unmount(const TQString &_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 TQString(); // that was easy + + TQString 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; + TDEIO::Job *job = TDEIO::unmount( medium->mountPoint(), false ); + connect(job, TQT_SIGNAL( result (TDEIO::Job *)), + TQT_SLOT( slotResult( TDEIO::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 : TQString::null; + } + + DBusMessage *dmesg, *reply; + DBusError error; + const char *options[2]; + TQString udi = TQString::null; + + if (!medium->isEncrypted()) { + // normal volume + udi = medium->id(); + } else { + // see if we have a clear volume + LibHalVolume* halVolume = libhal_volume_from_udi(m_halContext, medium->id().latin1()); + if (halVolume) { + char *clearUdi = libhal_volume_crypto_get_clear_volume_udi(m_halContext, halVolume); + udi = clearUdi; + libhal_free_string(clearUdi); + libhal_volume_free(halVolume); + } + } + if (udi.isNull()) { + kdDebug() << "unmount failed: no udi" << endl; + return i18n("Internal Error"); + } + + 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.latin1(), + "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"); + } + + char thisunmounthasfailed = 0; + dbus_error_init (&error); + if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, dmesg, -1, &error))) + { + thisunmounthasfailed = 1; + TQString qerror, reason, origqerror; + + if (!strcmp(error.name, "org.freedesktop.Hal.Device.PermissionDeniedByPolicy")) { + qerror = privilegedUnmount(udi.latin1()); + + if (qerror.isEmpty()) { + dbus_message_unref(dmesg); + dbus_error_free(&error); + return TQString(); + } + + // @todo handle unmount error message + } + + 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:"); + thisunmounthasfailed = 2; + } 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("Permission denied<p>Please ensure that:<br>1. You have permission to access this device.<br>2. This device was originally mounted using TDE.</p>"); + } else { + reason = error.message; + } + qerror += "<p><b>" + reason + "</b></p>"; + origqerror = qerror; + + // Include list of processes (if any) using the device in the error message + reason = listUsingProcesses(medium); + if (!reason.isEmpty()) { + qerror += reason; + if (thisunmounthasfailed == 2) { // Failed as BUSY + if (KMessageBox::warningYesNo(0, i18n("%1<p><b>Would you like to forcibly terminate these processes?</b><br><i>All unsaved data would be lost</i>").arg(qerror)) == KMessageBox::Yes) { + qerror = origqerror; + reason = killUsingProcesses(medium); + qerror = HALBackend::unmount(udi); + if (qerror.isNull()) { + thisunmounthasfailed = 0; + } + } + } + } + + if (thisunmounthasfailed != 0) { + 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(medium->id().latin1()); + + while (dbus_connection_dispatch(dbus_connection) == DBUS_DISPATCH_DATA_REMAINS) ; + + return TQString(); +} + +TQString HALBackend::decrypt(const TQString &_udi, const TQString &password) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + + if (!medium->isEncrypted() || !medium->clearDeviceUdi().isNull()) + return TQString(); + + const char *udi = medium->id().latin1(); + DBusMessage *msg = NULL; + DBusMessage *reply = NULL; + DBusError error; + + kdDebug() << "Setting up " << udi << " for crypto\n" <<endl; + + msg = dbus_message_new_method_call ("org.freedesktop.Hal", udi, + "org.freedesktop.Hal.Device.Volume.Crypto", + "Setup"); + if (msg == NULL) { + kdDebug() << "decrypt failed for " << udi << ": could not create dbus message\n"; + return i18n("Internal Error"); + } + + TQCString pwdUtf8 = password.utf8(); + const char *pwd_utf8 = pwdUtf8; + if (!dbus_message_append_args (msg, DBUS_TYPE_STRING, &pwd_utf8, DBUS_TYPE_INVALID)) { + kdDebug() << "decrypt failed for " << udi << ": could not append args to dbus message\n"; + dbus_message_unref (msg); + return i18n("Internal Error"); + } + + dbus_error_init (&error); + if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, msg, -1, &error)) || + dbus_error_is_set (&error)) + { + TQString qerror = i18n("Internal Error"); + kdDebug() << "decrypt failed for " << udi << ": " << error.name << " " << error.message << endl; + if (strcmp (error.name, "org.freedesktop.Hal.Device.Volume.Crypto.SetupPasswordError") == 0) { + qerror = i18n("Wrong password"); + } + dbus_error_free (&error); + dbus_message_unref (msg); + while (dbus_connection_dispatch(dbus_connection) == DBUS_DISPATCH_DATA_REMAINS) ; + return qerror; + } + + dbus_message_unref (msg); + dbus_message_unref (reply); + + while (dbus_connection_dispatch(dbus_connection) == DBUS_DISPATCH_DATA_REMAINS) ; + + return TQString(); +} + +TQString HALBackend::undecrypt(const TQString &_udi) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + + if (!medium->isEncrypted() || medium->clearDeviceUdi().isNull()) + return TQString(); + + const char *udi = medium->id().latin1(); + DBusMessage *msg = NULL; + DBusMessage *reply = NULL; + DBusError error; + + kdDebug() << "Tear down " << udi << "\n" <<endl; + + msg = dbus_message_new_method_call ("org.freedesktop.Hal", udi, + "org.freedesktop.Hal.Device.Volume.Crypto", + "Teardown"); + if (msg == NULL) { + kdDebug() << "teardown failed for " << udi << ": could not create dbus message\n"; + return i18n("Internal Error"); + } + + if (!dbus_message_append_args (msg, DBUS_TYPE_INVALID)) { + kdDebug() << "teardown failed for " << udi << ": could not append args to dbus message\n"; + dbus_message_unref (msg); + return i18n("Internal Error"); + } + + dbus_error_init (&error); + if (!(reply = dbus_connection_send_with_reply_and_block (dbus_connection, msg, -1, &error)) || + dbus_error_is_set (&error)) + { + TQString qerror = i18n("Internal Error"); + kdDebug() << "teardown failed for " << udi << ": " << error.name << " " << error.message << endl; + dbus_error_free (&error); + dbus_message_unref (msg); + while (dbus_connection_dispatch(dbus_connection) == DBUS_DISPATCH_DATA_REMAINS) ; + return qerror; + } + + dbus_message_unref (msg); + dbus_message_unref (reply); + + ResetProperties(udi); + + while (dbus_connection_dispatch(dbus_connection) == DBUS_DISPATCH_DATA_REMAINS) ; + + return TQString(); +} + +#include "halbackend.moc" diff --git a/tdeioslave/media/mediamanager/halbackend.h b/tdeioslave/media/mediamanager/halbackend.h new file mode 100644 index 000000000..8c8bbfce6 --- /dev/null +++ b/tdeioslave/media/mediamanager/halbackend.h @@ -0,0 +1,232 @@ +/* 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 <tqobject.h> +#include <tqstringlist.h> +#include <tqstring.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 TDEIO { + class Job; +} + +class HALBackend : public TQObject, public BackendBase +{ +Q_OBJECT + +public: + /** + * Constructor + */ + HALBackend(MediaList &list, TQObject* 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(); + + TQStringList mountoptions(const TQString &id); + + bool setMountoptions(const TQString &id, const TQStringList &options); + + TQString mount(const TQString &id); + TQString mount(const Medium *medium); + TQString unmount(const TQString &id); + TQString decrypt(const TQString &id, const TQString &password); + TQString undecrypt(const TQString &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); + TQString generateName(const TQString &devNode); + static TQString isInFstab(const Medium *medium); + static TQString listUsingProcesses(const Medium *medium); + static TQString killUsingProcesses(const Medium *medium); + +private slots: + void slotResult(TDEIO::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 + ); + + TQStringList getHALmountoptions(TQString udi); +/* 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 + */ + TQObject* 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] TDEIO::Error if an error occured during operation. Otherwise, 0 + int error; + // [out] Error message to be displayed to the user + TQString errorMessage; + }; + + TQMap<TDEIO::Job *, struct mount_job_data*> mount_jobs; +}; + +#endif /* _HALBACKEND_H_ */ diff --git a/tdeioslave/media/mediamanager/linuxcdpolling.cpp b/tdeioslave/media/mediamanager/linuxcdpolling.cpp new file mode 100644 index 000000000..0fcda8d0f --- /dev/null +++ b/tdeioslave/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 <tqthread.h> +#include <tqmutex.h> +#include <tqfile.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 TQThread +{ +public: + PollingThread(const TQCString &devNode) : m_dev(devNode) + { + kdDebug(1219) << "PollingThread::PollingThread(" + << devNode << ")" << endl; + m_stop = false; + m_currentType = DiscType::None; + m_lastPollType = DiscType::None; + } + + + void stop() + { + TQMutexLocker locker(&m_mutex); + m_stop = true; + } + + bool hasChanged() + { + TQMutexLocker locker(&m_mutex); + + return m_currentType!=m_lastPollType; + } + + DiscType type() + { + TQMutexLocker 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: + TQMutex m_mutex; + bool m_stop; + const TQCString m_dev; + DiscType m_currentType; + DiscType m_lastPollType; +}; + + +LinuxCDPolling::LinuxCDPolling(MediaList &list) + : TQObject(), BackendBase(list) +{ + connect(&m_mediaList, TQT_SIGNAL(mediumAdded(const TQString &, + const TQString &, bool)), + this, TQT_SLOT(slotMediumAdded(const TQString &)) ); + + connect(&m_mediaList, TQT_SIGNAL(mediumRemoved(const TQString &, + const TQString &, bool)), + this, TQT_SLOT(slotMediumRemoved(const TQString &)) ); + + connect(&m_mediaList, TQT_SIGNAL(mediumStateChanged(const TQString &, + const TQString &, bool, bool)), + this, TQT_SLOT(slotMediumStateChanged(const TQString &)) ); + + connect(&m_timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotTimeout())); +} + +LinuxCDPolling::~LinuxCDPolling() +{ + TQMap<TQString, PollingThread*>::iterator it = m_threads.begin(); + TQMap<TQString, PollingThread*>::iterator end = m_threads.end(); + + for(; it!=end; ++it) + { + PollingThread *thread = it.data(); + thread->stop(); + thread->wait(); + delete thread; + } +} + +void LinuxCDPolling::slotMediumAdded(const TQString &id) +{ + kdDebug(1219) << "LinuxCDPolling::slotMediumAdded(" << id << ")" << endl; + + if (m_threads.contains(id)) return; + + const Medium *medium = m_mediaList.findById(id); + + TQString 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 ); + + TQCString dev = TQFile::encodeName( medium->deviceNode() ).data(); + PollingThread *thread = new PollingThread(dev); + m_threads[id] = thread; + thread->start(); + m_timer.start(500); + } +} + +void LinuxCDPolling::slotMediumRemoved(const TQString &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 TQString &id) +{ + kdDebug(1219) << "LinuxCDPolling::slotMediumStateChanged(" + << id << ")" << endl; + + const Medium *medium = m_mediaList.findById(id); + + TQString 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 ); + + TQCString dev = TQFile::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; + } + + TQMap<TQString, PollingThread*>::iterator it = m_threads.begin(); + TQMap<TQString, PollingThread*>::iterator end = m_threads.end(); + + for(; it!=end; ++it) + { + TQString 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 TQString baseType(const Medium *medium) +{ + kdDebug(1219) << "baseType(" << medium->id() << ")" << endl; + + TQString devNode = medium->deviceNode(); + TQString mountPoint = medium->mountPoint(); + TQString fsType = medium->fsType(); + bool mounted = medium->isMounted(); + + TQString 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; + + TQString id = medium->id(); + TQString devNode = medium->deviceNode(); + TQString mountPoint = medium->mountPoint(); + TQString fsType = medium->fsType(); + bool mounted = medium->isMounted(); + + TQString 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; + + TQString id = medium->id(); + TQString 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 TQCString &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 TQCString &devNode, const TQCString &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 + TQCString 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, TQCString(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/tdeioslave/media/mediamanager/linuxcdpolling.h b/tdeioslave/media/mediamanager/linuxcdpolling.h new file mode 100644 index 000000000..3dec7e3ee --- /dev/null +++ b/tdeioslave/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 <tqobject.h> +#include <tqcstring.h> +#include <tqmap.h> +#include <tqtimer.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 TQObject, 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 TQCString &devNode, + const DiscType ¤t = DiscType::Unknown); + +private slots: + void slotMediumAdded(const TQString &id); + void slotMediumRemoved(const TQString &id); + void slotMediumStateChanged(const TQString &id); + void slotTimeout(); + +private: + void applyType(DiscType type, const Medium *medium); + + static bool hasDirectory(const TQCString &devNode, const TQCString &dir); + + TQMap<TQString, PollingThread*> m_threads; + TQStringList m_excludeNotification; + TQTimer m_timer; +}; + +#endif diff --git a/tdeioslave/media/mediamanager/mediadirnotify.cpp b/tdeioslave/media/mediamanager/mediadirnotify.cpp new file mode 100644 index 000000000..9e6658153 --- /dev/null +++ b/tdeioslave/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 TQPtrList<Medium> list = m_mediaList.list(); + + TQPtrList<Medium>::const_iterator it = list.begin(); + TQPtrList<Medium>::const_iterator end = list.end(); + + for (; it!=end; ++it) + { + const Medium *m = *it; + KURL base = m->prettyBaseURL(); + + if ( base.isParentOf(url) ) + { + TQString 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/tdeioslave/media/mediamanager/mediadirnotify.h b/tdeioslave/media/mediamanager/mediadirnotify.h new file mode 100644 index 000000000..beb1b8849 --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mediamanager/medialist.cpp b/tdeioslave/media/mediamanager/medialist.cpp new file mode 100644 index 000000000..6ac7dc341 --- /dev/null +++ b/tdeioslave/media/mediamanager/medialist.cpp @@ -0,0 +1,303 @@ +/* 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 TQPtrList<Medium> MediaList::list() const +{ + kdDebug(1219) << "MediaList::list()" << endl; + + return m_media; +} + +const Medium *MediaList::findById(const TQString &id) const +{ + kdDebug(1219) << "MediaList::findById(" << id << ")" << endl; + + if ( !m_idMap.contains(id) ) return 0L; + + return m_idMap[id]; +} + +const Medium *MediaList::findByName(const TQString &name) const +{ + kdDebug(1219) << "MediaList::findByName(" << name << ")" << endl; + + if ( !m_nameMap.contains(name) ) return 0L; + + return m_nameMap[name]; +} + +const Medium *MediaList::findByClearUdi(const TQString &name) +{ + kdDebug(1219) << "MediaList::findByClearUdi(" << name << ")" << endl; + + Medium *medium; + for (medium = m_media.first(); medium; medium = m_media.next()) { + if (medium->clearDeviceUdi() == name) return medium; + } + + return 0L; +} + + +TQString MediaList::addMedium(Medium *medium, bool allowNotification) +{ + kdDebug(1219) << "MediaList::addMedium(@" << medium->id() << ")" << endl; + + TQString id = medium->id(); + if ( m_idMap.contains(id) ) return TQString::null; + + m_media.append( medium ); + m_idMap[id] = medium; + + TQString 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; + } + + TQString base_name = name+"_"; + int i = 1; + + while ( m_nameMap.contains(base_name+TQString::number(i)) ) + { + i++; + } + + name = base_name+TQString::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 TQString &id, bool allowNotification) +{ + kdDebug(1219) << "MediaList::removeMedium(" << id << ")" << endl; + + if ( !m_idMap.contains(id) ) return false; + + Medium *medium = m_idMap[id]; + TQString 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() ) + { + TQString device_node = medium.deviceNode(); + TQString clear_device_udi = medium.clearDeviceUdi(); + TQString mount_point = medium.mountPoint(); + TQString fs_type = medium.fsType(); + bool mounted = medium.isMounted(); + + m->mountableState( device_node, clear_device_udi, 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() ); + } + + m->setHidden(medium.hidden()); + + emit mediumStateChanged(m->id(), m->name(), !m->needMounting(), allowNotification); + return true; +} + +bool MediaList::changeMediumState(const TQString &id, + const TQString &baseURL, + bool allowNotification, + const TQString &mimeType, + const TQString &iconName, + const TQString &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 TQString &id, + const TQString &deviceNode, + const TQString &mountPoint, + const TQString &fsType, bool mounted, + bool allowNotification, + const TQString &mimeType, + const TQString &iconName, + const TQString &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 TQString &id, bool mounted, + bool allowNotification, + const TQString &mimeType, + const TQString &iconName, + const TQString &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 TQString &name, const TQString &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/tdeioslave/media/mediamanager/medialist.h b/tdeioslave/media/mediamanager/medialist.h new file mode 100644 index 000000000..59ddb6bd2 --- /dev/null +++ b/tdeioslave/media/mediamanager/medialist.h @@ -0,0 +1,80 @@ +/* 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 <tqobject.h> + +#include "medium.h" + +class MediaList : public QObject +{ +Q_OBJECT + +public: + MediaList(); + + // FIXME: should be <const Medium> or something similar... + const TQPtrList<Medium> list() const; + const Medium *findById(const TQString &id) const; + const Medium *findByName(const TQString &name) const; + const Medium *findByClearUdi(const TQString &name); + +public: + TQString addMedium(Medium *medium, bool allowNotification = true); + bool removeMedium(const TQString &id, bool allowNotification = true); + + bool changeMediumState(const Medium &medium, bool allowNotification); + bool changeMediumState(const TQString &id, + const TQString &baseURL, + bool allowNotification = true, + const TQString &mimeType = TQString::null, + const TQString &iconName = TQString::null, + const TQString &label = TQString::null); + bool changeMediumState(const TQString &id, + const TQString &deviceNode, + const TQString &mountPoint, + const TQString &fsType, bool mounted, + bool allowNotification = true, + const TQString &mimeType = TQString::null, + const TQString &iconName = TQString::null, + const TQString &label = TQString::null); + bool changeMediumState(const TQString &id, bool mounted, + bool allowNotification = true, + const TQString &mimeType = TQString::null, + const TQString &iconName = TQString::null, + const TQString &label = TQString::null); + + bool setUserLabel(const TQString &name, const TQString &label); + +signals: + void mediumAdded(const TQString &id, const TQString &name, + bool allowNotification); + void mediumRemoved(const TQString &id, const TQString &name, + bool allowNotification); + void mediumStateChanged(const TQString &id, const TQString &name, + bool mounted, bool allowNotification); + +private: + TQPtrList<Medium> m_media; + TQMap<TQString,Medium*> m_nameMap; + TQMap<TQString,Medium*> m_idMap; +}; + +#endif diff --git a/tdeioslave/media/mediamanager/mediamanager.cpp b/tdeioslave/media/mediamanager/mediamanager.cpp new file mode 100644 index 000000000..db2c12a71 --- /dev/null +++ b/tdeioslave/media/mediamanager/mediamanager.cpp @@ -0,0 +1,423 @@ +/* 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 <tqtimer.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_TDEHARDWAREBACKEND +#include "tdehardwarebackend.h" +#endif // COMPILE_TDEHARDWAREBACKEND + +#ifdef COMPILE_HALBACKEND +#include "halbackend.h" +#endif //COMPILE_HALBACKEND + +#ifdef COMPILE_LINUXCDPOLLING +#include "linuxcdpolling.h" +#endif //COMPILE_LINUXCDPOLLING + + +MediaManager::MediaManager(const TQCString &obj) + : KDEDModule(obj), m_dirNotify(m_mediaList) +{ + connect( &m_mediaList, TQT_SIGNAL(mediumAdded(const TQString&, const TQString&, bool)), + TQT_SLOT(slotMediumAdded(const TQString&, const TQString&, bool)) ); + connect( &m_mediaList, TQT_SIGNAL(mediumRemoved(const TQString&, const TQString&, bool)), + TQT_SLOT(slotMediumRemoved(const TQString&, const TQString&, bool)) ); + connect( &m_mediaList, + TQT_SIGNAL(mediumStateChanged(const TQString&, const TQString&, bool, bool)), + TQT_SLOT(slotMediumChanged(const TQString&, const TQString&, bool, bool)) ); + + TQTimer::singleShot( 10, this, TQT_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_tdebackend = 0L; + m_fstabbackend = 0L; + +#ifdef COMPILE_HALBACKEND + if ( MediaManagerSettings::self()->halBackendEnabled() ) + { + m_mediaList.blockSignals(false); + 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; + m_mediaList.blockSignals(true); + } + } +#endif // COMPILE_HALBACKEND + +#ifdef COMPILE_TDEHARDWAREBACKEND + if ( MediaManagerSettings::self()->tdeHardwareBackendEnabled() ) + { + m_mediaList.blockSignals(false); + m_tdebackend = new TDEBackend(m_mediaList, this); + m_backends.append( m_tdebackend ); + m_fstabbackend = new FstabBackend(m_mediaList, true); + m_backends.append( m_fstabbackend ); + // No need to load something else... + m_mediaList.blockSignals(false); + return; + } +#endif // COMPILE_TDEHARDWAREBACKEND + + 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); +} + + +TQStringList MediaManager::fullList() +{ + TQPtrList<Medium> list = m_mediaList.list(); + + TQStringList result; + + TQPtrList<Medium>::const_iterator it = list.begin(); + TQPtrList<Medium>::const_iterator end = list.end(); + for (; it!=end; ++it) + { + result+= (*it)->properties(); + result+= Medium::SEPARATOR; + } + + return result; +} + +TQStringList MediaManager::properties(const TQString &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") + { + TQString 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 + TQPtrList<Medium> list = m_mediaList.list(); + TQPtrList<Medium>::const_iterator it = list.begin(); + TQPtrList<Medium>::const_iterator end = list.end(); + TQString 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 TQStringList(); + } +} + +TQStringList MediaManager::mountoptions(const TQString &name) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return TQStringList(); + return m_halbackend->mountoptions(name); +#else // COMPILE_HALBACKEND + #ifdef COMPILE_TDEHARDWAREBACKEND + if (!m_tdebackend) + return TQStringList(); + return m_tdebackend->mountoptions(name); + #else // COMPILE_TDEHARDWAREBACKEND + return TQStringList(); + #endif // COMPILE_TDEHARDWAREBACKEND +#endif // COMPILE_HALBACKEND +} + +bool MediaManager::setMountoptions(const TQString &name, const TQStringList &options) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return false; + return m_halbackend->setMountoptions(name, options); +#else // COMPILE_HALBACKEND + #ifdef COMPILE_TDEHARDWAREBACKEND + if (!m_tdebackend) + return false; + return m_tdebackend->setMountoptions(name, options); + #else // COMPILE_TDEHARDWAREBACKEND + return false; + #endif // COMPILE_TDEHARDWAREBACKEND +#endif // COMPILE_HALBACKEND +} + +TQString MediaManager::mount(const TQString &name) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return i18n("Feature only available with HAL"); + return m_halbackend->mount(name); +#else // COMPILE_HALBACKEND + #ifdef COMPILE_TDEHARDWAREBACKEND + if (!m_tdebackend) + return i18n("Feature only available with the TDE hardware backend"); + return m_tdebackend->mount(name); + #else // COMPILE_TDEHARDWAREBACKEND + if ( !m_fstabbackend ) // lying :) + return i18n("Feature only available with HAL or TDE hardware backend"); + return m_fstabbackend->mount( name ); + #endif // COMPILE_TDEHARDWAREBACKEND +#endif // COMPILE_HALBACKEND +} + +TQString MediaManager::unmount(const TQString &name) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return i18n("Feature only available with HAL"); + return m_halbackend->unmount(name); +#else // COMPILE_HALBACKEND + #ifdef COMPILE_TDEHARDWAREBACKEND + if (!m_tdebackend) + return i18n("Feature only available with HAL or TDE hardware backend"); + return m_tdebackend->unmount(name); + #else // COMPILE_TDEHARDWAREBACKEND + if ( !m_fstabbackend ) // lying :) + return i18n("Feature only available with HAL or TDE hardware backend"); + return m_fstabbackend->unmount( name ); + #endif // COMPILE_TDEHARDWAREBACKEND +#endif // COMPILE_HALBACKEND +} + +TQString MediaManager::decrypt(const TQString &name, const TQString &password) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return i18n("Feature only available with HAL"); + return m_halbackend->decrypt(name, password); +#else // COMPILE_HALBACKEND +// #ifdef COMPILE_TDEHARDWAREBACKEND +// if (!m_tdebackend) +// return i18n("Feature only available with HAL or TDE hardware backend"); +// return m_tdebackend->decrypt(name, password); +// #else // COMPILE_TDEHARDWAREBACKEND + return i18n("Feature only available with HAL"); +// #endif // COMPILE_TDEHARDWAREBACKEND +#endif // COMPILE_HALBACKEND +} + +TQString MediaManager::undecrypt(const TQString &name) +{ +#ifdef COMPILE_HALBACKEND + if (!m_halbackend) + return i18n("Feature only available with HAL"); + return m_halbackend->undecrypt(name); +#else // COMPILE_HALBACKEND +// #ifdef COMPILE_TDEHARDWAREBACKEND +// if (!m_tdebackend) +// return i18n("Feature only available with HAL or TDE hardware backend"); +// return m_tdebackend->undecrypt(name); +// #else // COMPILE_TDEHARDWAREBACKEND + return i18n("Feature only available with HAL"); +// #endif // COMPILE_TDEHARDWAREBACKEND +#endif // COMPILE_HALBACKEND +} + +TQString MediaManager::nameForLabel(const TQString &label) +{ + const TQPtrList<Medium> media = m_mediaList.list(); + + TQPtrList<Medium>::const_iterator it = media.begin(); + TQPtrList<Medium>::const_iterator end = media.end(); + for (; it!=end; ++it) + { + const Medium *m = *it; + + if (m->prettyLabel()==label) + { + return m->name(); + } + } + + return TQString::null; +} + +ASYNC MediaManager::setUserLabel(const TQString &name, const TQString &label) +{ + m_mediaList.setUserLabel(name, label); +} + +ASYNC MediaManager::reloadBackends() +{ + MediaManagerSettings::self()->readConfig(); + loadBackends(); +} + +bool MediaManager::removablePlug(const TQString &devNode, const TQString &label) +{ + if (mp_removableBackend) + { + return mp_removableBackend->plug(devNode, label); + } + return false; +} + +bool MediaManager::removableUnplug(const TQString &devNode) +{ + if (mp_removableBackend) + { + return mp_removableBackend->unplug(devNode); + } + return false; +} + +bool MediaManager::removableCamera(const TQString &devNode) +{ + if (mp_removableBackend) + { + return mp_removableBackend->camera(devNode); + } + return false; +} + + +void MediaManager::slotMediumAdded(const TQString &/*id*/, const TQString &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 TQString &/*id*/, const TQString &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 TQString &/*id*/, const TQString &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 TQCString &obj) + { + TDEGlobal::locale()->insertCatalogue("kio_media"); + return new MediaManager(obj); + } +} + +#include "mediamanager.moc" diff --git a/tdeioslave/media/mediamanager/mediamanager.desktop b/tdeioslave/media/mediamanager/mediamanager.desktop new file mode 100644 index 000000000..48261e428 --- /dev/null +++ b/tdeioslave/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-TDE-ModuleType=Library +X-TDE-Library=mediamanager +X-TDE-FactoryName=mediamanager +X-TDE-Kded-autoload=true +X-TDE-Kded-load-on-demand=true +X-TDE-Kded-phase=1 diff --git a/tdeioslave/media/mediamanager/mediamanager.h b/tdeioslave/media/mediamanager/mediamanager.h new file mode 100644 index 000000000..256d11bf8 --- /dev/null +++ b/tdeioslave/media/mediamanager/mediamanager.h @@ -0,0 +1,94 @@ +/* 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 <tqstring.h> +#include <tqstringlist.h> + +#include "medialist.h" +#include "backendbase.h" +#include "removablebackend.h" +#include "mediadirnotify.h" + +class HALBackend; +class TDEBackend; +class FstabBackend; + +class MediaManager : public KDEDModule +{ +Q_OBJECT +K_DCOP +public: + MediaManager(const TQCString &obj); + ~MediaManager(); + +k_dcop: + TQStringList fullList(); + TQStringList properties(const TQString &name); + TQStringList mountoptions(const TQString &name); + bool setMountoptions(const TQString &name, const TQStringList &options); + + TQString mount(const TQString &uid); + TQString unmount(const TQString &uid); + TQString decrypt(const TQString &uid, const TQString &password); + TQString undecrypt(const TQString &uid); + + TQString nameForLabel(const TQString &label); + ASYNC setUserLabel(const TQString &name, const TQString &label); + + ASYNC reloadBackends(); + + // Removable media handling (for people not having HAL) + bool removablePlug(const TQString &devNode, const TQString &label); + bool removableUnplug(const TQString &devNode); + bool removableCamera(const TQString &devNode); + +k_dcop_signals: + void mediumAdded(const TQString &name, bool allowNotification); + void mediumRemoved(const TQString &name, bool allowNotification); + void mediumChanged(const TQString &name, bool allowNotification); + + // For compatibility purpose, not needed for KDE4 + void mediumAdded(const TQString &name); + void mediumRemoved(const TQString &name); + void mediumChanged(const TQString &name); + +private slots: + void loadBackends(); + + void slotMediumAdded(const TQString &id, const TQString &name, + bool allowNotification); + void slotMediumRemoved(const TQString &id, const TQString &name, + bool allowNotification); + void slotMediumChanged(const TQString &id, const TQString &name, + bool mounted, bool allowNotification); + +private: + MediaList m_mediaList; + TQValueList<BackendBase*> m_backends; + RemovableBackend *mp_removableBackend; + HALBackend *m_halbackend; + TDEBackend *m_tdebackend; + MediaDirNotify m_dirNotify; + FstabBackend *m_fstabbackend; +}; + +#endif diff --git a/tdeioslave/media/mediamanager/removablebackend.cpp b/tdeioslave/media/mediamanager/removablebackend.cpp new file mode 100644 index 000000000..6b83ae1ea --- /dev/null +++ b/tdeioslave/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) + : TQObject(), BackendBase(list) +{ + KDirWatch::self()->addFile(MTAB); + + connect( KDirWatch::self(), TQT_SIGNAL( dirty(const TQString&) ), + this, TQT_SLOT( slotDirty(const TQString&) ) ); + KDirWatch::self()->startScan(); +} + +RemovableBackend::~RemovableBackend() +{ + TQStringList::iterator it = m_removableIds.begin(); + TQStringList::iterator end = m_removableIds.end(); + + for (; it!=end; ++it) + { + m_mediaList.removeMedium(*it, false); + } + + KDirWatch::self()->removeFile(MTAB); +} + +bool RemovableBackend::plug(const TQString &devNode, const TQString &label) +{ + TQString name = generateName(devNode); + TQString id = generateId(devNode); + + if (!m_removableIds.contains(id)) + { + Medium *medium = new Medium(id, id, name); + medium->mountableState(devNode, TQString::null, + TQString::null, false); + + TQStringList words = TQStringList::split(" ", label); + + TQStringList::iterator it = words.begin(); + TQStringList::iterator end = words.end(); + + TQString tmp = (*it).lower(); + tmp[0] = tmp[0].upper(); + TQString 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 TQString &devNode) +{ + TQString id = generateId(devNode); + if (m_removableIds.contains(id)) + { + m_removableIds.remove(id); + return m_mediaList.removeMedium(id); + } + return false; +} + +bool RemovableBackend::camera(const TQString &devNode) +{ + TQString id = generateId(devNode); + if (m_removableIds.contains(id)) + { + return m_mediaList.changeMediumState(id, + TQString("camera:/"), false, "media/gphoto2camera"); + } + return false; +} + +void RemovableBackend::slotDirty(const TQString &path) +{ + if (path==MTAB) + { + handleMtabChange(); + } +} + + +void RemovableBackend::handleMtabChange() +{ + TQStringList new_mtabIds; + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + + KMountPoint::List::iterator it = mtab.begin(); + KMountPoint::List::iterator end = mtab.end(); + + for (; it!=end; ++it) + { + TQString dev = (*it)->mountedFrom(); + TQString mp = (*it)->mountPoint(); + TQString fs = (*it)->mountType(); + + TQString 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"); + } + } + + TQStringList::iterator it2 = m_mtabIds.begin(); + TQStringList::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; +} + +TQString RemovableBackend::generateId(const TQString &devNode) +{ + TQString dev = KStandardDirs::realFilePath(devNode); + + return "/org/kde/mediamanager/removable/" + +dev.replace("/", ""); +} + +TQString RemovableBackend::generateName(const TQString &devNode) +{ + return KURL(devNode).fileName(); +} + +#include "removablebackend.moc" diff --git a/tdeioslave/media/mediamanager/removablebackend.h b/tdeioslave/media/mediamanager/removablebackend.h new file mode 100644 index 000000000..fa16a391b --- /dev/null +++ b/tdeioslave/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 <tqobject.h> +#include <tqstringlist.h> + +class RemovableBackend : public TQObject, public BackendBase +{ +Q_OBJECT + +public: + RemovableBackend(MediaList &list); + virtual ~RemovableBackend(); + + bool plug(const TQString &devNode, const TQString &label); + bool unplug(const TQString &devNode); + bool camera(const TQString &devNode); + +private slots: + void slotDirty(const TQString &path); + +private: + void handleMtabChange(); + + static TQString generateId(const TQString &devNode); + static TQString generateName(const TQString &devNode); + + TQStringList m_removableIds; + TQStringList m_mtabIds; +}; + +#endif diff --git a/tdeioslave/media/mediamanager/tdehardwarebackend.cpp b/tdeioslave/media/mediamanager/tdehardwarebackend.cpp new file mode 100644 index 000000000..32ed746cf --- /dev/null +++ b/tdeioslave/media/mediamanager/tdehardwarebackend.cpp @@ -0,0 +1,1376 @@ +/* This file is part of the TDE Project + Copyright (c) 2012 Timothy Pearson <[email protected]> + + 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 "tdehardwarebackend.h" + +#include <tqfile.h> +#include <tqfileinfo.h> +#include <tqeventloop.h> +#include <tqstylesheet.h> + +#include <kglobal.h> +#include <klocale.h> +#include <tdeconfig.h> +#include <tdeio/job.h> +#include <kprocess.h> +#include <kmimetype.h> +#include <kmountpoint.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <kprotocolinfo.h> + +#include "dialog.h" + +#define MOUNT_SUFFIX ( \ + (medium->isMounted() ? TQString("_mounted") : TQString("_unmounted")) + \ + (medium->isEncrypted() ? (sdevice->isDiskOfType(TDEDiskDeviceType::UnlockedCrypt) ? "_decrypted" : "_encrypted") : "" ) \ + ) +#define MOUNT_ICON_SUFFIX ( \ + (medium->isMounted() ? TQString("_mount") : TQString("_unmount")) + \ + (medium->isEncrypted() ? (sdevice->isDiskOfType(TDEDiskDeviceType::UnlockedCrypt) ? "_decrypt" : "_encrypt") : "" ) \ + ) + +#define CHECK_FOR_AND_EXECUTE_AUTOMOUNT(udi, medium, allowNotification) { \ + TQMap<TQString,TQString> options = MediaManagerUtils::splitOptions(mountoptions(udi)); \ + kdDebug(1219) << "automount " << options["automount"] << endl; \ + if (options["automount"] == "true" && allowNotification ) { \ + TQString error = mount(medium); \ + if (!error.isEmpty()) \ + kdDebug(1219) << "error " << error << endl; \ + } \ + } + +/* Constructor */ +TDEBackend::TDEBackend(MediaList &list, TQObject* parent) + : TQObject() + , BackendBase(list) + , m_decryptDialog(0) + , m_parent(parent) +{ + // Initialize the TDE device manager + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + // Connect device monitoring signals/slots + connect(hwdevices, TQT_SIGNAL(hardwareAdded(TDEGenericDevice*)), this, TQT_SLOT(AddDeviceHandler(TDEGenericDevice*))); + connect(hwdevices, TQT_SIGNAL(hardwareRemoved(TDEGenericDevice*)), this, TQT_SLOT(RemoveDeviceHandler(TDEGenericDevice*))); + connect(hwdevices, TQT_SIGNAL(hardwareUpdated(TDEGenericDevice*)), this, TQT_SLOT(ModifyDeviceHandler(TDEGenericDevice*))); + + // List devices at startup + ListDevices(); +} + +/* Destructor */ +TDEBackend::~TDEBackend() +{ + // Remove all media from the media list + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + TDEGenericHardwareList hwlist = hwdevices->listAllPhysicalDevices(); + TDEGenericDevice *hwdevice; + for ( hwdevice = hwlist.first(); hwdevice; hwdevice = hwlist.next() ) { + if (hwdevice->type() == TDEGenericDeviceType::Disk) { + TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice); + RemoveDevice(sdevice); + } + } +} + +void TDEBackend::AddDeviceHandler(TDEGenericDevice *device) { + if (device->type() == TDEGenericDeviceType::Disk) { + TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(device); + AddDevice(sdevice); + } +} + +void TDEBackend::RemoveDeviceHandler(TDEGenericDevice *device) { + if (device->type() == TDEGenericDeviceType::Disk) { + TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(device); + RemoveDevice(sdevice); + } +} + +void TDEBackend::ModifyDeviceHandler(TDEGenericDevice *device) { + if (device->type() == TDEGenericDeviceType::Disk) { + TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(device); + ModifyDevice(sdevice); + } +} + +// List devices (at startup) +bool TDEBackend::ListDevices() +{ + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + TDEGenericHardwareList hwlist = hwdevices->listAllPhysicalDevices(); + TDEGenericDevice *hwdevice; + for ( hwdevice = hwlist.first(); hwdevice; hwdevice = hwlist.next() ) { + if (hwdevice->type() == TDEGenericDeviceType::Disk) { + TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice); + AddDevice(sdevice, false); + } + } + + return true; +} + +// Create a media instance for a new storage device +void TDEBackend::AddDevice(TDEStorageDevice * sdevice, bool allowNotification) +{ + kdDebug(1219) << "TDEBackend::AddDevice for " << sdevice->uniqueID() << endl; + + // If the device is already listed, do not process it + // This should not happen, but who knows... + /** @todo : refresh properties instead ? */ + if (m_mediaList.findById(sdevice->uniqueID())) { + kdDebug(1219) << "TDEBackend::AddDevice for " << sdevice->uniqueID() << " found existing entry in media list" << endl; + return; + } + + // Add volume block devices + if (sdevice->isDiskOfType(TDEDiskDeviceType::HDD)) { + /* We only list volumes that... + * - are encrypted with LUKS or + * - have a filesystem or + * - have an audio track + */ + if (!(sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::ContainsFilesystem)) + && !(sdevice->isDiskOfType(TDEDiskDeviceType::CDAudio)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) + ) { + // + } + else { + // Create medium + Medium* medium = new Medium(sdevice->uniqueID(), driveUDIFromDeviceUID(sdevice->uniqueID()), ""); + setVolumeProperties(medium); + + // Do not list the LUKS backend device if it has been unlocked elsewhere + if (sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) { + if (sdevice->holdingDevices().count() > 0) { + medium->setHidden(true); + } + else { + medium->setHidden(false); + } + } + + // Insert medium into list + m_mediaList.addMedium(medium, allowNotification); + + kdDebug(1219) << "TDEBackend::AddDevice inserted hard medium for " << sdevice->uniqueID() << endl; + + // Automount if enabled + CHECK_FOR_AND_EXECUTE_AUTOMOUNT(sdevice->uniqueID(), medium, allowNotification) + } + } + + // Add CD drives + if ((sdevice->isDiskOfType(TDEDiskDeviceType::CDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRAM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDAudio)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDVideo)) + ) { + + // Create medium + Medium* medium = new Medium(sdevice->uniqueID(), driveUDIFromDeviceUID(sdevice->uniqueID()), ""); + setVolumeProperties(medium); + + // Insert medium into list + m_mediaList.addMedium(medium, allowNotification); + + kdDebug(1219) << "TDEBackend::AddDevice inserted optical medium for " << sdevice->uniqueID() << endl; + + // Automount if enabled + CHECK_FOR_AND_EXECUTE_AUTOMOUNT(sdevice->uniqueID(), medium, allowNotification) + } + + // Floppy & zip drives + if ((sdevice->isDiskOfType(TDEDiskDeviceType::Floppy)) || + (sdevice->isDiskOfType(TDEDiskDeviceType::Zip)) || + (sdevice->isDiskOfType(TDEDiskDeviceType::Jaz)) + ) { + if ((sdevice->checkDiskStatus(TDEDiskDeviceStatus::Removable)) && (!(sdevice->checkDiskStatus(TDEDiskDeviceStatus::Inserted)))) { + allowNotification = false; + } + + /* We only list volumes that... + * - are encrypted with LUKS or + * - have a filesystem or + * - are a floppy disk + */ + if (!(sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::ContainsFilesystem)) + && !(sdevice->isDiskOfType(TDEDiskDeviceType::Floppy)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) + ) { + // + } + else { + // Create medium + Medium* medium = new Medium(sdevice->uniqueID(), driveUDIFromDeviceUID(sdevice->uniqueID()), ""); + + setFloppyProperties(medium); + + // Do not list the LUKS backend device if it has been unlocked elsewhere + if (sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) { + if (sdevice->holdingDevices().count() > 0) { + medium->setHidden(true); + } + else { + medium->setHidden(false); + } + } + + m_mediaList.addMedium(medium, allowNotification); + + kdDebug(1219) << "TDEBackend::AddDevice inserted floppy medium for " << sdevice->uniqueID() << endl; + + return; + } + } + + // PTP camera + if (sdevice->isDiskOfType(TDEDiskDeviceType::Camera)) { + // PTP cameras are handled by the "camera" tdeioslave + if (KProtocolInfo::isKnownProtocol( TQString("camera") ) ) + { + // Create medium + Medium* medium = new Medium(sdevice->uniqueID(), driveUDIFromDeviceUID(sdevice->uniqueID()), ""); + setCameraProperties(medium); + m_mediaList.addMedium(medium, allowNotification); + + kdDebug(1219) << "TDEBackend::AddDevice inserted camera medium for " << sdevice->uniqueID() << endl; + + return; + } + } +} + +void TDEBackend::RemoveDevice(TDEStorageDevice * sdevice) +{ + kdDebug(1219) << "TDEBackend::RemoveDevice for " << sdevice->uniqueID() << endl; + + if (!m_mediaList.findById(sdevice->uniqueID())) { + kdDebug(1219) << "TDEBackend::RemoveDevice for " << sdevice->uniqueID() << " existing entry in media list was not found" << endl; + return; + } + + m_mediaList.removeMedium(sdevice->uniqueID(), true); +} + +void TDEBackend::ModifyDevice(TDEStorageDevice * sdevice) +{ + kdDebug(1219) << "TDEBackend::ModifyDevice for " << sdevice->uniqueID() << endl; + + bool allowNotification = false; + ResetProperties(sdevice, allowNotification); +} + +void TDEBackend::ResetProperties(TDEStorageDevice * sdevice, bool allowNotification, bool overrideIgnoreList) +{ + kdDebug(1219) << "TDEBackend::ResetProperties for " << sdevice->uniqueID() << " allowNotification: " << allowNotification << " overrideIgnoreList: " << overrideIgnoreList << endl; + + if (!m_mediaList.findById(sdevice->uniqueID())) { + // This device is not currently in the device list, so add it and exit + kdDebug(1219) << "TDEBackend::ResetProperties for " << sdevice->uniqueID() << " existing entry in media list was not found" << endl; + AddDevice(sdevice); + return; + } + + // If we should ignore device change events for this device, do so + if (overrideIgnoreList == false) { + if (m_ignoreDeviceChangeEvents.contains(sdevice->uniqueID())) { + return; + } + } + + Medium* m = new Medium(sdevice->uniqueID(), driveUDIFromDeviceUID(sdevice->uniqueID()), ""); + + // Keep these conditions in sync with ::AddDevice above, OR ELSE!!! + // BEGIN + + if (!(sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::ContainsFilesystem)) + && !(sdevice->isDiskOfType(TDEDiskDeviceType::CDAudio)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) + ) { + } + else { + // Do not list the LUKS backend device if it has been unlocked elsewhere + if (sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) { + if (sdevice->holdingDevices().count() > 0) { + m->setHidden(true); + } + else { + m->setHidden(false); + } + } + setVolumeProperties(m); + } + + if ((sdevice->isDiskOfType(TDEDiskDeviceType::CDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRAM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDAudio)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDVideo)) + ) { + setVolumeProperties(m); + } + + // Floppy & zip drives + if ((sdevice->isDiskOfType(TDEDiskDeviceType::Floppy)) || + (sdevice->isDiskOfType(TDEDiskDeviceType::Zip)) || + (sdevice->isDiskOfType(TDEDiskDeviceType::Jaz)) + ) { + + if (!(sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::ContainsFilesystem)) + && !(sdevice->isDiskOfType(TDEDiskDeviceType::Floppy)) + && !(sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) + ) { + // + } + else { + // Do not list the LUKS backend device if it has been unlocked elsewhere + if (sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) { + if (sdevice->holdingDevices().count() > 0) { + m->setHidden(true); + } + else { + m->setHidden(false); + } + } + + setFloppyProperties(m); + } + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::Camera)) { + setCameraProperties(m); + } + + // END + + if ((sdevice->checkDiskStatus(TDEDiskDeviceStatus::Removable)) && (!(sdevice->checkDiskStatus(TDEDiskDeviceStatus::Inserted)))) { + kdDebug(1219) << "TDEBackend::ResetProperties for " << sdevice->uniqueID() << " device was removed from system" << endl; + RemoveDevice(sdevice); + return; + } + + m_mediaList.changeMediumState(*m, allowNotification); + + delete m; +} + +void TDEBackend::setVolumeProperties(Medium* medium) +{ + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + TDEStorageDevice * sdevice = hwdevices->findDiskByUID(medium->id()); + if (!sdevice) { + return; + } + + medium->setName(generateName(sdevice->deviceNode())); + if ((sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) || (sdevice->isDiskOfType(TDEDiskDeviceType::UnlockedCrypt))) { + medium->setEncrypted(true); + } + else { + medium->setEncrypted(false); + } + + // USAGE: mountableState(Device node, Mount point, Filesystem type, Mounted ?) + medium->mountableState(sdevice->deviceNode(), sdevice->mountPath(), sdevice->fileSystemName(), !sdevice->mountPath().isNull()); + + TQString diskLabel = sdevice->diskLabel(); + if (diskLabel.isNull()) { + diskLabel = i18n("%1 Removable Device").arg(sdevice->deviceFriendlySize()); + } + + TQString mimeType; + + if ((sdevice->isDiskOfType(TDEDiskDeviceType::CDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRAM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDROM)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDRW)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDAudio)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDVideo)) + ) { + // This device is a CD drive of some sort + + // Default + mimeType = "media/cdrom" + MOUNT_SUFFIX; + + if (sdevice->isDiskOfType(TDEDiskDeviceType::CDROM)) { + mimeType = "media/cdrom" + MOUNT_SUFFIX; + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) { + mimeType = "media/blankcd"; + medium->unmountableState(""); + diskLabel = i18n("Blank CDROM"); + } + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::CDRW)) { + mimeType = "media/cdwriter" + MOUNT_SUFFIX; + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) { + mimeType = "media/blankcd"; + medium->unmountableState(""); + diskLabel = i18n("Blank CDRW"); + } + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::DVDROM)) { + mimeType = "media/dvd" + MOUNT_SUFFIX; + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) { + mimeType = "media/blankdvd"; + medium->unmountableState(""); + diskLabel = i18n("Blank DVDROM"); + } + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRAM)) { + mimeType = "media/dvd" + MOUNT_SUFFIX; + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) { + mimeType = "media/blankdvd"; + medium->unmountableState(""); + diskLabel = i18n("Blank DVDRAM"); + } + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::DVDRW)) { + mimeType = "media/dvd" + MOUNT_SUFFIX; + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) { + mimeType = "media/blankdvd" + MOUNT_SUFFIX; + medium->unmountableState(""); + diskLabel = i18n("Blank DVDRW"); + } + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::BDROM)) { + mimeType = "media/bluray" + MOUNT_SUFFIX; + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) { + mimeType = "media/blankbd" + MOUNT_SUFFIX; + medium->unmountableState(""); + diskLabel = i18n("Blank BDROM"); + } + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::BDRW)) { + mimeType = "media/bluray" + MOUNT_SUFFIX; + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) { + mimeType = "media/blankbd" + MOUNT_SUFFIX; + medium->unmountableState(""); + diskLabel = i18n("Blank BDRW"); + } + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::CDAudio)) { + mimeType = "media/audiocd"; + medium->unmountableState("audiocd:/?device=" + sdevice->deviceNode()); + diskLabel = i18n("Audio CD"); + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::CDVideo)) { + mimeType = "media/vcd"; + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::DVDVideo)) { + mimeType = "media/dvdvideo"; + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::BDVideo)) { + mimeType = "media/bdvideo"; + } + + medium->setIconName(TQString::null); + } + else { + // This device is a hard or flash disk of some kind + + // Default + mimeType = "media/hdd" + MOUNT_SUFFIX; + + if (sdevice->isDiskOfType(TDEDiskDeviceType::USB)) { + mimeType = "media/removable" + MOUNT_SUFFIX; + medium->needMounting(); + + if (sdevice->isDiskOfType(TDEDiskDeviceType::CompactFlash)) { + medium->setIconName("compact_flash" + MOUNT_ICON_SUFFIX); + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::MemoryStick)) { + medium->setIconName("memory_stick" + MOUNT_ICON_SUFFIX); + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::SmartMedia)) { + medium->setIconName("smart_media" + MOUNT_ICON_SUFFIX); + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::SDMMC)) { + medium->setIconName("sd_mmc" + MOUNT_ICON_SUFFIX); + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::MediaDevice)) { + medium->setIconName("ipod" + MOUNT_ICON_SUFFIX); + + if (sdevice->vendorModel().upper().contains("IPOD") && KProtocolInfo::isKnownProtocol( TQString("ipod") ) ) + { + medium->unmountableState( "ipod:/" ); + medium->mountableState(!sdevice->mountPath().isNull()); + } + } + if (sdevice->isDiskOfType(TDEDiskDeviceType::Tape)) { + medium->setIconName("magnetic_tape" + MOUNT_ICON_SUFFIX); + } + if (medium->isMounted() && TQFile::exists(medium->mountPoint() + "/dcim")) + { + mimeType = "media/camera" + MOUNT_SUFFIX; + } + } + } + + if (!medium->needMounting()) { + if (sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) { + if (sdevice->checkDiskStatus(TDEDiskDeviceStatus::UsedByDevice)) { + // Encrypted base devices must be set to this mimetype or they won't open when the base device node is passed to the tdeioslave + mimeType = "media/removable_mounted"; + } + } + } + + medium->setLabel(diskLabel); + medium->setMimeType(mimeType); +} + +// Handle floppies and zip drives +bool TDEBackend::setFloppyProperties(Medium* medium) +{ + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + TDEStorageDevice * sdevice = hwdevices->findDiskByUID(medium->id()); + if (!sdevice) { + return false; + } + + medium->setName(generateName(sdevice->deviceNode())); + medium->setLabel(i18n("Unknown Drive")); + + // Certain disks have a lot in common with hard drives + // FIXME + // Any more? + if ((sdevice->isDiskOfType(TDEDiskDeviceType::Zip)) || (sdevice->isDiskOfType(TDEDiskDeviceType::Jaz))) { + medium->setName(generateName(sdevice->deviceNode())); + if ((sdevice->isDiskOfType(TDEDiskDeviceType::LUKS)) || (sdevice->isDiskOfType(TDEDiskDeviceType::UnlockedCrypt))) { + medium->setEncrypted(true); + } + else { + medium->setEncrypted(false); + } + + // USAGE: mountableState(Device node, Mount point, Filesystem type, Mounted ?) + medium->mountableState(sdevice->deviceNode(), sdevice->mountPath(), sdevice->fileSystemName(), !sdevice->mountPath().isNull()); + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::Floppy)) { + setFloppyMountState(medium); + + // We don't use the routine above as floppy disks are extremely slow (we don't want them accessed at all during media listing) + medium->mountableState(sdevice->deviceNode(), sdevice->mountPath(), sdevice->fileSystemName(), !sdevice->mountPath().isNull()); + + if (sdevice->mountPath().isNull()) { + medium->setMimeType("media/floppy_unmounted"); + } + else { + medium->setMimeType("media/floppy_mounted" ); + } + medium->setLabel(i18n("Floppy Drive")); + } + + if (sdevice->isDiskOfType(TDEDiskDeviceType::Zip)) { + if (sdevice->mountPath().isNull()) { + medium->setMimeType("media/zip_unmounted"); + } + else { + medium->setMimeType("media/zip_mounted" ); + } + + // Set label + TQString diskLabel = sdevice->diskLabel(); + if (diskLabel.isNull()) { + diskLabel = i18n("%1 Zip Disk").arg(sdevice->deviceFriendlySize()); + } + medium->setLabel(diskLabel); + } + + /** @todo Mimetype for JAZ drives ? */ + + medium->setIconName(TQString::null); + + return true; +} + +void TDEBackend::setCameraProperties(Medium* medium) +{ + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + TDEStorageDevice * sdevice = hwdevices->findDiskByUID(medium->id()); + if (!sdevice) { + return; + } + + TQString cameraName = sdevice->friendlyName(); + cameraName.replace("/", "_"); + medium->setName(cameraName); + + TQString device = "camera:/"; + + TQStringList devNodeList = TQStringList::split("/", sdevice->deviceNode(), TRUE); + TQString devNode0 = devNodeList[devNodeList.count()-2]; + TQString devNode1 = devNodeList[devNodeList.count()-1]; + + if ((devNode0 != "") && (devNode1 != "")) { + device.sprintf("camera://@[usb:%s,%s]/", devNode0.ascii(), devNode1.ascii()); + } + + medium->unmountableState(device); + medium->setMimeType("media/gphoto2camera"); + medium->setIconName(TQString::null); + + if (sdevice->friendlyName() != "") { + medium->setLabel(sdevice->friendlyName()); + } + else { + medium->setLabel(i18n("Camera")); + } +} + +void TDEBackend::setFloppyMountState( Medium *medium ) +{ + KMountPoint::List mtab = KMountPoint::currentMountPoints(); + KMountPoint::List::iterator it = mtab.begin(); + KMountPoint::List::iterator end = mtab.end(); + + TQString fstype; + TQString 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; + } + } +} + +TQStringList TDEBackend::mountoptions(const TQString &name) +{ + const Medium* medium = m_mediaList.findById(name); + if (!medium) { + return TQStringList(); // we know nothing about that device + } + if (!isInFstab(medium).isNull()) { + return TQStringList(); // device is listed in fstab, therefore is not handled by us + } + + if (medium->isEncrypted()) { + // if not decrypted yet then there are no mountoptions + return TQStringList(); + } + + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + TDEStorageDevice * sdevice = hwdevices->findDiskByUID(medium->id()); + if (!sdevice) { + return TQStringList(); // we can't get the information needed in order to process mount options + } + + TQStringList result; + + // Allow only those options which are valid for the given device + // pmount only allows a subset of options, and those are given in the map below + // FIXME + TQMap<TQString,bool> valids; + valids["ro"] = true; + //valids["quiet"] = false; + //valids["flush"] = false; + //valids["uid"] = false; + valids["utf8"] = true; + //valids["shortname"] = false; + valids["locale"] = true; + valids["sync"] = true; + valids["noatime"] = true; + //valids["data"] = false; + + TQString drive_udi = driveUDIFromDeviceUID(medium->id()); + + TDEConfig config("mediamanagerrc"); + + bool use_defaults = true; + if (config.hasGroup(drive_udi)) { + config.setGroup(drive_udi); + use_defaults = config.readBoolEntry("use_defaults", false); + } + if (use_defaults) { + config.setGroup("DefaultOptions"); + } + result << TQString("use_defaults=%1").arg(use_defaults ? "true" : "false"); + + bool removable = false; + if (!drive_udi.isNull()) { + removable = ((sdevice->checkDiskStatus(TDEDiskDeviceStatus::Removable)) || (sdevice->checkDiskStatus(TDEDiskDeviceStatus::Hotpluggable))); + } + + TQString tmp; + bool value; + if (use_defaults) { + value = config.readBoolEntry("automount", false); + } + else { + QString current_group = config.group(); + config.setGroup(drive_udi); + value = config.readBoolEntry("automount", false); + config.setGroup(current_group); + } + + if ((sdevice->checkDiskStatus(TDEDiskDeviceStatus::Blank)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDAudio)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::CDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::DVDVideo)) + || (sdevice->isDiskOfType(TDEDiskDeviceType::BDVideo)) + ) { + value = false; + } + + result << TQString("automount=%1").arg(value ? "true" : "false"); + + if (valids.contains("ro")) { + value = config.readBoolEntry("ro", false); + tmp = TQString("ro=%1").arg(value ? "true" : "false"); + if (sdevice->fileSystemName() != "iso9660") { + result << tmp; + } + } + + if (valids.contains("quiet")) { + value = config.readBoolEntry("quiet", false); + tmp = TQString("quiet=%1").arg(value ? "true" : "false"); + if (sdevice->fileSystemName() != "iso9660") { + result << tmp; + } + } + + if (valids.contains("flush")) { + value = config.readBoolEntry("flush", sdevice->fileSystemName().endsWith("fat")); + tmp = TQString("flush=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("uid")) { + value = config.readBoolEntry("uid", true); + tmp = TQString("uid=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("utf8")) { + value = config.readBoolEntry("utf8", true); + tmp = TQString("utf8=%1").arg(value ? "true" : "false"); + result << tmp; + } + + if (valids.contains("shortname")) { + TQString 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") && (sdevice->fileSystemName() == "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 << TQString("locale=%1").arg(cType); + } + } + + if (valids.contains("sync")) { + value = config.readBoolEntry("sync", ( valids.contains("flush") && !sdevice->fileSystemName().endsWith("fat") ) && removable); + tmp = TQString("sync=%1").arg(value ? "true" : "false"); + if (sdevice->fileSystemName() != "iso9660") { + result << tmp; + } + } + + if (valids.contains("noatime")) { + value = config.readBoolEntry("atime", !sdevice->fileSystemName().endsWith("fat")); + tmp = TQString("atime=%1").arg(value ? "true" : "false"); + if (sdevice->fileSystemName() != "iso9660") { + result << tmp; + } + } + + TQString mount_point; + mount_point = config.readEntry("mountpoint", TQString::null); + + if (!mount_point.startsWith("/")) { + mount_point = "/media/" + mount_point; + } + if (mount_point != "") { + result << TQString("mountpoint=%1").arg(mount_point); + } + + TQString file_system_name; + file_system_name = sdevice->fileSystemName(); + if (file_system_name != "") { + result << TQString("filesystem=%1").arg(file_system_name); + } + + if (valids.contains("data")) { + TQString 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 TDEBackend::setMountoptions(const TQString &name, const TQStringList &options ) +{ + const Medium* medium = m_mediaList.findById(name); + if (!medium) { + return false; // we know nothing about that device + } + if (!isInFstab(medium).isNull()) { + return false; // device is listed in fstab, therefore is not handled by us + } + + TQString drive_udi = driveUDIFromDeviceUID(medium->id()); + + kdDebug(1219) << "setMountoptions " << name << " " << options << endl; + + TDEConfig config("mediamanagerrc"); + config.setGroup(drive_udi); + + TQMap<TQString,TQString> valids = MediaManagerUtils::splitOptions(options); + + const char *names[] = { "use_defaults", "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(TQString("mountpoint=%1").arg(valids["mountpoint"]))) { + config.writeEntry("mountpoint", valids["mountpoint"]); + } + + if (valids.contains("automount")) { + config.setGroup(drive_udi); + config.writeEntry("automount", valids["automount"]); + } + + return true; +} + +void TDEBackend::slotPasswordReady() { + m_decryptionPassword = m_decryptDialog->getPassword(); + m_decryptPasswordValid = true; +} + +void TDEBackend::slotPasswordCancel() { + m_decryptionPassword = TQString::null; + m_decryptPasswordValid = true; +} + +TQString TDEBackend::mount(const Medium *medium) +{ + if (medium->isMounted()) { + return TQString(); // that was easy + } + + TQString mountPoint = isInFstab(medium); + if (!mountPoint.isNull()) + { + struct mount_job_data data; + data.completed = false; + data.medium = medium; + + TDEIO::Job *job = TDEIO::mount( false, 0, medium->deviceNode(), mountPoint ); + connect(job, TQT_SIGNAL( result (TDEIO::Job *)), TQT_SLOT( slotResult( TDEIO::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 : TQString::null; + + } + + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + TDEStorageDevice * sdevice = hwdevices->findDiskByUID(medium->id()); + if (!sdevice) { + return i18n("Internal error"); + } + + TQString optionString; + TQString diskLabel; + + TQMap<TQString,TQString> valids = MediaManagerUtils::splitOptions(mountoptions(medium->id())); + if (valids["ro"] == "true") { + optionString.append(" -r"); + } + + if (valids["atime"] != "true") { + optionString.append(" -A"); + } + + if (valids["utf8"] == "true") { + optionString.append(" -c utf8"); + } + + if (valids["sync"] == "true") { + optionString.append(" -s"); + } + + TQString mount_point = valids["mountpoint"]; + if (mount_point.startsWith("/media/")) { + diskLabel = mount_point.mid(7); + } + + if (valids.contains("filesystem")) { + optionString.append(TQString(" -t %1").arg(valids["filesystem"])); + } + + if (valids.contains("locale")) { + optionString.append(TQString(" -c %1").arg(valids["locale"])); + } + + if (diskLabel == "") { + // Try to use a pretty mount point if possible + TQStringList pieces = TQStringList::split("/", sdevice->deviceNode(), FALSE); + TQString node = pieces[pieces.count()-1]; + diskLabel = medium->label() + " (" + node + ")"; + diskLabel.replace("/", "_"); + } + + TQString qerror = i18n("Cannot mount encrypted drives!"); + + if (!medium->isEncrypted()) { + // normal volume + TQString mountMessages; + TQString mountedPath = sdevice->mountDevice(diskLabel, optionString, &mountMessages); + if (mountedPath.isNull()) { + qerror = i18n("<qt>Unable to mount this device.<p>Potential reasons include:<br>Improper device and/or user privilege level<br>Corrupt data on storage device"); + if (!mountMessages.isNull()) { + qerror.append(i18n("<p>Technical details:<br>").append(mountMessages)); + } + qerror.append("</qt>"); + } + else { + qerror = ""; + } + } + else { + TQString iconName = medium->iconName(); + if (iconName.isEmpty()) + { + TQString mime = medium->mimeType(); + iconName = KMimeType::mimeType(mime)->icon(mime, false); + } + + bool continue_trying_to_decrypt = true; + while (continue_trying_to_decrypt == true) { + m_decryptPasswordValid = false; + + m_decryptDialog = new Dialog(sdevice->deviceNode(), iconName); + m_decryptDialog->show(); + + connect(m_decryptDialog, TQT_SIGNAL (user1Clicked()), this, TQT_SLOT (slotPasswordReady())); + connect(m_decryptDialog, TQT_SIGNAL (cancelClicked()), this, TQT_SLOT (slotPasswordCancel())); + connect(this, TQT_SIGNAL (signalDecryptionPasswordError(TQString)), m_decryptDialog, TQT_SLOT (slotDialogError(TQString))); + + while (m_decryptPasswordValid == false) { + tqApp->processEvents(); + } + + m_decryptDialog->setEnabled(false); + tqApp->processEvents(); + + if (m_decryptionPassword.isNull()) { + delete m_decryptDialog; + return TQString("Decryption aborted"); + } + else { + // Just for some added fun, if udev emits a medium change event, which I then forward, with mounted==0, it stops the MediaProtocol::listDir method dead in its tracks, + // and therefore the media:/ tdeioslave won't refresh after the encrypted device mount + // Therefore, I need to ignore all change events on this device during the mount process and hope nothing bad happens as a result! + if (!m_ignoreDeviceChangeEvents.contains(sdevice->uniqueID())) { + m_ignoreDeviceChangeEvents.append(sdevice->uniqueID()); + } + + // mount encrypted volume with password + int mountRetcode; + TQString mountMessages; + TQString mountedPath = sdevice->mountEncryptedDevice(m_decryptionPassword, diskLabel, optionString, &mountMessages, &mountRetcode); + if (mountedPath.isNull()) { + if (mountRetcode == 0) { + // Mounting was successful + // Because the TDE hardware backend is event driven it might take a little while for the new unencrypted mapped device to show up + // Wait up to 30 seconds for it to appear... + for (int i=0;i<300;i++) { + mountedPath = sdevice->mountPath(); + if (!mountedPath.isNull()) { + break; + } + tqApp->processEvents(50); + usleep(50000); + } + } + } + if (mountedPath.isNull()) { + if (mountRetcode == 25600) { + // Probable LUKS failure + // Retry + m_decryptDialog->setEnabled(true); + continue_trying_to_decrypt = true; + } + else { + qerror = i18n("<qt>Unable to mount this device.<p>Potential reasons include:<br>Improper device and/or user privilege level<br>Corrupt data on storage device<br>Incorrect encryption password"); + if (!mountMessages.isNull()) { + qerror.append(i18n("<p>Technical details:<br>").append(mountMessages)); + } + qerror.append("</qt>"); + continue_trying_to_decrypt = false; + } + } + else { + qerror = ""; + continue_trying_to_decrypt = false; + } + + delete m_decryptDialog; + } + } + } + + if (!qerror.isEmpty()) { + return qerror; + } + + ResetProperties(sdevice, false, true); + + if (m_ignoreDeviceChangeEvents.contains(sdevice->uniqueID())) { + m_ignoreDeviceChangeEvents.remove(sdevice->uniqueID()); + } + + return TQString(); +} + +TQString TDEBackend::mount(const TQString &_udi) +{ + const Medium* medium = m_mediaList.findById(_udi); + if (!medium) + return i18n("No such medium: %1").arg(_udi); + + return mount(medium); +} + +TQString TDEBackend::unmount(const TQString &_udi) +{ + const Medium* medium = m_mediaList.findById(_udi); + + if ( !medium ) + return i18n("No such medium: %1").arg(_udi); + + if (!medium->isMounted()) + return TQString(); // that was easy + + TQString mountPoint = isInFstab(medium); + if (!mountPoint.isNull()) + { + struct mount_job_data data; + data.completed = false; + data.medium = medium; + + TDEIO::Job *job = TDEIO::unmount( medium->mountPoint(), false ); + connect(job, TQT_SIGNAL( result (TDEIO::Job *)), TQT_SLOT( slotResult( TDEIO::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 : TQString::null; + } + + TQString udi = TQString::null; + + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + TDEStorageDevice * sdevice = hwdevices->findDiskByUID(medium->id()); + if (!sdevice) { + return i18n("Internal error"); + } + + TQString qerror; + TQString origqerror; + + // Save these for later + TQString uid = sdevice->uniqueID(); + TQString node = sdevice->deviceNode(); + + TQString unmountMessages; + int unmountRetcode = 0; + if (!sdevice->unmountDevice(&unmountMessages, &unmountRetcode)) { + // Unmount failed! + qerror = "<qt>" + 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()); + if (!unmountMessages.isNull()) { + qerror.append(i18n("<p>Technical details:<br>").append(unmountMessages)); + } + qerror.append("</qt>"); + } + else { + qerror = ""; + } + + if (unmountRetcode == 1280) { + // Failed as BUSY + TQString processesUsingDev = listUsingProcesses(medium); + if (!processesUsingDev.isNull()) { + if (KMessageBox::warningYesNo(0, i18n("<qt>The device <b>%1</b> (%2) named <b>'%3'</b> and currently mounted at <b>%4</b> can not be unmounted at this time.<p>%5<p><b>Would you like to forcibly terminate these processes?</b><br><i>All unsaved data would be lost</i>").arg("system:/media/" + medium->name()).arg(medium->deviceNode()).arg(medium->prettyLabel()).arg(medium->prettyBaseURL().pathOrURL()).arg(processesUsingDev)) == KMessageBox::Yes) { + killUsingProcesses(medium); + if (!sdevice->unmountDevice(&unmountMessages, &unmountRetcode)) { + // Unmount failed! + qerror = "<qt>" + 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()); + if (!unmountMessages.isNull()) { + qerror.append(i18n("<p>Technical details:<br>").append(unmountMessages)); + } + qerror.append("</qt>"); + } + else { + qerror = ""; + } + } + } + } + + if (qerror != "") { + return qerror; + } + + // There is a possibility that the storage device was unceremoniously removed from the system immediately after it was unmounted + // There is no reliable way to know if this happened either! + // For now, see if the device node still exists + TQFileInfo checkDN(node); + if (!checkDN.exists()) { + m_mediaList.removeMedium(uid, true); + } + else { + TQString mountedPath = sdevice->mountPath(); + if (!mountedPath.isNull()) { + // Because the TDE hardware backend is event driven it might take a little while for the device to show up as unmounted + // Wait up to 30 seconds for the mount to disappear... + for (int i=0;i<300;i++) { + mountedPath = sdevice->mountPath(); + if (mountedPath.isNull()) { + break; + } + tqApp->processEvents(50); + usleep(50000); + } + } + + ResetProperties(sdevice, false); + } + + return TQString(); +} + +TQString TDEBackend::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) + { + TQString reald = (*it)->realDeviceName(); + if ( reald.endsWith( "/" ) ) { + reald = reald.left( reald.length() - 1 ); + } + if ((*it)->mountedFrom() == medium->deviceNode() || ( !medium->deviceNode().isEmpty() && reald == medium->deviceNode() ) ) + { + TQStringList opts = (*it)->mountOptions(); + if (opts.contains("user") || opts.contains("users")) { + return (*it)->mountPoint(); + } + } + } + + return TQString::null; +} + +TQString TDEBackend::listUsingProcesses(const Medium* medium) +{ + TQString proclist, fullmsg; + TQString cmdline = TQString("/usr/bin/env fuser -vm %1 2>&1").arg(TDEProcess::quote(medium->mountPoint())); + FILE *fuser = popen(cmdline.latin1(), "r"); + + uint counter = 0; + if (fuser) { + proclist += "<pre>"; + TQTextIStream is(fuser); + TQString tmp; + while (!is.atEnd()) { + tmp = is.readLine(); + tmp = TQStyleSheet::escape(tmp) + "\n"; + + proclist += tmp; + if (counter++ > 10) { + proclist += "..."; + break; + } + } + proclist += "</pre>"; + (void)pclose( fuser ); + } + if (counter) { + fullmsg = i18n("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 TQString::null; + } +} + +TQString TDEBackend::killUsingProcesses(const Medium* medium) +{ + TQString proclist, fullmsg; + TQString cmdline = TQString("/usr/bin/env fuser -vmk %1 2>&1").arg(TDEProcess::quote(medium->mountPoint())); + FILE *fuser = popen(cmdline.latin1(), "r"); + + uint counter = 0; + if (fuser) { + proclist += "<pre>"; + TQTextIStream is(fuser); + TQString tmp; + while (!is.atEnd()) { + tmp = is.readLine(); + tmp = TQStyleSheet::escape(tmp) + "\n"; + + proclist += tmp; + if (counter++ > 10) { + proclist += "..."; + break; + } + } + proclist += "</pre>"; + (void)pclose( fuser ); + } + if (counter) { + fullmsg = i18n("Programs that were still using the device " + "have been forcibly terminated. They are listed below."); + fullmsg += "<br>" + proclist; + return fullmsg; + } + else { + return TQString::null; + } +} + +TQString TDEBackend::generateName(const TQString &devNode) +{ + return KURL(devNode).fileName(); +} + +TQString TDEBackend::driveUDIFromDeviceUID(TQString uuid) { + TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices(); + + TDEStorageDevice * sdevice = hwdevices->findDiskByUID(uuid); + TQString ret; + if (sdevice) { + ret = sdevice->diskUUID(); + if (ret != "") { + ret = "volume_uuid_" + ret; + } + else { + ret = sdevice->deviceNode(); + if (ret != "") { + ret = "device_node_" + ret; + } + else { + ret = sdevice->uniqueID(); + } + } + } + if (ret == "") { + return TQString::null; + } + else { + return ret; + } +} + +#include "tdehardwarebackend.moc"
\ No newline at end of file diff --git a/tdeioslave/media/mediamanager/tdehardwarebackend.h b/tdeioslave/media/mediamanager/tdehardwarebackend.h new file mode 100644 index 000000000..9b5407d8f --- /dev/null +++ b/tdeioslave/media/mediamanager/tdehardwarebackend.h @@ -0,0 +1,171 @@ +/* This file is part of the TDE Project + Copyright (c) 2012 Timothy Pearson <[email protected]> + + 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 builtin TDE hardware library +* +* @author Timothy Pearson <[email protected]> +* @short media:/ backend for the TDE hardware library +*/ + +#ifndef _TDEBACKEND_H_ +#define _TDEBACKEND_H_ + +#include "backendbase.h" + +#include <tqobject.h> +#include <tqstringlist.h> +#include <tqstring.h> + +#include <config.h> + +#include <tdehardwaredevices.h> + +namespace TDEIO { + class Job; +} + +class Dialog; + +class TDEBackend : public TQObject, public BackendBase +{ +Q_OBJECT + +public: + /** + * Constructor + */ + TDEBackend(MediaList &list, TQObject* parent); + + /** + * Destructor + */ + ~TDEBackend(); + + /** + * List all devices and append them to the media device list (called only once, at startup). + * + * @return true if succeded, false otherwise + */ + bool ListDevices(); + + TQStringList mountoptions(const TQString &id); + + bool setMountoptions(const TQString &id, const TQStringList &options); + + TQString mount(const TQString &id); + TQString mount(const Medium *medium); + TQString unmount(const TQString &id); +// TQString decrypt(const TQString &id, const TQString &password); +// TQString undecrypt(const TQString &id); + +private: + /** + * Append a device in the media list. This function will check if the device + * is worth listing. + * + * @param sdevice A pointer to a TDEStorageDevice object + * @param allowNotification Indicates if this event will be notified to the user + */ + void AddDevice(TDEStorageDevice * sdevice, bool allowNotification=true); + + /** + * Remove a device from the device list + * + * @param sdevice A pointer to a TDEStorageDevice object + */ + void RemoveDevice(TDEStorageDevice * sdevice); + + /** + * A device has changed, update it + * + * @param sdevice A pointer to a TDEStorageDevice object + */ + void ModifyDevice(TDEStorageDevice * sdevice); + +private slots: + void AddDeviceHandler(TDEGenericDevice* device); + void RemoveDeviceHandler(TDEGenericDevice* device); + void ModifyDeviceHandler(TDEGenericDevice* device); + + void slotPasswordReady(); + void slotPasswordCancel(); + +signals: + void signalDecryptionPasswordError(TQString); + +/* Set media properties */ +private: + /** + * Reset properties for the given medium + * + * @param sdevice A pointer to a TDEStorageDevice objec + * @param allowNotification Indicates if this event will be notified to the user + * @param overrideIgnoreList If true, override event ignore requests for the current device node + */ + void ResetProperties(TDEStorageDevice * sdevice, bool allowNotification=false, bool overrideIgnoreList=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); + TQString generateName(const TQString &devNode); + static TQString isInFstab(const Medium *medium); + static TQString listUsingProcesses(const Medium *medium); + static TQString killUsingProcesses(const Medium *medium); + + TQString driveUDIFromDeviceUID(TQString uuid); + + // Decryption + Dialog* m_decryptDialog; + TQString m_decryptionPassword; + bool m_decryptPasswordValid; + +/* TDE structures */ +private: + /** + * Object for the kded module + */ + TQObject* m_parent; + + /** + * 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] TDEIO::Error if an error occured during operation. Otherwise, 0 + int error; + // [out] Error message to be displayed to the user + TQString errorMessage; + }; + + TQMap<TDEIO::Job *, struct mount_job_data*> mount_jobs; + + TQStringList m_ignoreDeviceChangeEvents; +}; + +#endif /* _TDEBACKEND_H_ */ diff --git a/tdeioslave/media/medianotifier/CMakeLists.txt b/tdeioslave/media/medianotifier/CMakeLists.txt new file mode 100644 index 000000000..9618f79ab --- /dev/null +++ b/tdeioslave/media/medianotifier/CMakeLists.txt @@ -0,0 +1,43 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR}/tdeioslave/media/libmediacommon + ${CMAKE_SOURCE_DIR}/tdeioslave/media/libmediacommon + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES medianotifier.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded ) + + +##### kded_medianotifier (module) ############### + +set( target kded_medianotifier ) + +set( ${target}_SRCS + medianotifier.cpp medianotifier.skel notificationdialog.cpp + notificationdialogview.ui +) + +tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK mediacommon-static tdeinit_kded-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/media/medianotifier/Makefile.am b/tdeioslave/media/medianotifier/Makefile.am new file mode 100644 index 000000000..b31354810 --- /dev/null +++ b/tdeioslave/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_TDECORE) \ + $(LIB_TDEUI) $(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/tdeioslave/media/medianotifier/medianotifier.cpp b/tdeioslave/media/medianotifier/medianotifier.cpp new file mode 100644 index 000000000..de6a56645 --- /dev/null +++ b/tdeioslave/media/medianotifier/medianotifier.cpp @@ -0,0 +1,386 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <sys/vfs.h> + +#include <tqfile.h> +#include <tqfileinfo.h> +#include <tqdir.h> +#include <tqcheckbox.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 TQCString &name) : KDEDModule(name) +{ + connectDCOPSignal( "kded", "mediamanager", "mediumAdded(TQString, bool)", + "onMediumChange(TQString, bool)", true ); + + connectDCOPSignal( "kded", "mediamanager", "mediumChanged(TQString, bool)", + "onMediumChange(TQString, bool)", true ); + + m_freeTimer = new TQTimer( this ); + connect( m_freeTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( checkFreeDiskSpace() ) ); + m_freeTimer->start( 1000*6*2 /* 20 minutes */ ); + m_freeDialog = 0; +} + +MediaNotifier::~MediaNotifier() +{ + disconnectDCOPSignal( "kded", "mediamanager", "mediumAdded(TQString, bool)", + "onMediumChange(TQString, bool)" ); + + disconnectDCOPSignal( "kded", "mediamanager", "mediumChanged(TQString, bool)", + "onMediumChange(TQString, bool)" ); +} + +void MediaNotifier::onMediumChange( const TQString &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 ); + + TDEIO::SimpleJob *job = TDEIO::stat( url, false ); + job->setInteractive( false ); + + m_allowNotificationMap[job] = allowNotification; + + connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), + this, TQT_SLOT( slotStatResult( TDEIO::Job * ) ) ); +} + +void MediaNotifier::slotStatResult( TDEIO::Job *job ) +{ + bool allowNotification = m_allowNotificationMap[job]; + m_allowNotificationMap.remove( job ); + + if ( job->error() != 0 ) return; + + TDEIO::StatJob *stat_job = static_cast<TDEIO::StatJob *>( job ); + + TDEIO::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 ) +{ + TQString mimetype = medium.mimetype(); + + bool is_cdrom = mimetype.startsWith( "media/cd" ) || mimetype.startsWith( "media/dvd" ); + bool is_mounted = mimetype.contains( "_mounted" ); + + // We autorun only on CD/DVD or removable disks (USB, Firewire) + if ( !( is_cdrom || is_mounted ) + && !mimetype.startsWith("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; + TQString 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 + TQStringList autorun_list; + autorun_list << ".autorun" << "autorun" << "autorun.sh"; + + TQStringList::iterator it = autorun_list.begin(); + TQStringList::iterator end = autorun_list.end(); + + for ( ; it!=end; ++it ) + { + if ( TQFile::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 + TQStringList autoopen_list; + autoopen_list << ".autoopen" << "autoopen"; + + it = autoopen_list.begin(); + end = autoopen_list.end(); + + for ( ; it!=end; ++it ) + { + if ( TQFile::exists( path + "/" + *it ) ) + { + return execAutoopen( medium, path, *it ); + } + } + + return false; +} + +bool MediaNotifier::execAutorun( const KFileItem &medium, const TQString &path, + const TQString &autorunFile ) +{ + // The desktop environment MUST prompt the user for confirmation + // before automatically starting an application. + TQString mediumType = medium.mimeTypePtr()->name(); + TQString 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 ); + TQString 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, + TQString::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. + TDEProcess proc; + proc << "sh" << autorunFile; + proc.setWorkingDirectory( path ); + proc.start(); + proc.detach(); + } + + return true; +} + +bool MediaNotifier::execAutoopen( const KFileItem &medium, const TQString &path, + const TQString &autoopenFile ) +{ + // An Autoopen file MUST contain a single relative path that points + // to a non-executable file contained on the medium. [...] + TQFile file( path+"/"+autoopenFile ); + file.open( IO_ReadOnly ); + TQTextStream stream( &file ); + + TQString 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 [...] + TQString resolved_path + = KStandardDirs::realFilePath( path+"/"+relative_path ); + + if ( !resolved_path.startsWith( path ) ) + { + return false; + } + + + TQFile 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() /*|| TQFileInfo(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. + TQString mediumType = medium.mimeTypePtr()->name(); + TQString filename = url.filename(); + TQString 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 ); + TQString 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, + TQString::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 ) + { + TQValueList<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 TQCString &name) + { + TDEGlobal::locale()->insertCatalogue("kay"); + return new MediaNotifier(name); + } +} + +void MediaNotifier::checkFreeDiskSpace() +{ + struct statfs sfs; + long total, avail; + if ( m_freeDialog ) + return; + + if ( statfs( TQFile::encodeName( TQDir::homeDirPath() ), &sfs ) == 0 ) + { + total = sfs.f_blocks; + avail = ( getuid() ? sfs.f_bavail : sfs.f_bfree ); + + if (avail < 0 || total <= 0) + return; // we better do not say anything about it + + int freeperc = static_cast<int>(100 * double(avail) / total); + + if ( freeperc < 5 && KMessageBox::shouldBeShownContinue( "dontagainfreespace" ) ) // free disk space dropped under a limit + { + m_freeDialog= new KDialogBase( + i18n( "Low Disk Space" ), + KDialogBase::Yes | KDialogBase::No, + KDialogBase::Yes, KDialogBase::No, + 0, "warningYesNo", false, true, + i18n( "Start Konqueror" ), KStdGuiItem::cancel()); + + TQString text = i18n( "You are running low on disk space on your home partition (currently %1% free), would you like to " + "run Konqueror to free some disk space and fix the problem?" ).arg( freeperc ); + bool checkboxResult = false; + KMessageBox::createKMessageBox(m_freeDialog, TQMessageBox::Warning, text, TQStringList(), + i18n("Do not ask again"), + &checkboxResult, KMessageBox::Notify | KMessageBox::NoExec); + m_freeDialog->show(); + connect( m_freeDialog, TQT_SIGNAL( yesClicked() ), TQT_SLOT( slotFreeContinue() ) ); + connect( m_freeDialog, TQT_SIGNAL( noClicked() ), TQT_SLOT( slotFreeCancel() ) ); + } + } +} + +void MediaNotifier::slotFreeContinue() +{ + slotFreeFinished( KMessageBox::Continue ); +} + +void MediaNotifier::slotFreeCancel() +{ + slotFreeFinished( KMessageBox::Cancel ); +} + +void MediaNotifier::slotFreeFinished( KMessageBox::ButtonCode res ) +{ + TQCheckBox *checkbox = ::tqqt_cast<TQCheckBox*>( m_freeDialog->child( 0, TQCHECKBOX_OBJECT_NAME_STRING ) ); + if ( checkbox && checkbox->isChecked() ) + KMessageBox::saveDontShowAgainYesNo("dontagainfreespace", res); + m_freeDialog->delayedDestruct(); + m_freeDialog = 0; + + if ( res == KMessageBox::Continue ) // start Konqi + { + ( void ) new KRun( KURL::fromPathOrURL( TQDir::homeDirPath() ) ); + } + else // people don't want to be bothered, at least stop the timer; there's no way to save the dontshowagain entry in this case + m_freeTimer->stop(); +} + +#include "medianotifier.moc" diff --git a/tdeioslave/media/medianotifier/medianotifier.desktop b/tdeioslave/media/medianotifier/medianotifier.desktop new file mode 100644 index 000000000..8cbf6063a --- /dev/null +++ b/tdeioslave/media/medianotifier/medianotifier.desktop @@ -0,0 +1,122 @@ +[Desktop Entry] +Type=Service +ServiceTypes=KDEDModule +X-TDE-ModuleType=Library +X-TDE-Library=medianotifier +X-TDE-FactoryName=medianotifier +X-TDE-Kded-autoload=true +X-TDE-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/tdeioslave/media/medianotifier/medianotifier.h b/tdeioslave/media/medianotifier/medianotifier.h new file mode 100644 index 000000000..9e9a772bb --- /dev/null +++ b/tdeioslave/media/medianotifier/medianotifier.h @@ -0,0 +1,66 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tdefileitem.h> +#include <tdeio/job.h> +#include <kmessagebox.h> + +#include <tqstring.h> +#include <tqmap.h> + +class KDialogBase; + +class MediaNotifier: public KDEDModule +{ + Q_OBJECT + K_DCOP + +public: + MediaNotifier( const TQCString &name ); + virtual ~MediaNotifier(); + +k_dcop: + void onMediumChange( const TQString &name, bool allowNotification ); + +private slots: + void slotStatResult( TDEIO::Job *job ); + void checkFreeDiskSpace(); + void slotFreeFinished( KMessageBox::ButtonCode ); + void slotFreeContinue(); + void slotFreeCancel(); + +private: + bool autostart( const KFileItem &medium ); + void notify( KFileItem &medium ); + + bool execAutorun( const KFileItem &medium, const TQString &path, + const TQString &autorunFile ); + bool execAutoopen( const KFileItem &medium, const TQString &path, + const TQString &autoopenFile ); + + TQMap<TDEIO::Job*,bool> m_allowNotificationMap; + TQTimer * m_freeTimer; + KDialogBase * m_freeDialog; +}; +#endif + diff --git a/tdeioslave/media/medianotifier/notificationdialog.cpp b/tdeioslave/media/medianotifier/notificationdialog.cpp new file mode 100644 index 000000000..c74228198 --- /dev/null +++ b/tdeioslave/media/medianotifier/notificationdialog.cpp @@ -0,0 +1,147 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqlayout.h> + +#include <krun.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <tdeio/global.h> +#include <klistbox.h> +#include <tqlabel.h> +#include <tqcheckbox.h> +#include <tqpushbutton.h> + +#include "actionlistboxitem.h" +#include "notificationdialogview.h" + +NotificationDialog::NotificationDialog( KFileItem medium, NotifierSettings *settings, + TQWidget* parent, const char* name ) + : KDialogBase( parent, name, false, i18n( "Medium Detected" ), Ok|Cancel|User1, Ok, true), + m_medium(medium), m_settings( settings ) +{ + setCaption( TDEIO::decodeFileName(m_medium.name()) ); + clearWState( WState_Polished ); + + TQWidget *page = new TQWidget( this ); + setMainWidget(page); + TQVBoxLayout *topLayout = new TQVBoxLayout( 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( TQSize(400,400).expandedTo( minimumSizeHint() ) ); + + + m_actionWatcher = new KDirWatch(); + TQString services_dir + = locateLocal( "data", "konqueror/servicemenus", true ); + m_actionWatcher->addDir( services_dir ); + + setButtonText( User1, i18n("Configure...") ); + + connect( m_actionWatcher, TQT_SIGNAL( dirty( const TQString & ) ), + this, TQT_SLOT( slotActionsChanged( const TQString & ) ) ); + connect( this , TQT_SIGNAL( okClicked() ), + this, TQT_SLOT( slotOk() ) ); + connect( this, TQT_SIGNAL( user1Clicked() ), + this, TQT_SLOT( slotConfigure() ) ); + connect( m_view->actionsList, TQT_SIGNAL( doubleClicked ( TQListBoxItem*, const TQPoint & ) ), + this, TQT_SLOT( slotOk() ) ); + + connect( this, TQT_SIGNAL( finished() ), + this, TQT_SLOT( delayedDestruct() ) ); + + m_actionWatcher->startScan(); + TQPushButton * btn = actionButton( Ok ); + btn->setFocus(); +} + +NotificationDialog::~NotificationDialog() +{ + delete m_actionWatcher; + delete m_settings; +} + +void NotificationDialog::updateActionsListBox() +{ + m_view->actionsList->clear(); + + TQValueList<NotifierAction*> actions + = m_settings->actionsForMimetype( m_medium.mimetype() ); + + TQValueList<NotifierAction*>::iterator it = actions.begin(); + TQValueList<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 TQString &/*dir*/) +{ + m_settings->reload(); + updateActionsListBox(); +} + +void NotificationDialog::slotOk() +{ + TQListBoxItem *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); + + TQDialog::accept(); +} + +void NotificationDialog::slotConfigure() +{ + KRun::runCommand("tdecmshell media"); +} + +#include "notificationdialog.moc" diff --git a/tdeioslave/media/medianotifier/notificationdialog.h b/tdeioslave/media/medianotifier/notificationdialog.h new file mode 100644 index 000000000..01792c2a5 --- /dev/null +++ b/tdeioslave/media/medianotifier/notificationdialog.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tdefileitem.h> + +#include "notifiersettings.h" +#include "notifieraction.h" +#include "notificationdialogview.h" + +class NotificationDialog : public KDialogBase +{ + Q_OBJECT + +public: + NotificationDialog( KFileItem medium, NotifierSettings *settings, + TQWidget* parent = 0, const char* name = 0 ); + ~NotificationDialog(); + +private slots: + void slotOk(); + void slotConfigure(); + void slotActionsChanged(const TQString& dir); + +private: + void launchAction( NotifierAction *action ); + void updateActionsListBox(); + + KFileItem m_medium; + NotifierSettings *m_settings; + KDirWatch * m_actionWatcher; + NotificationDialogView *m_view; +}; + +#endif diff --git a/tdeioslave/media/medianotifier/notificationdialogview.ui b/tdeioslave/media/medianotifier/notificationdialogview.ui new file mode 100644 index 000000000..f7bd744be --- /dev/null +++ b/tdeioslave/media/medianotifier/notificationdialogview.ui @@ -0,0 +1,117 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>NotificationDialogView</class> +<widget class="TQWidget"> + <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="TQLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="spacing"> + <number>20</number> + </property> + <widget class="TQLabel"> + <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="TQLabel"> + <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="TQLabel"> + <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="TQCheckBox"> + <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/tdeioslave/media/mimetypes/CMakeLists.txt b/tdeioslave/media/mimetypes/CMakeLists.txt new file mode 100644 index 000000000..5b1946c2d --- /dev/null +++ b/tdeioslave/media/mimetypes/CMakeLists.txt @@ -0,0 +1,35 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +##### other data ################################ + +install( FILES + floppy_mounted.desktop floppy_unmounted.desktop + floppy5_unmounted.desktop floppy5_mounted.desktop + zip_mounted.desktop zip_unmounted.desktop + hdd_mounted.desktop hdd_unmounted.desktop + hdd_mounted_decrypted.desktop hdd_unmounted_encrypted.desktop + hdd_unmounted_decrypted.desktop removable_mounted.desktop + removable_unmounted.desktop removable_mounted_decrypted.desktop + removable_unmounted_encrypted.desktop removable_unmounted_decrypted.desktop + cdrom_mounted.desktop cdrom_unmounted.desktop cdrom_mounted_decrypted.desktop + cdrom_unmounted_encrypted.desktop cdrom_unmounted_decrypted.desktop + dvd_mounted.desktop dvd_unmounted.desktop dvd_mounted_decrypted.desktop + dvd_unmounted_encrypted.desktop dvd_unmounted_decrypted.desktop + cdwriter_mounted.desktop cdwriter_unmounted.desktop + cdwriter_mounted_decrypted.desktop cdwriter_unmounted_encrypted.desktop + cdwriter_unmounted_decrypted.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 mydocuments.desktop mycomputer.desktop + mynetworkplaces.desktop printers.desktop trash.desktop webbrowser.desktop + DESTINATION ${MIME_INSTALL_DIR}/media ) diff --git a/tdeioslave/media/mimetypes/Makefile.am b/tdeioslave/media/mimetypes/Makefile.am new file mode 100644 index 000000000..7b821303b --- /dev/null +++ b/tdeioslave/media/mimetypes/Makefile.am @@ -0,0 +1,33 @@ +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 \ + hdd_mounted_decrypted.desktop hdd_unmounted_encrypted.desktop \ + hdd_unmounted_decrypted.desktop \ + removable_mounted.desktop removable_unmounted.desktop \ + removable_mounted_decrypted.desktop removable_unmounted_encrypted.desktop \ + removable_unmounted_decrypted.desktop \ + cdrom_mounted.desktop cdrom_unmounted.desktop \ + cdrom_mounted_decrypted.desktop cdrom_unmounted_encrypted.desktop \ + cdrom_unmounted_decrypted.desktop \ + dvd_mounted.desktop dvd_unmounted.desktop \ + dvd_mounted_decrypted.desktop dvd_unmounted_encrypted.desktop \ + dvd_unmounted_decrypted.desktop \ + cdwriter_mounted.desktop cdwriter_unmounted.desktop \ + cdwriter_mounted_decrypted.desktop cdwriter_unmounted_encrypted.desktop \ + cdwriter_unmounted_decrypted.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 \ + mydocuments.desktop mycomputer.desktop mynetworkplaces.desktop printers.desktop trash.desktop webbrowser.desktop + + +EXTRA_DIST = $(mimetype_DATA) diff --git a/tdeioslave/media/mimetypes/audiocd.desktop b/tdeioslave/media/mimetypes/audiocd.desktop new file mode 100644 index 000000000..ec53de33d --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory + diff --git a/tdeioslave/media/mimetypes/blankcd.desktop b/tdeioslave/media/mimetypes/blankcd.desktop new file mode 100644 index 000000000..723ade236 --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mimetypes/blankdvd.desktop b/tdeioslave/media/mimetypes/blankdvd.desktop new file mode 100644 index 000000000..f5f06c9bc --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mimetypes/camera_mounted.desktop b/tdeioslave/media/mimetypes/camera_mounted.desktop new file mode 100644 index 000000000..1383e998b --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory + diff --git a/tdeioslave/media/mimetypes/camera_unmounted.desktop b/tdeioslave/media/mimetypes/camera_unmounted.desktop new file mode 100644 index 000000000..687bc1af4 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory + diff --git a/tdeioslave/media/mimetypes/cdrom_mounted.desktop b/tdeioslave/media/mimetypes/cdrom_mounted.desktop new file mode 100644 index 000000000..427951b57 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdrom_mounted_decrypted.desktop b/tdeioslave/media/mimetypes/cdrom_mounted_decrypted.desktop new file mode 100644 index 000000000..d8bd4f199 --- /dev/null +++ b/tdeioslave/media/mimetypes/cdrom_mounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=cdrom_mount_decrypt +Type=MimeType +MimeType=media/cdrom_mounted_decrypted +Comment=Mounted Decrypted CD-ROM +Comment[de]=Eingebundene, entschlüsselte CD-ROM +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdrom_unmounted.desktop b/tdeioslave/media/mimetypes/cdrom_unmounted.desktop new file mode 100644 index 000000000..3d35eef32 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdrom_unmounted_decrypted.desktop b/tdeioslave/media/mimetypes/cdrom_unmounted_decrypted.desktop new file mode 100644 index 000000000..c53abbc82 --- /dev/null +++ b/tdeioslave/media/mimetypes/cdrom_unmounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=cdrom_unmount_decrypt +Type=MimeType +MimeType=media/cdrom_unmounted_decrypted +Comment=Unmounted Decrypted CD-ROM +Comment[de]=Nicht eingebundene, entschlüsselte CD-ROM +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdrom_unmounted_encrypted.desktop b/tdeioslave/media/mimetypes/cdrom_unmounted_encrypted.desktop new file mode 100644 index 000000000..f11e43807 --- /dev/null +++ b/tdeioslave/media/mimetypes/cdrom_unmounted_encrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=cdrom_unmount_encrypt +Type=MimeType +MimeType=media/cdrom_unmounted_encrypted +Comment=Unmounted Encrypted CD-ROM +Comment[de]=Nicht eingebundene, verschlüsselte CD-ROM +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdwriter_mounted.desktop b/tdeioslave/media/mimetypes/cdwriter_mounted.desktop new file mode 100644 index 000000000..88739824f --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdwriter_mounted_decrypted.desktop b/tdeioslave/media/mimetypes/cdwriter_mounted_decrypted.desktop new file mode 100644 index 000000000..e3e8877d1 --- /dev/null +++ b/tdeioslave/media/mimetypes/cdwriter_mounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=cdwriter_mount_decrypt +Type=MimeType +MimeType=media/cdwriter_mounted_decrypted +Comment=Mounted Decrypted CD Writer +Comment[de]=Eingebundener, entschlüsselter CD-Brenner +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdwriter_unmounted.desktop b/tdeioslave/media/mimetypes/cdwriter_unmounted.desktop new file mode 100644 index 000000000..357e197d4 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdwriter_unmounted_decrypted.desktop b/tdeioslave/media/mimetypes/cdwriter_unmounted_decrypted.desktop new file mode 100644 index 000000000..e81c0b857 --- /dev/null +++ b/tdeioslave/media/mimetypes/cdwriter_unmounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=cdwriter_unmount_decrypt +Type=MimeType +MimeType=media/cdwriter_unmounted_decrypted +Comment=Unmounted Decrypted CD Writer +Comment[de]=Nicht eingebundener, entschlüsselter CD-Brenner +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/cdwriter_unmounted_encrypted.desktop b/tdeioslave/media/mimetypes/cdwriter_unmounted_encrypted.desktop new file mode 100644 index 000000000..5eb9f3396 --- /dev/null +++ b/tdeioslave/media/mimetypes/cdwriter_unmounted_encrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=cdwriter_unmount_encrypt +Type=MimeType +MimeType=media/cdwriter_unmounted_encrypted +Comment=Unmounted Encrypted CD Writer +Comment[de]=Nicht eingebundener, entschlüsselter CD-Brenner +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/dvd_mounted.desktop b/tdeioslave/media/mimetypes/dvd_mounted.desktop new file mode 100644 index 000000000..7e0ff307b --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/dvd_mounted_decrypted.desktop b/tdeioslave/media/mimetypes/dvd_mounted_decrypted.desktop new file mode 100644 index 000000000..ac5f34db9 --- /dev/null +++ b/tdeioslave/media/mimetypes/dvd_mounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=dvd_mount_decrypt +Type=MimeType +MimeType=media/dvd_mounted_decrypted +Comment=Mounted Decrypted DVD +Comment[de]=Eingebundene, entschlüsselte DVD +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/dvd_unmounted.desktop b/tdeioslave/media/mimetypes/dvd_unmounted.desktop new file mode 100644 index 000000000..4b6785e34 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/dvd_unmounted_decrypted.desktop b/tdeioslave/media/mimetypes/dvd_unmounted_decrypted.desktop new file mode 100644 index 000000000..a34f174ea --- /dev/null +++ b/tdeioslave/media/mimetypes/dvd_unmounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=dvd_unmount_decrypt +Type=MimeType +MimeType=media/dvd_unmounted_decrypted +Comment=Unmounted Decrypted DVD +Comment[de]=Nicht eingebundene, entschlüsselte DVD +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/dvd_unmounted_encrypted.desktop b/tdeioslave/media/mimetypes/dvd_unmounted_encrypted.desktop new file mode 100644 index 000000000..e6eba099d --- /dev/null +++ b/tdeioslave/media/mimetypes/dvd_unmounted_encrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=dvd_unmount_encrypt +Type=MimeType +MimeType=media/dvd_unmounted_encrypted +Comment=Unmounted Encrypted DVD +Comment[de]=Nicht eingebundene, verschlüsselte DVD +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/dvdvideo.desktop b/tdeioslave/media/mimetypes/dvdvideo.desktop new file mode 100644 index 000000000..8a7d44c52 --- /dev/null +++ b/tdeioslave/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-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/floppy5_mounted.desktop b/tdeioslave/media/mimetypes/floppy5_mounted.desktop new file mode 100644 index 000000000..8439720a2 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/floppy5_unmounted.desktop b/tdeioslave/media/mimetypes/floppy5_unmounted.desktop new file mode 100644 index 000000000..2e23cd6eb --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/floppy_mounted.desktop b/tdeioslave/media/mimetypes/floppy_mounted.desktop new file mode 100644 index 000000000..ee0c1075c --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/floppy_unmounted.desktop b/tdeioslave/media/mimetypes/floppy_unmounted.desktop new file mode 100644 index 000000000..c413cbc03 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/gphoto2camera.desktop b/tdeioslave/media/mimetypes/gphoto2camera.desktop new file mode 100644 index 000000000..a84f3baf5 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory + diff --git a/tdeioslave/media/mimetypes/hdd_mounted.desktop b/tdeioslave/media/mimetypes/hdd_mounted.desktop new file mode 100644 index 000000000..c442b554d --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/hdd_mounted_decrypted.desktop b/tdeioslave/media/mimetypes/hdd_mounted_decrypted.desktop new file mode 100644 index 000000000..4330b82be --- /dev/null +++ b/tdeioslave/media/mimetypes/hdd_mounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=hdd_mount_decrypt +Type=MimeType +MimeType=media/hdd_mounted_decrypted +Comment=Mounted Decrypted Hard Disk Volume +Comment[de]=Eingebundene, entschlüsselte Festplattenpartition +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/hdd_unmounted.desktop b/tdeioslave/media/mimetypes/hdd_unmounted.desktop new file mode 100644 index 000000000..cee21730f --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/hdd_unmounted_decrypted.desktop b/tdeioslave/media/mimetypes/hdd_unmounted_decrypted.desktop new file mode 100644 index 000000000..cd2e387ce --- /dev/null +++ b/tdeioslave/media/mimetypes/hdd_unmounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=hdd_unmount_decrypt +Type=MimeType +MimeType=media/hdd_unmounted_decrypted +Comment=Unmounted Decrypted Hard Disk Volume +Comment[de]=Nicht eingebundene, entschlüsselte Festplattenpartition +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/hdd_unmounted_encrypted.desktop b/tdeioslave/media/mimetypes/hdd_unmounted_encrypted.desktop new file mode 100644 index 000000000..ecc744974 --- /dev/null +++ b/tdeioslave/media/mimetypes/hdd_unmounted_encrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=hdd_unmount_encrypt +Type=MimeType +MimeType=media/hdd_unmounted_encrypted +Comment=Unmounted Encrypted Hard Disk Volume +Comment[de]=Nicht eingebundene, verschlüsselte Festplattenpartition +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/mycomputer.desktop b/tdeioslave/media/mimetypes/mycomputer.desktop new file mode 100644 index 000000000..886d95aed --- /dev/null +++ b/tdeioslave/media/mimetypes/mycomputer.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Icon=system +Type=MimeType +MimeType=media/builtin-mycomputer +Comment=My Computer +Patterns=My_Computer + +X-TDE-AutoEmbed=false diff --git a/tdeioslave/media/mimetypes/mydocuments.desktop b/tdeioslave/media/mimetypes/mydocuments.desktop new file mode 100644 index 000000000..1e3cf8d03 --- /dev/null +++ b/tdeioslave/media/mimetypes/mydocuments.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Icon=folder_wordprocessing +Type=MimeType +MimeType=media/builtin-mydocuments +Comment=My Documents +Patterns=My_Documents + +X-TDE-AutoEmbed=false diff --git a/tdeioslave/media/mimetypes/mynetworkplaces.desktop b/tdeioslave/media/mimetypes/mynetworkplaces.desktop new file mode 100644 index 000000000..30294890c --- /dev/null +++ b/tdeioslave/media/mimetypes/mynetworkplaces.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Icon=network +Type=MimeType +MimeType=media/builtin-mynetworkplaces +Comment=My Network Places +Patterns=My_Network_Places + +X-TDE-AutoEmbed=false diff --git a/tdeioslave/media/mimetypes/nfs_mounted.desktop b/tdeioslave/media/mimetypes/nfs_mounted.desktop new file mode 100644 index 000000000..3de6c0303 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/nfs_unmounted.desktop b/tdeioslave/media/mimetypes/nfs_unmounted.desktop new file mode 100644 index 000000000..d2bb456b6 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/printers.desktop b/tdeioslave/media/mimetypes/printers.desktop new file mode 100644 index 000000000..cb808e522 --- /dev/null +++ b/tdeioslave/media/mimetypes/printers.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Icon=kjobviewer +Type=MimeType +MimeType=media/builtin-printers +Comment=Printers +Patterns=Printers + +X-TDE-AutoEmbed=false diff --git a/tdeioslave/media/mimetypes/removable_mounted.desktop b/tdeioslave/media/mimetypes/removable_mounted.desktop new file mode 100644 index 000000000..8371af6ac --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/removable_mounted_decrypted.desktop b/tdeioslave/media/mimetypes/removable_mounted_decrypted.desktop new file mode 100644 index 000000000..88298712e --- /dev/null +++ b/tdeioslave/media/mimetypes/removable_mounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=usbpendrive_mount_decrypt +Type=MimeType +MimeType=media/removable_mounted_decrypted +Comment=Mounted Decrypted Removable Medium +Comment[de]=Eingebundenes, entschlüsseltes Wechsellaufwerk +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/removable_unmounted.desktop b/tdeioslave/media/mimetypes/removable_unmounted.desktop new file mode 100644 index 000000000..0fe53d898 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/removable_unmounted_decrypted.desktop b/tdeioslave/media/mimetypes/removable_unmounted_decrypted.desktop new file mode 100644 index 000000000..3af682050 --- /dev/null +++ b/tdeioslave/media/mimetypes/removable_unmounted_decrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=usbpendrive_unmount_decrypt +Type=MimeType +MimeType=media/removable_unmounted_decrypted +Comment=Unmounted Decrypted Removable Medium +Comment[de]=Nicht eingebundenes, entschlüsseltes Wechsellaufwerk +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/removable_unmounted_encrypted.desktop b/tdeioslave/media/mimetypes/removable_unmounted_encrypted.desktop new file mode 100644 index 000000000..f904eb9ec --- /dev/null +++ b/tdeioslave/media/mimetypes/removable_unmounted_encrypted.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Encoding=UTF-8 +Icon=usbpendrive_unmount_encrypt +Type=MimeType +MimeType=media/removable_unmounted_encrypted +Comment=Unmounted Encrypted Removable Medium +Comment[de]=Nicht eingebundenes, verschlüsseltes Wechsellaufwerk +Patterns= + +X-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/smb_mounted.desktop b/tdeioslave/media/mimetypes/smb_mounted.desktop new file mode 100644 index 000000000..16ee7c815 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/smb_unmounted.desktop b/tdeioslave/media/mimetypes/smb_unmounted.desktop new file mode 100644 index 000000000..98adf4332 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/svcd.desktop b/tdeioslave/media/mimetypes/svcd.desktop new file mode 100644 index 000000000..68c965a6f --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mimetypes/trash.desktop b/tdeioslave/media/mimetypes/trash.desktop new file mode 100644 index 000000000..340bdccb7 --- /dev/null +++ b/tdeioslave/media/mimetypes/trash.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Icon=trashcan_full +EmptyIcon=trashcan_empty +Type=MimeType +MimeType=media/builtin-trash +Comment=Trash +Patterns=Trash + +X-TDE-AutoEmbed=false diff --git a/tdeioslave/media/mimetypes/vcd.desktop b/tdeioslave/media/mimetypes/vcd.desktop new file mode 100644 index 000000000..dae4ddcaf --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/mimetypes/webbrowser.desktop b/tdeioslave/media/mimetypes/webbrowser.desktop new file mode 100644 index 000000000..0134b05ef --- /dev/null +++ b/tdeioslave/media/mimetypes/webbrowser.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Icon=konqueror +Type=MimeType +MimeType=media/builtin-webbrowser +Comment=Web Browser +Patterns=Web_Browser + +X-TDE-AutoEmbed=false diff --git a/tdeioslave/media/mimetypes/zip_mounted.desktop b/tdeioslave/media/mimetypes/zip_mounted.desktop new file mode 100644 index 000000000..ce82fe968 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mimetypes/zip_unmounted.desktop b/tdeioslave/media/mimetypes/zip_unmounted.desktop new file mode 100644 index 000000000..eaa35577f --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/media/mounthelper/CMakeLists.txt b/tdeioslave/media/mounthelper/CMakeLists.txt new file mode 100644 index 000000000..ea06278a8 --- /dev/null +++ b/tdeioslave/media/mounthelper/CMakeLists.txt @@ -0,0 +1,36 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/tdeioslave/media/libmediacommon + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### kio_media_mounthelper (executable) ######## + +set( target kio_media_mounthelper ) + +set( ${target}_SRCS + kio_media_mounthelper.cpp decryptdialog.ui dialog.cpp +) + +tde_add_executable( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK mediacommon-static tdeio-shared + DESTINATION ${BIN_INSTALL_DIR} +) diff --git a/tdeioslave/media/mounthelper/Makefile.am b/tdeioslave/media/mounthelper/Makefile.am new file mode 100644 index 000000000..934559d40 --- /dev/null +++ b/tdeioslave/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 decryptdialog.ui dialog.cpp + +kio_media_mounthelper_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor +kio_media_mounthelper_LDADD = ../libmediacommon/libmediacommon.la $(LIB_KIO) $(LIB_TDEUI) + +METASOURCES = AUTO + diff --git a/tdeioslave/media/mounthelper/decryptdialog.ui b/tdeioslave/media/mounthelper/decryptdialog.ui new file mode 100644 index 000000000..939f0a36a --- /dev/null +++ b/tdeioslave/media/mounthelper/decryptdialog.ui @@ -0,0 +1,201 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>DecryptDialog</class> +<widget class="TQWidget"> + <property name="name"> + <cstring>DecryptDialog</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>207</width> + <height>172</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Decrypting Storage Device</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <property name="name"> + <cstring>encryptedIcon</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>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>AlignTop</set> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>31</width> + <height>41</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="TQLabel"> + <property name="name"> + <cstring>descLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string><p><b>%1</b> is an encrypted storage device.</p> +<p>Please enter the password to decrypt the storage device.</p></string> + </property> + <property name="alignment"> + <set>WordBreak|AlignTop</set> + </property> + </widget> + </hbox> + </widget> + <widget class="TQLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>&Password:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>passwordEdit</cstring> + </property> + </widget> + <widget class="TQLineEdit"> + <property name="name"> + <cstring>passwordEdit</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="echoMode"> + <enum>Password</enum> + </property> + </widget> + </hbox> + </widget> + <widget class="TQGroupBox" row="2" column="0"> + <property name="name"> + <cstring>errorBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Error</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel" row="0" column="0"> + <property name="name"> + <cstring>errorLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>WordBreak|AlignTop</set> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/tdeioslave/media/mounthelper/dialog.cpp b/tdeioslave/media/mounthelper/dialog.cpp new file mode 100644 index 000000000..40e99a84d --- /dev/null +++ b/tdeioslave/media/mounthelper/dialog.cpp @@ -0,0 +1,68 @@ +/* This file is part of the KDE project + * Copyright (C) 2007 Jan Klötzke <jan kloetzke at freenet de> + * + * Based on kryptomedia- Another KDE cryto media application. + * Copyright (C) 2006 Daniel Gollub <[email protected]> + * + * 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 "dialog.h" + +Dialog::Dialog(TQString url, TQString iconName) : + KDialogBase(NULL, "Dialog", true, "Decrypt Storage Device", (Cancel|User1), User1, false, KGuiItem(i18n("Decrypt"), "decrypted" )) +{ + decryptDialog = new DecryptDialog(this); + + decryptDialog->errorBox->hide(); + decryptDialog->descLabel->setText(decryptDialog->descLabel->text().arg(url)); + decryptDialog->descLabel->adjustSize(); + decryptDialog->adjustSize(); + + enableButton( User1, false ); + + TQPixmap pixmap = TDEGlobal::iconLoader()->loadIcon(iconName, KIcon::NoGroup, KIcon::SizeLarge); + decryptDialog->encryptedIcon->setPixmap( pixmap ); + + connect(decryptDialog->passwordEdit, TQT_SIGNAL (textChanged(const TQString &)), this, TQT_SLOT (slotPasswordChanged(const TQString &))); + + setMainWidget(decryptDialog); +} + +Dialog::~Dialog() +{ + delete decryptDialog; +} + +TQString Dialog::getPassword() +{ + return decryptDialog->passwordEdit->text(); +} + +void Dialog::slotDialogError(TQString errorMsg) +{ + kdDebug() << __func__ << "(" << errorMsg << " )" << endl; + + decryptDialog->errorLabel->setText(TQString("<b>%1</b>").arg(errorMsg)); + decryptDialog->errorBox->show(); +} + +void Dialog::slotPasswordChanged(const TQString &text) +{ + enableButton( User1, !text.isEmpty() ); +} + +#include "dialog.moc" diff --git a/tdeioslave/media/mounthelper/dialog.h b/tdeioslave/media/mounthelper/dialog.h new file mode 100644 index 000000000..dc1d552d8 --- /dev/null +++ b/tdeioslave/media/mounthelper/dialog.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + * Copyright (C) 2007 Jan Klötzke <jan kloetzke at freenet de> + * + * Based on kryptomedia- Another KDE cryto media application. + * Copyright (C) 2006 Daniel Gollub <[email protected]> + * + * 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 DIALOG_H_ +#define DIALOG_H_ + +#include <kmessagebox.h> +#include <klocale.h> +#include <tdeconfig.h> +#include <kdebug.h> +#include <kdialogbase.h> +#include <kiconloader.h> + +#include <tqlineedit.h> +#include <tqlabel.h> +#include <tqgroupbox.h> + +#include "decryptdialog.h" + +class KryptoMedia; + +class Dialog : public KDialogBase +{ + +Q_OBJECT + +public: + Dialog(TQString url, TQString iconName); + ~Dialog(); + + TQString getPassword(); + +public slots: + void slotDialogError(TQString errorMsg); + void slotPasswordChanged(const TQString &text); + +private: + DecryptDialog *decryptDialog; +}; + +#endif // DIALOG_H_ + diff --git a/tdeioslave/media/mounthelper/kio_media_mounthelper.cpp b/tdeioslave/media/mounthelper/kio_media_mounthelper.cpp new file mode 100644 index 000000000..f321293d6 --- /dev/null +++ b/tdeioslave/media/mounthelper/kio_media_mounthelper.cpp @@ -0,0 +1,282 @@ +/* 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 <[email protected]> + + 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 <tqtimer.h> +#include <stdlib.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kprocess.h> +#include <kstartupinfo.h> +#include <kmimetype.h> + +#include "dialog.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 TDE mediamanager is not running.")+"\n"; + return Medium(TQString::null, TQString::null, TQString::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(TQString::null, TQString::null, TQString::null); + } + return Medium::create(reply); + } else { + return medium; + } +} + +MountHelper::MountHelper() : TDEApplication() +{ + TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs(); + + m_errorStr = TQString::null; + + 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()); + TQTimer::singleShot(0, this, TQT_SLOT(error()) ); + return; + } + + if ( !medium.isMountable() && !args->isSet("e") && !args->isSet("s")) + { + m_errorStr = i18n("%1 is not a mountable media.").arg(url.prettyURL()); + TQTimer::singleShot(0, this, TQT_SLOT(error()) ); + return; + } + + TQString device = medium.deviceNode(); + TQString mount_point = medium.mountPoint(); + + m_isCdrom = medium.mimeType().find("dvd")!=-1 + || medium.mimeType().find("cd")!=-1; + + if (args->isSet("d")) + { + if (!medium.isEncrypted()) + { + m_errorStr = i18n("%1 is not an encrypted media.").arg(url.prettyURL()); + TQTimer::singleShot(0, this, TQT_SLOT(error()) ); + return; + } + if (!medium.needDecryption()) + { + m_errorStr = i18n("%1 is already decrypted.").arg(url.prettyURL()); + TQTimer::singleShot(0, this, TQT_SLOT(error()) ); + return; + } + + TQString iconName = medium.iconName(); + if (iconName.isEmpty()) + { + TQString mime = medium.mimeType(); + iconName = KMimeType::mimeType(mime)->icon(mime, false); + } + + m_mediumId = medium.id(); + dialog = new Dialog(url.prettyURL(), iconName); + dialog->show(); + + connect(dialog, TQT_SIGNAL (user1Clicked()), this, TQT_SLOT (slotSendPassword())); + connect(dialog, TQT_SIGNAL (cancelClicked()), this, TQT_SLOT (slotCancel())); + connect(this, TQT_SIGNAL (signalPasswordError(TQString)), dialog, TQT_SLOT (slotDialogError(TQString))); + } + else 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")) + { + DCOPRef mediamanager("kded", "mediamanager"); + + /* + * 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()) + { + DCOPReply reply = mediamanager.call( "unmount", medium.id()); + if (reply.isValid()) + reply.get(m_errorStr); + } + + /* If this is a decrypted volume and there is no error yet + * we try to teardown the decryption */ + if (m_errorStr.isNull() && medium.isEncrypted() && !medium.clearDeviceUdi().isNull()) + { + DCOPReply reply = mediamanager.call( "undecrypt", medium.id()); + if (reply.isValid()) + reply.get(m_errorStr); + } + + if (m_errorStr.isNull()) + invokeEject(device, true); + else + error(); + } + 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 TQString &device, bool quiet) +{ + TDEProcess *proc = new TDEProcess(TQT_TQOBJECT(this)); + *proc << "kdeeject"; + if (quiet) + { + *proc << "-q"; + } + *proc << device; + connect( proc, TQT_SIGNAL(processExited(TDEProcess *)), + this, TQT_SLOT( ejectFinished(TDEProcess *) ) ); + proc->start(); +} + +void MountHelper::ejectFinished(TDEProcess* 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"); + } +//X Comment this because the error is useless as long as the unmount is successful. +//X TQTimer::singleShot(0, this, TQT_SLOT(error())); + ::exit(0); + } +} + +void MountHelper::error() +{ + TQString prettyErrorString = m_errorStr; + if (m_errorStr.contains("<") && m_errorStr.contains(">")) { + if (!m_errorStr.contains("<qt>")) { + prettyErrorString = TQString("<qt>%1</qt>").arg(m_errorStr); + } + } + KMessageBox::error(0, prettyErrorString); + ::exit(1); +} + +void MountHelper::slotSendPassword() +{ + DCOPRef mediamanager("kded", "mediamanager"); + + DCOPReply reply = mediamanager.call( "decrypt", m_mediumId, dialog->getPassword() ); + if (!reply.isValid()) { + m_errorStr = i18n("The TDE mediamanager is not running."); + error(); + } else { + TQString errorMsg = reply; + if (errorMsg.isNull()) { + exit(0); + } else { + emit signalPasswordError(errorMsg); + } + } +} + +void MountHelper::slotCancel() +{ + exit(0); +} + +static KCmdLineOptions options[] = +{ + { "d", I18N_NOOP("Decrypt given URL"), 0 }, + { "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) +{ + TDECmdLineArgs::init(argc, argv, "kio_media_mounthelper", + "kio_media_mounthelper", "kio_media_mounthelper", + "0.1"); + + TDECmdLineArgs::addCmdLineOptions( options ); + TDEGlobal::locale()->setMainCatalogue("kio_media"); + TDEApplication::addCmdLineOptions(); + + if (TDECmdLineArgs::parsedArgs()->count()==0) TDECmdLineArgs::usage(); + TDEApplication *app = new MountHelper(); + + KStartupInfo::appStarted(); + app->dcopClient()->attach(); + return app->exec(); +} + +#include "kio_media_mounthelper.moc" diff --git a/tdeioslave/media/mounthelper/kio_media_mounthelper.h b/tdeioslave/media/mounthelper/kio_media_mounthelper.h new file mode 100644 index 000000000..50ea88b3b --- /dev/null +++ b/tdeioslave/media/mounthelper/kio_media_mounthelper.h @@ -0,0 +1,57 @@ +/* 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 <[email protected]> + + 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 <tqstring.h> +#include <tdeio/job.h> + +#include "medium.h" + +class Dialog; + +class MountHelper : public TDEApplication +{ + Q_OBJECT +public: + MountHelper(); + +private: + const Medium findMedium(const KURL &url); + void invokeEject(const TQString &device, bool quiet=false); + TQString m_errorStr; + bool m_isCdrom; + TQString m_mediumId; + Dialog *dialog; + +private slots: + void slotSendPassword(); + void slotCancel(); + void ejectFinished(TDEProcess* proc); + void error(); + +signals: + void signalPasswordError(TQString errorMsg); +}; + +#endif diff --git a/tdeioslave/media/propsdlgplugin/CMakeLists.txt b/tdeioslave/media/propsdlgplugin/CMakeLists.txt new file mode 100644 index 000000000..f029b52ab --- /dev/null +++ b/tdeioslave/media/propsdlgplugin/CMakeLists.txt @@ -0,0 +1,35 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES media_propsdlgplugin.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### media_propsdlgplugin (module) ############# + +tde_add_kpart( media_propsdlgplugin AUTOMOC + SOURCES + propertiespage.cpp propertiespagegui.ui propsdlgshareplugin.cpp + LINK mediacommon-static tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/media/propsdlgplugin/Makefile.am b/tdeioslave/media/propsdlgplugin/Makefile.am new file mode 100644 index 000000000..5e3d8af83 --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/propsdlgplugin/media_propsdlgplugin.desktop b/tdeioslave/media/propsdlgplugin/media_propsdlgplugin.desktop new file mode 100644 index 000000000..2b24348c5 --- /dev/null +++ b/tdeioslave/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-TDE-Library=media_propsdlgplugin +ServiceTypes=KPropsDlg/Plugin,media/audiocd,media/hdd_mounted,media/hdd_unmounted,media/hdd_mounted_decrypted,media/hdd_unmounted_decrypted,media/cdrom_mounted,media/cdrom_unmounted,media/cdrom_mounted_decrypted,media/cdrom_unmounted_decrypted,media/cdwriter_mounted,media/cdwriter_mounted_decrypted,media/nfs_mounted,media/cdwriter_unmounted,media/cdwriter_unmounted_decrypted,media/nfs_unmounted,media/removable_mounted,media/removable_mounted_decrypted,media/dvd_mounted,media/dvd_mounted_decrypted,media/removable_unmounted,media/removable_unmounted_decrypted,media/dvd_unmounted,media/dvd_unmounted_decrypted,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/tdeioslave/media/propsdlgplugin/propertiespage.cpp b/tdeioslave/media/propsdlgplugin/propertiespage.cpp new file mode 100644 index 000000000..33d481ef5 --- /dev/null +++ b/tdeioslave/media/propsdlgplugin/propertiespage.cpp @@ -0,0 +1,229 @@ +/* + Copyright (c) 2004 Jan Schaefer <[email protected]> + + 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 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 <tqcheckbox.h> +#include <tqtooltip.h> +#include <tqbuttongroup.h> +#include <tqlineedit.h> +#include <tqfileinfo.h> +#include <tqlabel.h> +#include <tqregexp.h> +#include <kpushbutton.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <tqcombobox.h> +#include <tqtimer.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(TQWidget* parent, const TQString &_id) + : PropertiesPageGUI(parent), id(_id) +{ + kdDebug() << "props page " << id << endl; + DCOPRef mediamanager("kded", "mediamanager"); + DCOPReply reply = mediamanager.call( "mountoptions", id); + + TQStringList list; + + if (reply.isValid()) + list = reply; + + if (list.size()) { + kdDebug() << "list " << list << endl; + + for (TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it) + { + TQString key = (*it).left((*it).find('=')); + TQString 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, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + if (!options.contains("quiet")) + option_quiet->hide(); + else + option_quiet->setChecked(options["quiet"] == "true"); + connect( option_quiet, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + if (!options.contains("sync")) + option_sync->hide(); + else + option_sync->setChecked(options["sync"] == "true"); + connect( option_sync, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + if (!options.contains("atime")) + option_atime->hide(); + else + option_atime->setChecked(options["atime"] == "true"); + connect( option_atime, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + if (!options.contains("flush")) + option_flush->hide(); + else + option_flush->setChecked(options["flush"] == "true"); + connect( option_flush, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + if (!options.contains("utf8")) + option_utf8->hide(); + else + option_utf8->setChecked(options["utf8"] == "true"); + connect( option_utf8, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + if (!options.contains("uid")) + option_uid->hide(); + else + option_uid->setChecked(options["uid"] == "true"); + connect( option_uid, TQT_SIGNAL( stateChanged(int) ), TQT_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, TQT_SIGNAL( activated(int) ), TQT_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, TQT_SIGNAL( activated(int) ), TQT_SIGNAL( changed() ) ); + } + + label_filesystem->setText(i18n("Filesystem: %1").arg(options["filesystem"])); + option_mountpoint->setText(options["mountpoint"]); + connect( option_mountpoint, TQT_SIGNAL( textChanged( const TQString &) ), TQT_SIGNAL( changed() ) ); + option_automount->setChecked(options["automount"] == "true"); + connect( option_automount, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + bool has_groupbox_specific = true; + if (!options.contains("journaling") && + !options.contains("shortname") && + !options.contains("uid") && + !options.contains("utf8") && + !options.contains("flush")) { + groupbox_specific->hide(); + has_groupbox_specific = false; + } + + // The order is important - we want groupboxes to hide automatically depending on use_defaults + // but don't want to emit changed() until user actually changes something. + connect( option_defaults, TQT_SIGNAL( toggled(bool) ), groupbox_generic, SLOT( setHidden(bool) ) ); + if (has_groupbox_specific) + connect( option_defaults, TQT_SIGNAL( toggled(bool) ), groupbox_specific, SLOT( setHidden(bool) ) ); + option_defaults->setChecked(options["use_defaults"] == "true"); + connect( option_defaults, TQT_SIGNAL( stateChanged(int) ), TQT_SIGNAL( changed() ) ); + + } else { + + groupbox_generic->setEnabled(false); + groupbox_specific->setEnabled(false); + label_filesystem->hide(); + } +} + +PropertiesPage::~PropertiesPage() +{ +} + +bool PropertiesPage::save() +{ + TQStringList result; + + if (options.contains("ro")) + result << TQString("ro=%1").arg(option_ro->isChecked() ? "true" : "false"); + + if (options.contains("quiet")) + result << TQString("quiet=%1").arg(option_quiet->isChecked() ? "true" : "false"); + + if (options.contains("sync")) + result << TQString("sync=%1").arg(option_sync->isChecked() ? "true" : "false"); + + if (options.contains("atime")) + result << TQString("atime=%1").arg(option_atime->isChecked() ? "true" : "false"); + + if (options.contains("flush")) + result << TQString("flush=%1").arg(option_flush->isChecked() ? "true" : "false"); + + if (options.contains("utf8")) + result << TQString("utf8=%1").arg(option_utf8->isChecked() ? "true" : "false"); + + if (options.contains("uid")) + result << TQString("uid=%1").arg(option_uid->isChecked() ? "true" : "false"); + + if (options.contains("shortname")) + result << TQString("shortname=%1").arg(short_names[option_shortname->currentItem()]); + + if (options.contains("journaling")) + result << TQString("journaling=%1").arg(journales[option_journaling->currentItem()]); + + TQString mp = option_mountpoint->text(); + if (!mp.startsWith("/media/")) + { + KMessageBox::sorry(this, i18n("Mountpoint has to be below /media")); + return false; + } + result << TQString("mountpoint=%1").arg(mp); + result << TQString("automount=%1").arg(option_automount->isChecked() ? "true" : "false"); + result << TQString("use_defaults=%1").arg(option_defaults->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/tdeioslave/media/propsdlgplugin/propertiespage.h b/tdeioslave/media/propsdlgplugin/propertiespage.h new file mode 100644 index 000000000..50a480719 --- /dev/null +++ b/tdeioslave/media/propsdlgplugin/propertiespage.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2004 Jan Schaefer <[email protected]> + + 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 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 PROPERTIESPAGE_H +#define PROPERTIESPAGE_H + +#include "propertiespagegui.h" +#include <tqmap.h> + +class TQCheckBox; +class Medium; + +class PropertiesPage : public PropertiesPageGUI +{ + Q_OBJECT + +public: + PropertiesPage(TQWidget* parent, const TQString &_id); + virtual ~PropertiesPage(); + + bool save(); + +protected: + + TQMap<TQString,TQString> options; + TQString id; + +}; + +#endif diff --git a/tdeioslave/media/propsdlgplugin/propertiespagegui.ui b/tdeioslave/media/propsdlgplugin/propertiespagegui.ui new file mode 100644 index 000000000..2a6ff899e --- /dev/null +++ b/tdeioslave/media/propsdlgplugin/propertiespagegui.ui @@ -0,0 +1,385 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>PropertiesPageGUI</class> +<widget class="TQWidget"> + <property name="name"> + <cstring>PropertiesPageGUI</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>219</width> + <height>446</height> + </rect> + </property> + <property name="caption"> + <string>PropertiesPageGUI</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQCheckBox"> + <property name="name"> + <cstring>option_defaults</cstring> + </property> + <property name="text"> + <string>Use default mount options</string> + </property> + </widget> + <widget class="TQGroupBox"> + <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="TQLayoutWidget"> + <property name="name"> + <cstring>layout15</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQCheckBox"> + <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="TQCheckBox"> + <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="TQCheckBox"> + <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="TQCheckBox"> + <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="TQLayoutWidget"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <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="TQLineEdit"> + <property name="name"> + <cstring>option_mountpoint</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="TQCheckBox"> + <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="TQGroupBox"> + <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="TQLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQCheckBox"> + <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="TQCheckBox"> + <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="TQCheckBox"> + <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="TQLayoutWidget"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <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="TQComboBox"> + <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="TQLayoutWidget"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <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="TQComboBox"> + <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="TQLabel"> + <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> +<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> +<Q_SIGNALS> + <signal>changed()</signal> +</Q_SIGNALS> +<Q_SLOTS> + <slot access="protected">changedSlot()</slot> +</Q_SLOTS> +<functions> + <function returnType="bool">hasChanged()</function> +</functions> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/tdeioslave/media/propsdlgplugin/propertiespagegui.ui.h b/tdeioslave/media/propsdlgplugin/propertiespagegui.ui.h new file mode 100644 index 000000000..45a9ca2d7 --- /dev/null +++ b/tdeioslave/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/tdeioslave/media/propsdlgplugin/propsdlgshareplugin.cpp b/tdeioslave/media/propsdlgplugin/propsdlgshareplugin.cpp new file mode 100644 index 000000000..1ceff0bc1 --- /dev/null +++ b/tdeioslave/media/propsdlgplugin/propsdlgshareplugin.cpp @@ -0,0 +1,101 @@ +/* + Copyright (c) 2004 Jan Schaefer <[email protected]> + + 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 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 <tqstring.h> +#include <tqvbox.h> +#include <tqlayout.h> +#include <tqlabel.h> +#include <tqtimer.h> + +#include <kgenericfactory.h> +#include <kdebug.h> +#include <kpushbutton.h> +#include <tdefileshare.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 TQStringList & ) + : 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; + + TQVBox* vbox = properties->addVBoxPage(i18n("&Mounting")); + + d = new Private(); + + d->page = new PropertiesPage(vbox, Medium::create(reply).id()); + connect(d->page, TQT_SIGNAL(changed()), + TQT_SLOT(slotChanged())); + + // TQTimer::singleShot(100, this, TQT_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/tdeioslave/media/propsdlgplugin/propsdlgshareplugin.h b/tdeioslave/media/propsdlgplugin/propsdlgshareplugin.h new file mode 100644 index 000000000..87b7d1e13 --- /dev/null +++ b/tdeioslave/media/propsdlgplugin/propsdlgshareplugin.h @@ -0,0 +1,44 @@ +/* + Copyright (c) 2004 Jan Schaefer <[email protected]> + + 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 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 KONQFILESHAREPLUGIN_H +#define KONQFILESHAREPLUGIN_H + +#include <kpropertiesdialog.h> + +class PropsDlgSharePlugin : public KPropsDlgPlugin +{ +Q_OBJECT +public: + PropsDlgSharePlugin( KPropertiesDialog *dlg, const char *, const TQStringList & ); + virtual ~PropsDlgSharePlugin(); + virtual void applyChanges(); + +public slots: + void slotChanged(); + +private: + class Private; + Private *d; + +}; + +#endif + + diff --git a/tdeioslave/media/services/CMakeLists.txt b/tdeioslave/media/services/CMakeLists.txt new file mode 100644 index 000000000..055bfdf48 --- /dev/null +++ b/tdeioslave/media/services/CMakeLists.txt @@ -0,0 +1,17 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +##### other data ################################ + +install( FILES + media_decrypt.desktop media_mount.desktop media_unmount.desktop + media_eject.desktop media_safelyremove.desktop + DESTINATION ${DATA_INSTALL_DIR}/konqueror/servicemenus ) diff --git a/tdeioslave/media/services/Makefile.am b/tdeioslave/media/services/Makefile.am new file mode 100644 index 000000000..c744934f2 --- /dev/null +++ b/tdeioslave/media/services/Makefile.am @@ -0,0 +1,3 @@ +servicesdir = $(kde_datadir)/konqueror/servicemenus +services_DATA = media_decrypt.desktop media_mount.desktop media_unmount.desktop media_eject.desktop media_safelyremove.desktop + diff --git a/tdeioslave/media/services/media_decrypt.desktop b/tdeioslave/media/services/media_decrypt.desktop new file mode 100644 index 000000000..dd8dadc39 --- /dev/null +++ b/tdeioslave/media/services/media_decrypt.desktop @@ -0,0 +1,9 @@ +[Desktop Action Decrypt] +Exec=kio_media_mounthelper -d %u +Icon=decrypted +Name=Decrypt +Name[de]=Entschlüsseln + +[Desktop Entry] +Actions=Decrypt +ServiceTypes=media/removable_unmounted_encrypted,media/hdd_unmounted_encrypted,media/cdrom_unmounted_encrypted,media/cdwriter_unmounted_encrypted,media/dvd_unmounted_encrypted diff --git a/tdeioslave/media/services/media_eject.desktop b/tdeioslave/media/services/media_eject.desktop new file mode 100644 index 000000000..af30d1088 --- /dev/null +++ b/tdeioslave/media/services/media_eject.desktop @@ -0,0 +1,90 @@ +[Desktop Entry] +ServiceTypes=media/cdrom_mounted,media/cdrom_unmounted,media/cdrom_mounted_decrypted,media/cdrom_unmounted_decrypted,media/cdrom_unmounted_encrypted,media/cdwriter_mounted,media/cdwriter_unmounted,media/cdwriter_mounted_decrypted,media/cdwriter_unmounted_decrypted,media/cdwriter_unmounted_encrypted,media/dvd_mounted,media/dvd_unmounted,media/dvd_mounted_decrypted,media/dvd_unmounted_decrypted,media/dvd_unmounted_encrypted,media/audiocd,media/blankcd,media/blankdvd,media/dvdvideo,media/svcd,media/vcd +Actions=MediaEject; +X-TDE-Priority=TopLevel +X-TDE-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/tdeioslave/media/services/media_mount.desktop b/tdeioslave/media/services/media_mount.desktop new file mode 100644 index 000000000..8011fe577 --- /dev/null +++ b/tdeioslave/media/services/media_mount.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +ServiceTypes=media/cdrom_unmounted,media/cdrom_unmounted_decrypted,media/cdwriter_unmounted,media/cdwriter_unmounted_decrypted,media/dvd_unmounted,media/dvd_unmounted_decrypted,media/floppy5_unmounted,media/floppy_unmounted,media/hdd_unmounted,media/hdd_unmounted_decrypted,media/nfs_unmounted,media/removable_unmounted,media/removable_unmounted_decrypted,media/smb_unmounted,media/zip_unmounted,media/camera_unmounted +Actions=MediaMount; +X-TDE-Priority=TopLevel +X-TDE-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/tdeioslave/media/services/media_safelyremove.desktop b/tdeioslave/media/services/media_safelyremove.desktop new file mode 100644 index 000000000..3dac85f15 --- /dev/null +++ b/tdeioslave/media/services/media_safelyremove.desktop @@ -0,0 +1,75 @@ +[Desktop Entry] +ServiceTypes=media/removable_mounted,media/removable_mounted_decrypted,media/removable_unmounted,media/removable_unmounted_decrypted,media/camera_mounted,media/camera_unmounted +Actions=MediaSafelyRemove; +X-TDE-Priority=TopLevel +X-TDE-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/tdeioslave/media/services/media_unmount.desktop b/tdeioslave/media/services/media_unmount.desktop new file mode 100644 index 000000000..04ced9d0d --- /dev/null +++ b/tdeioslave/media/services/media_unmount.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +ServiceTypes=media/cdrom_mounted,media/cdrom_mounted_decrypted,media/cdwriter_mounted,media/cdwriter_mounted_decrypted,media/dvd_mounted,media/dvd_mounted_decrypted,media/floppy5_mounted,media/floppy_mounted,media/hdd_mounted,media/hdd_mounted_decrypted,media/nfs_mounted,media/smb_mounted,media/zip_mounted,media/vcd,media/svcd,media/dvdvideo +Actions=MediaUnmount; +X-TDE-Priority=TopLevel +X-TDE-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/tdeioslave/media/tdecmodule/CMakeLists.txt b/tdeioslave/media/tdecmodule/CMakeLists.txt new file mode 100644 index 000000000..80128471e --- /dev/null +++ b/tdeioslave/media/tdecmodule/CMakeLists.txt @@ -0,0 +1,45 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR}/tdeioslave/media/libmediacommon + ${CMAKE_SOURCE_DIR}/tdeioslave/media/libmediacommon + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES media.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) + + +##### kcm_media (module) ######################## + +set( target kcm_media ) + +set( ${target}_SRCS + notifiermodule.cpp notifiermoduleview.ui serviceconfigdialog.cpp + serviceview.ui mimetypelistboxitem.cpp managermodule.cpp + managermoduleview.ui main.cpp +) + +tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK mediacommon-static tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/media/tdecmodule/Makefile.am b/tdeioslave/media/tdecmodule/Makefile.am new file mode 100644 index 000000000..72f9f385c --- /dev/null +++ b/tdeioslave/media/tdecmodule/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/tdeioslave/media/tdecmodule/main.cpp b/tdeioslave/media/tdecmodule/main.cpp new file mode 100644 index 000000000..9036583df --- /dev/null +++ b/tdeioslave/media/tdecmodule/main.cpp @@ -0,0 +1,104 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqtabwidget.h> +#include <tqlayout.h> + +#include <klocale.h> +#include <kaboutdata.h> +#include <kdialog.h> + +#include <kgenericfactory.h> + +#include "notifiermodule.h" +#include "managermodule.h" + + +typedef KGenericFactory<MediaModule, TQWidget> MediaFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_media, MediaFactory( "kcmmedia" ) ) + + +MediaModule::MediaModule( TQWidget *parent, const char *name, const TQStringList& ) + : TDECModule(MediaFactory::instance(), parent, name ) +{ + TDEGlobal::locale()->insertCatalogue("kio_media"); + TQVBoxLayout *layout = new TQVBoxLayout( this, 0, KDialog::spacingHint() ); + TQTabWidget *tab = new TQTabWidget( this ); + + layout->addWidget( tab ); + + + + m_notifierModule = new NotifierModule( this, "notifier" ); + tab->addTab( m_notifierModule, i18n( "&Notifications" ) ); + connect( m_notifierModule, TQT_SIGNAL( changed( bool ) ), + this, TQT_SLOT( moduleChanged( bool ) ) ); + + m_managerModule = new ManagerModule( this, "manager" ); + tab->addTab( m_managerModule, i18n( "&Advanced" ) ); + connect( m_managerModule, TQT_SIGNAL( changed( bool ) ), + this, TQT_SLOT( moduleChanged( bool ) ) ); + + + + TDEAboutData * about = new TDEAboutData("kcmmedia", + I18N_NOOP("Storage Media"), + "0.6", + I18N_NOOP("Storage Media Control Panel Module"), + TDEAboutData::License_GPL_V2, + I18N_NOOP("(c) 2005 Jean-Remy Falleri")); + about->addAuthor("Jean-Remy Falleri", I18N_NOOP("Maintainer"), "[email protected]"); + about->addAuthor("Kevin Ottens", 0, "ervin ipsquad net"); + about->addAuthor("Valentine Sinitsyn", 0, "[email protected]"); + 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 ); +} + +TQString MediaModule::quickHelp() const +{ + return i18n("FIXME : Write me..."); +} + +#include "main.moc" diff --git a/tdeioslave/media/tdecmodule/main.h b/tdeioslave/media/tdecmodule/main.h new file mode 100644 index 000000000..5052d58eb --- /dev/null +++ b/tdeioslave/media/tdecmodule/main.h @@ -0,0 +1,45 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tdecmodule.h> + +class MediaModule : public TDECModule +{ + Q_OBJECT + +public: + MediaModule( TQWidget *parent, const char *name, const TQStringList& ); + + virtual void load(); + virtual void save(); + virtual void defaults(); + virtual TQString quickHelp() const; + +protected slots: + void moduleChanged( bool state ); + +private: + TDECModule *m_notifierModule; + TDECModule *m_managerModule; +}; + +#endif diff --git a/tdeioslave/media/tdecmodule/managermodule.cpp b/tdeioslave/media/tdecmodule/managermodule.cpp new file mode 100644 index 000000000..3a44c446e --- /dev/null +++ b/tdeioslave/media/tdecmodule/managermodule.cpp @@ -0,0 +1,213 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 K�vin Ottens <ervin ipsquad net> + Copyright (c) 2006 Valentine Sinitsyn <[email protected]> + + 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 <tdeconfig.h> +#include <klocale.h> +#include <dcopref.h> +#include <tqbutton.h> +#include <tqcheckbox.h> +#include <tqcombobox.h> +#include <tqobjectlist.h> +#include <kdirnotify_stub.h> + +#include "managermoduleview.h" +#include "mediamanagersettings.h" + +ManagerModule::ManagerModule( TQWidget* parent, const char* name ) + : TDECModule( parent, name ) +{ + view = new ManagerModuleView( this ); + + addConfig( MediaManagerSettings::self(), view ); + +#ifndef COMPILE_HALBACKEND + TQString 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 + TQString 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 ); + + connect( view->option_automount, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_ro, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_quiet, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_flush, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_uid, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_utf8, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_sync, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_atime, SIGNAL( stateChanged(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_shortname, SIGNAL( activated(int) ), this, SLOT( emitChanged() ) ); + connect( view->option_journaling, SIGNAL( activated(int) ), this, SLOT( emitChanged() ) ); + + load(); +} + + +void ManagerModule::load() +{ + TDECModule::load(); + + TDEConfig config("mediamanagerrc"); + config.setGroup("DefaultOptions"); + + view->option_automount->setChecked( config.readBoolEntry("automount", false) ); + view->option_ro->setChecked( config.readBoolEntry("ro", false) ); + view->option_quiet->setChecked( config.readBoolEntry("quiet", false) ); + if (config.hasKey("flush")) + view->option_flush->setChecked( config.readBoolEntry("flush") ); + else + view->option_flush->setNoChange(); + view->option_uid->setChecked( config.readBoolEntry("uid", true) ); + view->option_utf8->setChecked( config.readBoolEntry("utf8", true) ); + if (config.hasKey("sync")) + view->option_sync->setChecked( config.readBoolEntry("sync") ); + else + view->option_sync->setNoChange(); + if (config.hasKey("atime")) + view->option_atime->setChecked( config.readBoolEntry("atime") ); + else + view->option_atime->setNoChange(); + + QString value; + + value = config.readEntry("shortname", "lower").lower(); + for (int i = 0; i < view->option_shortname->count(); i++) + if (view->option_shortname->text(i).lower() == value) view->option_shortname->setCurrentItem(i); + + value = config.readEntry("journaling", "ordered").lower(); + for (int i = 0; i < view->option_journaling->count(); i++) + if (view->option_journaling->text(i).lower() == value) view->option_journaling->setCurrentItem(i); + + rememberSettings(); +} + +void ManagerModule::save() +{ + TDECModule::save(); + + TDEConfig config("mediamanagerrc"); + config.setGroup("DefaultOptions"); + + config.writeEntry("automount", view->option_automount->isChecked()); + config.writeEntry("ro", view->option_ro->isChecked()); + config.writeEntry("quiet", view->option_quiet->isChecked()); + if (view->option_flush->state() == TQButton::NoChange) + config.deleteEntry("flush"); + else + config.writeEntry("flush", view->option_flush->isChecked()); + config.writeEntry("uid", view->option_uid->isChecked()); + config.writeEntry("utf8", view->option_utf8->isChecked()); + if (view->option_sync->state() == TQButton::NoChange) + config.deleteEntry("sync"); + else + config.writeEntry("sync", view->option_sync->isChecked()); + if (view->option_atime->state() == TQButton::NoChange) + config.deleteEntry("atime"); + else + config.writeEntry("atime", view->option_atime->isChecked()); + config.writeEntry("journaling", view->option_journaling->currentText().lower()); + config.writeEntry("shortname", view->option_shortname->currentText().lower()); + + rememberSettings(); + + //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:/" ); +} + +void ManagerModule::defaults() +{ + TDECModule::defaults(); + + view->option_automount->setChecked(false); + view->option_ro->setChecked(false); + view->option_quiet->setChecked(false); + view->option_flush->setNoChange(); + view->option_uid->setChecked(true); + view->option_utf8->setChecked(true); + view->option_sync->setNoChange(); + view->option_atime->setNoChange(); + view->option_journaling->setCurrentItem(1); + view->option_shortname->setCurrentItem(0); +} + +void ManagerModule::rememberSettings() +{ + TQObjectList *options = view->queryList(0, "^option_"); + TQObject *current = 0; + TQObjectListIterator it(*options); + + settings.clear(); + while ( (current = it.current()) != 0 ) { + if (current->isA("TQCheckBox")) + settings[current] = ((TQCheckBox *)current)->state(); + else if (current->isA("TQComboBox")) + settings[current] = ((TQComboBox *)current)->currentItem(); + ++it; + } + delete options; + +} + +void ManagerModule::emitChanged() +{ + TQObjectList *options = view->queryList(0, "^option_"); + TQObject *current = 0; + TQObjectListIterator it(*options); + int value = -1; + bool somethingChanged = false; + + while ( (current = it.current()) != 0 ) { + if (current->isA("TQCheckBox")) + value = ((TQCheckBox *)current)->state(); + else if (current->isA("TQComboBox")) + value = ((TQComboBox *)current)->currentItem(); + + if (settings[current] != value) { + somethingChanged = true; + break; + } + + ++it; + } + delete options; + + emit changed(somethingChanged); +} + +#include "managermodule.moc" diff --git a/tdeioslave/media/tdecmodule/managermodule.h b/tdeioslave/media/tdecmodule/managermodule.h new file mode 100644 index 000000000..a0a384675 --- /dev/null +++ b/tdeioslave/media/tdecmodule/managermodule.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 K�vin Ottens <ervin ipsquad net> + Copyright (c) 2006 Valentine Sinitsyn <[email protected]> + + 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 <tqmap.h> +#include <tdecmodule.h> + +class ManagerModuleView; + +class ManagerModule : public TDECModule +{ + Q_OBJECT + +public: + ManagerModule( TQWidget* parent = 0, const char* name = 0); + + void load(); + void save(); + void defaults(); + +private: + void rememberSettings(); + + ManagerModuleView *view; + TQMap<TQObject *, int> settings; + +private slots: + void emitChanged(); +}; + +#endif diff --git a/tdeioslave/media/tdecmodule/managermoduleview.ui b/tdeioslave/media/tdecmodule/managermoduleview.ui new file mode 100644 index 000000000..afde70c75 --- /dev/null +++ b/tdeioslave/media/tdecmodule/managermoduleview.ui @@ -0,0 +1,343 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>ManagerModuleView</class> +<widget class="TQWidget"> + <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> + <property name="caption"> + <string>ManagerModuleView</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQCheckBox"> + <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="TQCheckBox"> + <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="TQCheckBox"> + <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> + <widget class="TQGroupBox"> + <property name="name"> + <cstring>groupbox_mount</cstring> + </property> + <property name="title"> + <string>Mount options</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>Here you can specify default mount options for your storage media. Please note that some options are not supported for certain filesystems and/or medium. You will be able to redefine all these options on per-volume basis later, using Properties dialog of the corresponding volume.<br> +Some of the options are tristate. Leave them "undefined" to let KDE choose the best value depending on your media.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="TQLayoutWidget"> + <property name="name"> + <cstring>layout30</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQCheckBox" row="1" column="0"> + <property name="name"> + <cstring>option_ro</cstring> + </property> + <property name="text"> + <string>Read only</string> + </property> + <property name="whatsThis" stdset="0"> + <string>By default, mount all file systems read-only.</string> + </property> + </widget> + <widget class="TQCheckBox" row="2" column="1"> + <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="TQCheckBox" row="0" column="1"> + <property name="name"> + <cstring>option_flush</cstring> + </property> + <property name="text"> + <string>Flushed IO</string> + </property> + <property name="tristate"> + <bool>true</bool> + </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="TQCheckBox" row="3" column="0"> + <property name="name"> + <cstring>option_sync</cstring> + </property> + <property name="text"> + <string>Synchronous</string> + </property> + <property name="tristate"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>All I/O to the file system should be done synchronously.</string> + </property> + </widget> + <widget class="TQCheckBox" row="2" column="0"> + <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="TQCheckBox" row="1" column="1"> + <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="TQLayoutWidget" row="3" column="1" rowspan="2" colspan="1"> + <property name="name"> + <cstring>layout29</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel" row="1" column="0"> + <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="TQComboBox" row="0" column="1"> + <item> + <property name="text"> + <string>All Data</string> + </property> + </item> + <item> + <property name="text"> + <string>Ordered</string> + </property> + </item> + <item> + <property name="text"> + <string>Writeback</string> + </property> + </item> + <property name="name"> + <cstring>option_journaling</cstring> + </property> + <property name="currentItem"> + <number>1</number> + </property> + </widget> + <widget class="TQLabel" row="0" column="0"> + <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="TQComboBox" row="1" column="1"> + <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> + </widget> + </grid> + </widget> + <widget class="TQCheckBox" row="0" column="0"> + <property name="name"> + <cstring>option_automount</cstring> + </property> + <property name="text"> + <string>Mount automatically</string> + </property> + <property name="whatsThis" stdset="0"> + <string>By default, mount all file systems automatically.</string> + </property> + </widget> + <widget class="TQCheckBox" row="4" column="0"> + <property name="name"> + <cstring>option_atime</cstring> + </property> + <property name="text"> + <string>Access time updates</string> + </property> + <property name="tristate"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Update inode access time for each access.</string> + </property> + </widget> + </grid> + </widget> + </vbox> + </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>130</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<connections> + <connection> + <sender>kcfg_HalBackendEnabled</sender> + <signal>toggled(bool)</signal> + <receiver>groupbox_mount</receiver> + <slot>setEnabled(bool)</slot> + </connection> +</connections> +<tabstops> + <tabstop>kcfg_HalBackendEnabled</tabstop> + <tabstop>kcfg_CdPollingEnabled</tabstop> + <tabstop>kcfg_AutostartEnabled</tabstop> + <tabstop>option_automount</tabstop> + <tabstop>option_ro</tabstop> + <tabstop>option_quiet</tabstop> + <tabstop>option_sync</tabstop> + <tabstop>option_atime</tabstop> + <tabstop>option_flush</tabstop> + <tabstop>option_utf8</tabstop> + <tabstop>option_uid</tabstop> + <tabstop>option_journaling</tabstop> + <tabstop>option_shortname</tabstop> +</tabstops> +<includes> + <include location="local" impldecl="in implementation">managermoduleview.ui.h</include> +</includes> +<functions> + <function access="private" specifier="non virtual">init()</function> +</functions> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/tdeioslave/media/tdecmodule/media.desktop b/tdeioslave/media/tdecmodule/media.desktop new file mode 100644 index 000000000..d93f876f4 --- /dev/null +++ b/tdeioslave/media/tdecmodule/media.desktop @@ -0,0 +1,197 @@ +[Desktop Entry] +Type=Application +#DocPath= +Icon=system +Exec=tdecmshell media + + +X-TDE-Library=media +X-TDE-FactoryName=media +X-TDE-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;TDE;X-TDE-settings-peripherals; diff --git a/tdeioslave/media/tdecmodule/mimetypelistboxitem.cpp b/tdeioslave/media/tdecmodule/mimetypelistboxitem.cpp new file mode 100644 index 000000000..e7334248a --- /dev/null +++ b/tdeioslave/media/tdecmodule/mimetypelistboxitem.cpp @@ -0,0 +1,35 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 TQString &mimetype, TQListBox *parent) + : TQListBoxText(parent), m_mimetype(mimetype) +{ + KMimeType::Ptr mime = KMimeType::mimeType( mimetype ); + setText( mime->comment() ); +} + +const TQString &MimetypeListBoxItem::mimetype() const +{ + return m_mimetype; +} + diff --git a/tdeioslave/media/tdecmodule/mimetypelistboxitem.h b/tdeioslave/media/tdecmodule/mimetypelistboxitem.h new file mode 100644 index 000000000..e02f58b11 --- /dev/null +++ b/tdeioslave/media/tdecmodule/mimetypelistboxitem.h @@ -0,0 +1,37 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqlistbox.h> +#include <tqstring.h> + +class MimetypeListBoxItem : public TQListBoxText +{ +public: + MimetypeListBoxItem(const TQString &mimetype, TQListBox *parent); + + const TQString &mimetype() const; + +private: + TQString m_mimetype; +}; + +#endif diff --git a/tdeioslave/media/tdecmodule/notifiermodule.cpp b/tdeioslave/media/tdecmodule/notifiermodule.cpp new file mode 100644 index 000000000..4146c514f --- /dev/null +++ b/tdeioslave/media/tdecmodule/notifiermodule.cpp @@ -0,0 +1,230 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqlayout.h> +#include <kcombobox.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "notifiersettings.h" +#include "serviceconfigdialog.h" +#include "actionlistboxitem.h" +#include "mimetypelistboxitem.h" + +NotifierModule::NotifierModule(TQWidget *parent, const char *name) + : TDECModule(parent, name) +{ + TQBoxLayout *layout = new TQVBoxLayout( 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") ); + + TQStringList mimetypes = m_settings.supportedMimetypes(); + + TQStringList::iterator it = mimetypes.begin(); + TQStringList::iterator end = mimetypes.end(); + + for ( ; it!=end; ++it ) + { + new MimetypeListBoxItem( *it, m_view->mimetypesCombo->listBox() ); + } + + updateListBox(); + + connect( m_view->mimetypesCombo, TQT_SIGNAL( activated(int) ), + this, TQT_SLOT( slotMimeTypeChanged(int) ) ); + connect( m_view->actionsList, TQT_SIGNAL( selectionChanged(TQListBoxItem*) ), + this, TQT_SLOT( slotActionSelected(TQListBoxItem*) ) ); + connect( m_view->addButton, TQT_SIGNAL( clicked() ), + this, TQT_SLOT( slotAdd() ) ); + connect( m_view->editButton, TQT_SIGNAL( clicked() ), + this, TQT_SLOT( slotEdit() ) ); + connect( m_view->deleteButton, TQT_SIGNAL( clicked() ), + this, TQT_SLOT( slotDelete() ) ); + connect( m_view->toggleAutoButton, TQT_SIGNAL( clicked() ), + this, TQT_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 ); + + TQValueList<NotifierAction*> services; + if ( m_mimetype.isEmpty() ) + { + services = m_settings.actions(); + } + else + { + services = m_settings.actionsForMimetype( m_mimetype ); + } + + TQValueList<NotifierAction*>::iterator it; + + for ( it = services.begin(); it != services.end(); ++it ) + { + new ActionListBoxItem( *it, m_mimetype, m_view->actionsList ); + } +} + +void NotifierModule::slotActionSelected(TQListBoxItem *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 = TQString(); + } + else + { + TQListBoxItem *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 == TQDialog::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 == TQDialog::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/tdeioslave/media/tdecmodule/notifiermodule.h b/tdeioslave/media/tdecmodule/notifiermodule.h new file mode 100644 index 000000000..d5d97a96d --- /dev/null +++ b/tdeioslave/media/tdecmodule/notifiermodule.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tdecmodule.h> +#include <tqlistbox.h> + +#include "notifiersettings.h" +#include "notifiermoduleview.h" + +class NotifierModule : public TDECModule +{ + Q_OBJECT + +public: + NotifierModule( TQWidget* 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( TQListBoxItem * item ); + void slotMimeTypeChanged( int index ); + +private: + void updateListBox(); + + TQString m_mimetype; + NotifierSettings m_settings; + NotifierModuleView *m_view; +}; + +#endif diff --git a/tdeioslave/media/tdecmodule/notifiermoduleview.ui b/tdeioslave/media/tdecmodule/notifiermoduleview.ui new file mode 100644 index 000000000..ad508c03d --- /dev/null +++ b/tdeioslave/media/tdecmodule/notifiermoduleview.ui @@ -0,0 +1,171 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>NotifierModuleView</class> +<widget class="TQWidget"> + <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="TQLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <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="TQLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget" 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/tdeioslave/media/tdecmodule/serviceconfigdialog.cpp b/tdeioslave/media/tdecmodule/serviceconfigdialog.cpp new file mode 100644 index 000000000..61dd060f2 --- /dev/null +++ b/tdeioslave/media/tdecmodule/serviceconfigdialog.cpp @@ -0,0 +1,151 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 <tqlistbox.h> +#include <kservice.h> +#include <kopenwith.h> +#include <kpushbutton.h> +#include <kiconloader.h> +#include <tqpixmap.h> +#include <tqiconset.h> + + +#include "mimetypelistboxitem.h" + +ServiceConfigDialog::ServiceConfigDialog(NotifierServiceAction *action, + const TQStringList &mimetypesList, + TQWidget* 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 ); + + TQIconSet iconSet = SmallIconSet("configure"); + TQPixmap pixMap = iconSet.pixmap( TQIconSet::Small, TQIconSet::Normal ); + m_view->commandButton->setIconSet( iconSet ); + m_view->commandButton->setFixedSize( pixMap.width()+8, pixMap.height()+8 ); + + m_iconChanged = false; + + TQStringList all_mimetypes = mimetypesList; + TQStringList action_mimetypes = action->mimetypes(); + + TQStringList::iterator it = all_mimetypes.begin(); + TQStringList::iterator end = all_mimetypes.end(); + + for ( ; it!=end; ++it ) + { + TQListBox *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, TQT_SIGNAL( iconChanged(TQString) ), + this, TQT_SLOT( slotIconChanged() ) ); + connect( m_view->commandButton, TQT_SIGNAL( clicked() ), + this, TQT_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(); + + TQStringList mimetypes; + + uint list_count = m_view->mimetypesSelector->selectedListBox()->count(); + for( uint i=0; i < list_count; ++i ) + { + TQListBoxItem *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 == TQDialog::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/tdeioslave/media/tdecmodule/serviceconfigdialog.h b/tdeioslave/media/tdecmodule/serviceconfigdialog.h new file mode 100644 index 000000000..24e907205 --- /dev/null +++ b/tdeioslave/media/tdecmodule/serviceconfigdialog.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE Project + Copyright (c) 2005 Jean-Remy Falleri <[email protected]> + 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 TQStringList &mimetypesList, + TQWidget* 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/tdeioslave/media/tdecmodule/serviceview.ui b/tdeioslave/media/tdecmodule/serviceview.ui new file mode 100644 index 000000000..a6c3063f6 --- /dev/null +++ b/tdeioslave/media/tdecmodule/serviceview.ui @@ -0,0 +1,248 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>ServiceView</class> +<widget class="TQWidget"> + <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="TQGroupBox" 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="TQLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget"> + <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="TQLayoutWidget"> + <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="TQLayoutWidget"> + <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/tdeioslave/media/tdefile-plugin/CMakeLists.txt b/tdeioslave/media/tdefile-plugin/CMakeLists.txt new file mode 100644 index 000000000..88c9220fa --- /dev/null +++ b/tdeioslave/media/tdefile-plugin/CMakeLists.txt @@ -0,0 +1,40 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +check_include_file( "sys/statvfs.h" HAVE_STATVFS ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/tdeioslave/media/libmediacommon + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES tdefile_media.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### tdefile_media (module) ###################### + +set( target tdefile_media ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES tdefilemediaplugin.cpp + LINK mediacommon-static tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/media/tdefile-plugin/Makefile.am b/tdeioslave/media/tdefile-plugin/Makefile.am new file mode 100644 index 000000000..15807ea19 --- /dev/null +++ b/tdeioslave/media/tdefile-plugin/Makefile.am @@ -0,0 +1,13 @@ +kde_module_LTLIBRARIES = tdefile_media.la + +tdefile_media_la_SOURCES = tdefilemediaplugin.cpp +tdefile_media_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) -module $(KDE_PLUGIN) +tdefile_media_la_LIBADD = $(LIB_KIO) ../libmediacommon/libmediacommon.la + +kdelnkdir = $(kde_servicesdir) +kde_services_DATA = tdefile_media.desktop + +INCLUDES = -I$(srcdir)/../libmediacommon $(all_includes) + +METASOURCES = AUTO +noinst_HEADERS = tdefilemediaplugin.h diff --git a/tdeioslave/media/tdefile-plugin/tdefile_media.desktop b/tdeioslave/media/tdefile-plugin/tdefile_media.desktop new file mode 100644 index 000000000..7d652e09a --- /dev/null +++ b/tdeioslave/media/tdefile-plugin/tdefile_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-TDE-Library=tdefile_media +MimeType=media/audiocd;media/hdd_mounted;media/hdd_mounted_decrypted;media/blankcd;media/hdd_unmounted;media/hdd_unmounted_decrypted;media/blankdvd;media/cdrom_mounted;media/cdrom_mounted_decrypted;media/cdrom_unmounted;media/cdrom_unmounted_decrypted;media/cdwriter_mounted;media/cdwriter_mounted_decrypted;media/nfs_mounted;media/cdwriter_unmounted;media/cdwriter_unmounted_decrypted;media/nfs_unmounted;media/removable_mounted;media/removable_mounted_decrypted;media/dvd_mounted;media/dvd_mounted_decrypted;media/removable_unmounted;media/removable_unmounted_decrypted;media/dvd_unmounted;media/dvd_unmounted_decrypted;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/tdeioslave/media/tdefile-plugin/tdefilemediaplugin.cpp b/tdeioslave/media/tdefile-plugin/tdefilemediaplugin.cpp new file mode 100644 index 000000000..20e68e459 --- /dev/null +++ b/tdeioslave/media/tdefile-plugin/tdefilemediaplugin.cpp @@ -0,0 +1,207 @@ +/* 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 "tdefilemediaplugin.h" + +#include <kgenericfactory.h> + +#include <dcopref.h> + +#include <tqpixmap.h> +#include <tqpainter.h> +#include <tqstyle.h> +#include <tqapplication.h> +#include <tqfile.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(tdefile_media, KFileMediaPluginFactory("kio_media")) + +KFileMediaPlugin::KFileMediaPlugin(TQObject *parent, const char *name, + const TQStringList& args) + : KFilePlugin(parent, name, args) +{ + addMimeType( "media/audiocd" ); + addMimeType( "media/hdd_mounted" ); + addMimeType( "media/hdd_mounted_decrypted" ); + addMimeType( "media/blankcd" ); + addMimeType( "media/hdd_unmounted" ); + addMimeType( "media/hdd_unmounted_decrypted" ); + addMimeType( "media/blankdvd" ); + addMimeType( "media/cdrom_mounted" ); + addMimeType( "media/cdrom_mounted_decrypted" ); + addMimeType( "media/cdrom_unmounted" ); + addMimeType( "media/cdrom_unmounted_decrypted" ); + addMimeType( "media/cdwriter_mounted" ); + addMimeType( "media/cdwriter_mounted_decrypted" ); + addMimeType( "media/nfs_mounted" ); + addMimeType( "media/cdwriter_unmounted" ); + addMimeType( "media/cdwriter_unmounted_decrypted" ); + addMimeType( "media/nfs_unmounted" ); + addMimeType( "media/removable_mounted" ); + addMimeType( "media/removable_mounted_decrypted" ); + addMimeType( "media/dvd_mounted" ); + addMimeType( "media/dvd_mounted_decrypted" ); + addMimeType( "media/removable_unmounted" ); + addMimeType( "media/removable_unmounted_decrypted" ); + addMimeType( "media/dvd_unmounted" ); + addMimeType( "media/dvd_unmounted_decrypted" ); + 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; + + TQString mount_point = medium.mountPoint(); + KURL base_url = medium.prettyBaseURL(); + TQString 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(TQFile::encodeName(mount_point), &vfs) != -1 ) + { + m_total = static_cast<TDEIO::filesize_t>(vfs.f_blocks) * static_cast<TDEIO::filesize_t>(vfs.f_frsize); + m_free = static_cast<TDEIO::filesize_t>(vfs.f_bavail) * static_cast<TDEIO::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", TQString("%1%").arg(percent)); + + TQPixmap bar(150, 20); + TQPainter p(&bar); + + p.fillRect(0, 0, length, 20, Qt::red); + p.fillRect(length, 0, 150-length, 20, Qt::green); + + TQColorGroup cg = TQApplication::palette().active(); + + TQApplication::style().tqdrawPrimitive(TQStyle::PE_Panel, &p, + TQRect(0, 0, 150, 20), cg, + TQStyle::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(TQString::null, TQString::null, TQString::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"), TQVariant::ULongLong); + setUnit(item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "used", i18n("Used"), TQVariant::ULongLong); + setUnit(item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "total", i18n("Total"), TQVariant::ULongLong); + setUnit(item, KFileMimeTypeInfo::Bytes); + + item = addItemInfo(group, "baseURL", i18n("Base URL"), TQVariant::String); + item = addItemInfo(group, "mountPoint", i18n("Mount Point"), TQVariant::String); + item = addItemInfo(group, "deviceNode", i18n("Device Node"), TQVariant::String); + + group = addGroupInfo(info, "mediumSummary", i18n("Medium Summary")); + + item = addItemInfo(group, "percent", i18n("Usage"), TQVariant::String); + + item = addItemInfo( group, "thumbnail", i18n("Bar Graph"), TQVariant::Image ); + setHint( item, KFileMimeTypeInfo::Thumbnail ); +} + +#include "tdefilemediaplugin.moc" diff --git a/tdeioslave/media/tdefile-plugin/tdefilemediaplugin.h b/tdeioslave/media/tdefile-plugin/tdefilemediaplugin.h new file mode 100644 index 000000000..e8dc7d26a --- /dev/null +++ b/tdeioslave/media/tdefile-plugin/tdefilemediaplugin.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 <tdefilemetainfo.h> +#include <kurl.h> +#include <tdeio/global.h> + +#include "medium.h" + +class KFileMediaPlugin : public KFilePlugin +{ +Q_OBJECT +public: + KFileMediaPlugin(TQObject *parent, const char *name, + const TQStringList &args); + + bool readInfo(KFileMetaInfo &info, uint what = KFileMetaInfo::Fastest); + +private: + void addMimeType(const char *mimeType); + const Medium askMedium(KFileMetaInfo &info); + + TDEIO::filesize_t m_total; + TDEIO::filesize_t m_used; + TDEIO::filesize_t m_free; +}; + +#endif diff --git a/tdeioslave/media/testmedia.cpp b/tdeioslave/media/testmedia.cpp new file mode 100644 index 000000000..eb0871f6f --- /dev/null +++ b/tdeioslave/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 TQString& txt, TQString a, TQString b) +{ + if (a.isEmpty()) + a = TQString::null; + if (b.isEmpty()) + b = TQString::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[]) +{ + TDEApplication::disableAutoDcopRegistration(); + TDECmdLineArgs::init(argc,argv,"testmedia", 0, 0, 0, 0); + TDEApplication 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/tdeioslave/media/testmedia.h b/tdeioslave/media/testmedia.h new file mode 100644 index 000000000..dd2481bda --- /dev/null +++ b/tdeioslave/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/tdeioslave/nfs/AUTHORS b/tdeioslave/nfs/AUTHORS new file mode 100644 index 000000000..062f9d6cb --- /dev/null +++ b/tdeioslave/nfs/AUTHORS @@ -0,0 +1,2 @@ +Written and maintained by: +Alexander Neundorf, [email protected] diff --git a/tdeioslave/nfs/CMakeLists.txt b/tdeioslave/nfs/CMakeLists.txt new file mode 100644 index 000000000..da9040fc9 --- /dev/null +++ b/tdeioslave/nfs/CMakeLists.txt @@ -0,0 +1,45 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES nfs.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_nfs (module) ########################## + +set( target kio_nfs ) + +add_custom_command( OUTPUT mount_xdr.c + COMMAND rpcgen -c -o mount_xdr.c ${CMAKE_CURRENT_SOURCE_DIR}/mount.x + DEPENDS mount.x ) + +add_custom_command( OUTPUT nfs_prot_xdr.c + COMMAND rpcgen -c -o nfs_prot_xdr.c ${CMAKE_CURRENT_SOURCE_DIR}/nfs_prot.x + DEPENDS nfs_prot.x ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kio_nfs.cpp mount_xdr.c nfs_prot_xdr.c + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/nfs/Makefile.am b/tdeioslave/nfs/Makefile.am new file mode 100644 index 000000000..4ab02fc1d --- /dev/null +++ b/tdeioslave/nfs/Makefile.am @@ -0,0 +1,28 @@ +## Makefile.am of tdebase/tdeioslave/man + +INCLUDES= $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +####### 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)/tdeio_nfs.pot diff --git a/tdeioslave/nfs/README b/tdeioslave/nfs/README new file mode 100644 index 000000000..671934e2c --- /dev/null +++ b/tdeioslave/nfs/README @@ -0,0 +1,3 @@ +this is an ioslave for KDE 2 for NFS, version 2. + +Alex diff --git a/tdeioslave/nfs/TODO b/tdeioslave/nfs/TODO new file mode 100644 index 000000000..6e5525127 --- /dev/null +++ b/tdeioslave/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/tdeioslave/nfs/kio_nfs.cpp b/tdeioslave/nfs/kio_nfs.cpp new file mode 100644 index 000000000..8148a7745 --- /dev/null +++ b/tdeioslave/nfs/kio_nfs.cpp @@ -0,0 +1,1615 @@ +/* This file is part of the KDE project + + Copyright (C) 2000 Alexander Neundorf <[email protected]> + + 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 <tqfile.h> +#include <tqdir.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> + +#include <tdeio/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 TDEIO; +using namespace std; + +//this is taken from tdelibs/tdecore/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 ) +{ + TDEInstance 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 TQString& path) +{ + return (path.isEmpty() || (path=="/")); +} + +static bool isAbsoluteLink(const TQString& 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 = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_USER; + atom.m_str = "root"; + entry.append( atom ); + atom.m_uds = TDEIO::UDS_GROUP; + atom.m_str = "root"; + entry.append( atom ); + + //a dummy size + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = 1024; + entry.append( atom ); +} + + +static void stripTrailingSlash(TQString& path) +{ + //if (path=="/") return; + if (path=="/") path=""; + else if (path[path.length()-1]=='/') path.truncate(path.length()-1); +} + +static void getLastPart(const TQString& path, TQString& lastPart, TQString& rest) +{ + int slashPos=path.findRev("/"); + lastPart=path.mid(slashPos+1); + rest=path.left(slashPos+1); +} + +static TQString removeFirstPart(const TQString& path) +{ + TQString 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 TQCString &pool, const TQCString &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 TQString& 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(TQString 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]; + } + TQString 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); + TQCString tmpStr=TQFile::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; + } + } + TQCString 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 tdelibs/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; + TQString fname; + if ( exportlist->ex_dir[0] == '/' ) + fname = TDEIO::encodeFileName(exportlist->ex_dir + 1); + else + fname = TDEIO::encodeFileName(exportlist->ex_dir); + m_handleCache.insert(TQString("/")+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); + TQString path( TQFile::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 (TQStringList::Iterator it=m_exportedDirs.begin(); it!=m_exportedDirs.end(); it++) + { + UDSAtom atom; + entry.clear(); + atom.m_uds = TDEIO::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; + } + + TQStringList 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 ((TQString(".")!=dirEntry->name) && (TQString("..")!=dirEntry->name)) + filesToList.append(dirEntry->name); + } + } while (!listres.readdirres_u.reply.eof); + totalSize( filesToList.count()); + + UDSEntry entry; + //stat all files in filesToList + for (TQStringList::Iterator it=filesToList.begin(); it!=filesToList.end(); it++) + { + UDSAtom atom; + diropargs dirargs; + diropres dirres; + memcpy(dirargs.dir.data,fh,NFS_FHSIZE); + TQCString tmpStr=TQFile::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 = TDEIO::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; + TQCString linkDest(readLinkRes.readlinkres_u.data); + atom.m_uds = TDEIO::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=TQDir::cleanDirPath(path+TQString("/")+TQString(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) +{ + TQString path( TQFile::encodeName(url.path())); + stripTrailingSlash(path); + kdDebug(7121)<<"NFS::stat for -"<<path<<"-"<<endl; + TQString 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 = TDEIO::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); + TQCString tmpStr=TQFile::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; + + TQString fileName, parentDir; + getLastPart(path, fileName, parentDir); + stripTrailingSlash(parentDir); + + atom.m_uds = TDEIO::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; + TQCString linkDest(readLinkRes.readlinkres_u.data); + atom.m_uds = TDEIO::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=TQDir::cleanDirPath(parentDir+TQString("/")+TQString(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 TQCString& path) +{ + //taken from file.cc + struct stat buff; + if ( ::stat( path.data(), &buff ) == -1 ) return; + + UDSAtom atom; + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = buff.st_mode & S_IFMT; // extract file type + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = buff.st_mode & 07777; // extract permissions + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = buff.st_size; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; + atom.m_long = buff.st_mtime; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_USER; + uid_t uid = buff.st_uid; + TQString *temp = m_usercache.find( uid ); + + if ( !temp ) + { + struct passwd *user = getpwuid( uid ); + if ( user ) + { + m_usercache.insert( uid, new TQString(TQString::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 = TDEIO::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 TQString(TQString::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 = TDEIO::UDS_ACCESS_TIME; + atom.m_long = buff.st_atime; + entry.append( atom ); + + atom.m_uds = TDEIO::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 = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFMT - 1; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = S_IRWXU | S_IRWXG | S_IRWXO; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = 0L; + entry.append( atom ); +} + +void NFSProtocol::completeUDSEntry(UDSEntry& entry, fattr& attributes) +{ + UDSAtom atom; + + atom.m_uds = TDEIO::UDS_SIZE; + atom.m_long = attributes.size; + entry.append(atom); + + atom.m_uds = TDEIO::UDS_MODIFICATION_TIME; + atom.m_long = attributes.mtime.seconds; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS_TIME; + atom.m_long = attributes.atime.seconds; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_CREATION_TIME; + atom.m_long = attributes.ctime.seconds; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = (attributes.mode & 07777); + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long =attributes.mode & S_IFMT; // extract file type + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_USER; + uid_t uid = attributes.uid; + TQString *temp = m_usercache.find( uid ); + if ( !temp ) + { + struct passwd *user = getpwuid( uid ); + if ( user ) + { + m_usercache.insert( uid, new TQString(user->pw_name) ); + atom.m_str = user->pw_name; + } + else + atom.m_str = "???"; + } + else + atom.m_str = *temp; + entry.append( atom ); + + atom.m_uds = TDEIO::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 TQString(grp->gr_name) ); + atom.m_str = grp->gr_name; + } + else + atom.m_str = "???"; + } + else + atom.m_str = *temp; + entry.append( atom ); + +/* TDEIO::UDSEntry::ConstIterator it = entry.begin(); + for( ; it != entry.end(); it++ ) { + switch ((*it).m_uds) { + case TDEIO::UDS_FILE_TYPE: + kdDebug(7121) << "File Type : " << (mode_t)((*it).m_long) << endl; + break; + case TDEIO::UDS_ACCESS: + kdDebug(7121) << "Access permissions : " << (mode_t)((*it).m_long) << endl; + break; + case TDEIO::UDS_USER: + kdDebug(7121) << "User : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_GROUP: + kdDebug(7121) << "Group : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_NAME: + kdDebug(7121) << "Name : " << ((*it).m_str.ascii() ) << endl; + //m_strText = decodeFileName( (*it).m_str ); + break; + case TDEIO::UDS_URL: + kdDebug(7121) << "URL : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_MIME_TYPE: + kdDebug(7121) << "MimeType : " << ((*it).m_str.ascii() ) << endl; + break; + case TDEIO::UDS_LINK_DEST: + kdDebug(7121) << "LinkDest : " << ((*it).m_str.ascii() ) << endl; + break; + } + }*/ +} + +void NFSProtocol::setHost(const TQString& host, int /*port*/, const TQString& /*user*/, const TQString& /*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; + TQString thePath( TQFile::encodeName(url.path())); + stripTrailingSlash(thePath); + TQString 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); + TQCString tmpName=TQFile::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 TQString& 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) +{ + TQString thePath( TQFile::encodeName(url.path())); + stripTrailingSlash(thePath); + + TQString 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); + TQCString tmpName=TQFile::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); + TQCString tmpName=TQFile::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 ) +{ + TQString thePath( TQFile::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 ) +{ + TQString thePath( TQFile::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; + + TQByteArray 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( TQByteArray() ); + finished(); +} + +//TODO the partial putting thing is not yet implemented +void NFSProtocol::put( const KURL& url, int _mode, bool _overwrite, bool /*_resume*/ ) +{ + TQString destPath( TQFile::encodeName(url.path())); + kdDebug( 7121 ) << "Put -" << destPath <<"-"<<endl; + /*TQString dest_part( dest_orig ); + dest_part += ".part";*/ + + stripTrailingSlash(destPath); + TQString 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); + TQCString tmpName=TQFile::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 + { + TQByteArray 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 ) +{ + TQString srcPath( TQFile::encodeName(src.path())); + TQString destPath( TQFile::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; + } + } + + TQString 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); + TQCString tmpName=TQFile::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); + TQCString tmpName2=TQFile::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 + TQString thePath( TQFile::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 + TQString destPath( TQFile::encodeName(dest.path())); + stripTrailingSlash(destPath); + TQString 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); + TQCString tmpName=TQFile::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 TQString &target, const KURL &dest, bool ) +{ + kdDebug(7121)<<"symlinking "<<endl; + TQString destPath=dest.path(); + stripTrailingSlash(destPath); + + TQString 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; + TQCString tmpStr=target.latin1(); + symlinkargs symLinkArgs; + symLinkArgs.to=tmpStr.data(); + memcpy(symLinkArgs.from.dir.data,(const char*)fh,NFS_FHSIZE); + TQCString tmpStr2=TQFile::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 TQString& parentDir, const TQString& 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 TQFile::exists(linkDest); + } + else + { + kdDebug(7121)<<"is a relative link"<<endl; + TQString absDest=parentDir+"/"+linkDest; + kdDebug(7121)<<"pointing abs to "<<absDest<<endl; + absDest=removeFirstPart(absDest); + kdDebug(7121)<<"removed first part "<<absDest<<endl; + absDest=TQDir::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=TQDir::cleanDirPath(absDest); + kdDebug(7121)<<"getting file handle of "<<absDest<<endl; + NFSFileHandle fh=getFileHandle(absDest); + return (!fh.isInvalid()); + } + return FALSE; +} + diff --git a/tdeioslave/nfs/kio_nfs.h b/tdeioslave/nfs/kio_nfs.h new file mode 100644 index 000000000..b548fc487 --- /dev/null +++ b/tdeioslave/nfs/kio_nfs.h @@ -0,0 +1,109 @@ +/* This file is part of the KDE project + Copyright (C) 2000 Alexander Neundorf <[email protected]> + + 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 <tdeio/slavebase.h> +#include <tdeio/global.h> + +#include <tqmap.h> +#include <tqstring.h> +#include <tqstringlist.h> +#include <tqintdict.h> +#include <tqtimer.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 TQMap<TQString,NFSFileHandle> NFSFileHandleMap; + + +class NFSProtocol : public TDEIO::SlaveBase +{ + public: + NFSProtocol (const TQCString &pool, const TQCString &app ); + virtual ~NFSProtocol(); + + virtual void openConnection(); + virtual void closeConnection(); + + virtual void setHost( const TQString& host, int port, const TQString& user, const TQString& 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 TQString &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(TDEIO::UDSEntry & entry); + bool checkForError(int clientStat, int nfsStat, const TQString& text); + bool isExportedDir(const TQString& path); + void completeUDSEntry(TDEIO::UDSEntry& entry, fattr& attributes); + void completeBadLinkUDSEntry(TDEIO::UDSEntry& entry, fattr& attributes); + void completeAbsoluteLinkUDSEntry(TDEIO::UDSEntry& entry, const TQCString& path); + bool isValidLink(const TQString& parentDir, const TQString& linkDest); +// bool isAbsoluteLink(const TQString& path); + + NFSFileHandle getFileHandle(TQString path); + + NFSFileHandleMap m_handleCache; + TQIntDict<TQString> m_usercache; // maps long ==> TQString * + TQIntDict<TQString> m_groupcache; + + TQStringList m_exportedDirs; + TQString 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/tdeioslave/nfs/mount.h b/tdeioslave/nfs/mount.h new file mode 100644 index 000000000..c3b8c217f --- /dev/null +++ b/tdeioslave/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/tdeioslave/nfs/mount.x b/tdeioslave/nfs/mount.x new file mode 100644 index 000000000..4aaf97de9 --- /dev/null +++ b/tdeioslave/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/tdeioslave/nfs/mount_xdr.c b/tdeioslave/nfs/mount_xdr.c new file mode 100644 index 000000000..38a43ca28 --- /dev/null +++ b/tdeioslave/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/tdeioslave/nfs/nfs.protocol b/tdeioslave/nfs/nfs.protocol new file mode 100644 index 000000000..8f62d779a --- /dev/null +++ b/tdeioslave/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=tdeioslave/nfs.html +Icon=nfs_mount diff --git a/tdeioslave/nfs/nfs_prot.h b/tdeioslave/nfs/nfs_prot.h new file mode 100644 index 000000000..5ed218f20 --- /dev/null +++ b/tdeioslave/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/tdeioslave/nfs/nfs_prot.x b/tdeioslave/nfs/nfs_prot.x new file mode 100644 index 000000000..cd21123c7 --- /dev/null +++ b/tdeioslave/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/tdeioslave/nfs/nfs_prot_xdr.c b/tdeioslave/nfs/nfs_prot_xdr.c new file mode 100644 index 000000000..57d446489 --- /dev/null +++ b/tdeioslave/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/tdeioslave/nntp/CMakeLists.txt b/tdeioslave/nntp/CMakeLists.txt new file mode 100644 index 000000000..d3401c455 --- /dev/null +++ b/tdeioslave/nntp/CMakeLists.txt @@ -0,0 +1,36 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES nntp.protocol nntps.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_nntp (module) ######################### + +set( target kio_nntp ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES nntp.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/nntp/LICENSE b/tdeioslave/nntp/LICENSE new file mode 100644 index 000000000..d28a48f92 --- /dev/null +++ b/tdeioslave/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/tdeioslave/nntp/Makefile.am b/tdeioslave/nntp/Makefile.am new file mode 100644 index 000000000..a48a7de80 --- /dev/null +++ b/tdeioslave/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)/tdeio_nntp.pot diff --git a/tdeioslave/nntp/nntp.cpp b/tdeioslave/nntp/nntp.cpp new file mode 100644 index 000000000..b92ff6a30 --- /dev/null +++ b/tdeioslave/nntp/nntp.cpp @@ -0,0 +1,896 @@ +/* This file is part of KDE + Copyright (C) 2000 by Wolfram Diestel <[email protected]> + Copyright (C) 2005 by Tim Way <[email protected]> + Copyright (C) 2005 by Volker Krause <[email protected]> + + 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 <tqdir.h> +#include <tqregexp.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 TDEIO; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +int kdemain(int argc, char **argv) { + + TDEInstance 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 TQCString & pool, const TQCString & 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 TQString & host, int port, const TQString & user, + const TQString & pass ) +{ + DBG << "setHost: " << ( ! user.isEmpty() ? (user+"@") : TQString("")) + << 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; + TQString path = TQDir::cleanDirPath(url.path()); + TQRegExp regMsgId = TQRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false); + int pos; + TQString group; + TQString 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 + TQCString line; + TQByteArray 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 TQCString, 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 TQByteArray& data) { + // 1 = post article + int cmd; + TQDataStream 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 { + TQByteArray buffer; + TQCString data; + dataReq(); + result = readData(buffer); + // treat the buffer data + if (result>0) { + data = TQCString(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; + TQString path = TQDir::cleanDirPath(url.path()); + TQRegExp regGroup = TQRegExp("^\\/?[a-z0-9\\.\\-_]+\\/?$",false); + TQRegExp regMsgId = TQRegExp("^\\/?[a-z0-9\\.\\-_]+\\/<\\S+>$", false); + int pos; + TQString group; + TQString msg_id; + + // / = group list + if (path.isEmpty() || path == "/") { + DBG << "stat root" << endl; + fillUDSEntry(entry, TQString::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; + + TQString path = TQDir::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; + TQString group; + if (path.left(1) == "/") + path.remove(0,1); + if ((pos = path.find('/')) > 0) + group = path.left(pos); + else + group = path; + TQString first = url.queryItem( "first" ); + if ( fetchGroup( group, first.toULong() ) ) + finished(); + } +} + +void NNTPProtocol::fetchGroups( const TQString &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 + TQCString 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 = TQString::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( TQString &group, unsigned long first ) { + int res_code; + TQString 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 " + TQString::number( first ) ); + TQString resp_line = readBuffer; + if (res_code != 223) { + unexpected_response(res_code,"STAT"); + return false; + } + + //STAT res_line: 223 nnn <msg_id> ... + TQString 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; + + TQString line; + TQStringList 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 " + TQString::number( first ) + "-" ); + if ( res == 420 ) + return true; // no articles selected + if ( res == 500 ) + notSupported = true; // unknwon command + if ( res != 224 ) + return false; + + long msgSize; + TQString msgId; + UDSAtom atom; + UDSEntry entry; + UDSEntryList entryList; + + TQStringList 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 = TQStringList::split( "\t", line, true ); + msgId = TQString::null; + msgSize = 0; + TQStringList::ConstIterator it = headers.constBegin(); + TQStringList::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 TQString& 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 = TQString::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 = TQString::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 = TQString::null; + entry.append(atom); + + atom.m_uds = UDS_USER; + atom.m_str = mUser.isEmpty() ? TQString("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 TQString &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() ) { + TDEIO::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 TQString & 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 TDEIO::Error's instead, but let this here for + documentation of the NNTP response codes and may + by later use. +TQString& NNTPProtocol::errorStr(int resp_code) { + TQString 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 = TQString("unknown NNTP response code %1").arg(resp_code); + } + + return ret; +} +*/ diff --git a/tdeioslave/nntp/nntp.h b/tdeioslave/nntp/nntp.h new file mode 100644 index 000000000..7e6859c50 --- /dev/null +++ b/tdeioslave/nntp/nntp.h @@ -0,0 +1,130 @@ +/* This file is part of KDE + Copyright (C) 2000 by Wolfram Diestel <[email protected]> + Copyright (C) 2005 by Tim Way <[email protected]> + Copyright (C) 2005 by Volker Krause <[email protected]> + + 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 <tqstring.h> +#include <tdeio/global.h> +#include <tdeio/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 TDEIO::TCPSlaveBase +{ + + public: + /** Default Constructor + * @param isSSL is a true or false to indicate whether ssl is to be used + */ + NNTPProtocol ( const TQCString & pool, const TQCString & 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 TQString& host, int port, + const TQString& user, const TQString& 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 TQByteArray& data); + + protected: + + /** + * Send a command to the server. Returns the response code and + * the response line + */ + int sendCommand( const TQString &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: + TQString 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 TQString &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 ( TQString &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 ( TDEIO::UDSEntry & entry, const TQString & name, long size, + bool postingAllowed, bool is_article ); + /// error handling for unexpected responses + void unexpected_response ( int res_code, const TQString & 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/tdeioslave/nntp/nntp.protocol b/tdeioslave/nntp/nntp.protocol new file mode 100644 index 000000000..8a51f88fb --- /dev/null +++ b/tdeioslave/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=tdeioslave/nntp.html +Icon=news diff --git a/tdeioslave/nntp/nntps.protocol b/tdeioslave/nntp/nntps.protocol new file mode 100644 index 000000000..dd975d250 --- /dev/null +++ b/tdeioslave/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=tdeioslave/nntp.html +Icon=news diff --git a/tdeioslave/pop3/CMakeLists.txt b/tdeioslave/pop3/CMakeLists.txt new file mode 100644 index 000000000..a5fcaf2da --- /dev/null +++ b/tdeioslave/pop3/CMakeLists.txt @@ -0,0 +1,37 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES pop3.protocol pop3s.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_pop3 (module) ######################### + +set( target kio_pop3 ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES pop3.cc + LINK tdeio-shared ${SASL_LIBRARIES} + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/pop3/Makefile.am b/tdeioslave/pop3/Makefile.am new file mode 100644 index 000000000..86d649b66 --- /dev/null +++ b/tdeioslave/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)/tdeio_pop3.pot diff --git a/tdeioslave/pop3/pop3.cc b/tdeioslave/pop3/pop3.cc new file mode 100644 index 000000000..e4518da08 --- /dev/null +++ b/tdeioslave/pop3/pop3.cc @@ -0,0 +1,1263 @@ +/* + * Copyright (c) 1999-2001 Alex Zepeda + * Copyright (c) 2001-2002 Michael Haeckel <[email protected]> + * 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 <tqcstring.h> +#include <tqglobal.h> +#include <tqregexp.h> + +#include <kdebug.h> +#include <kinstance.h> +#include <klocale.h> +#include <kmdcodec.h> +#include <kprotocolmanager.h> +#include <ksock.h> + +#include <tdeio/connection.h> +#include <tdeio/slaveinterface.h> +#include <tdeio/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 TDEIO; + +#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 + + TDEInstance 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 TQCString & pool, const TQCString & 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 TQString & _host, int _port, + const TQString & _user, const TQString & _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) + memmove(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) + memmove(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))); + } + + TQString command = TQString::fromLatin1(cmd); + TQString serverMsg = TQString::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, TDEIO::AuthInfo &ai ) +{ + char buf[512]; + + TQString apop_string = TQString::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 \ +( TQString::fromUtf8( sasl_errdetail( conn ) ))); \ + +int POP3Protocol::loginSASL( TDEIO::AuthInfo &ai ) +{ +#ifdef HAVE_LIBSASL2 + char buf[512]; + TQString sasl_buffer = TQString::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 ) { + + TQStringList 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; + + TQByteArray challenge, tmp; + + TQString firstCommand = "AUTH " + TQString::fromLatin1( mechusing ); + challenge.setRawData( out, outlen ); + KCodecs::base64Encode( challenge, tmp ); + challenge.resetRawData( out, outlen ); + if ( !tmp.isEmpty() ) { + firstCommand += " "; + firstCommand += TQString::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: " << TQCString(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: " << TQCString(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( TDEIO::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; + + TQString one_string = TQString::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 = TQString::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 + } + TQCString greeting(greeting_buf); + delete[]greeting_buf; + + if (greeting.length() > 0) { + greeting.truncate(greeting.length() - 2); + } + + // Does the server support APOP? + TQString apop_cmd; + TQRegExp 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; + } + + TDEIO::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; + TQCString 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 TQByteArray & aData) +{ + TQString result; + char buf[MAX_PACKET_LEN]; + TQDataStream stream(aData, IO_ReadOnly); + int tmp; + stream >> tmp; + + if (tmp != 'c') + return; + + for (int i = 0; i < 2; i++) { + TQCString 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 += " " + TQString(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]; + TQByteArray array; + TQString 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(TQByteArray()); + finished(); + } else if (cmd == "remove") { + TQStringList waitingCommands = TQStringList::split(',', path); + int activeCommands = 0; + TQStringList::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") { + TQStringList waitingCommands = TQStringList::split(',', path); + bool noProgress = (metaData("progress") == "off" + || waitingCommands.count() > 1); + int p_size = 0; + unsigned int msg_len = 0; + TQString 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; + TQStringList::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(TQByteArray()); + finished(); + } else if ((cmd == "uid") || (cmd == "list")) { + TQString 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(TQByteArray()); + 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]; + TQCString 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; + TQString 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(TQString::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 = TDEIO::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) +{ + TQString _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 */ ) +{ + TQString invalidURI = TQString::null; + bool isInt; + + if (!pop3_open()) { + POP3_DEBUG << "pop3_open failed" << endl; + error(ERR_COULD_NOT_CONNECT, m_sServer); + return; + } + + TQString _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/tdeioslave/pop3/pop3.h b/tdeioslave/pop3/pop3.h new file mode 100644 index 000000000..62812c82c --- /dev/null +++ b/tdeioslave/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 <tqstring.h> + +#include <tdeio/tcpslavebase.h> + +#define MAX_PACKET_LEN 4096 + +class POP3Protocol:public TDEIO::TCPSlaveBase { +public: + POP3Protocol(const TQCString & pool, const TQCString & app, bool SSL); + virtual ~ POP3Protocol(); + + virtual void setHost(const TQString & host, int port, + const TQString & user, const TQString & pass); + + virtual void special(const TQByteArray & 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, TDEIO::AuthInfo &ai ); + + bool saslInteract( void *in, TDEIO::AuthInfo &ai ); + /** + * Authenticate via SASL + */ + int loginSASL( TDEIO::AuthInfo &ai ); + /** + * Authenticate via traditional USER/PASS + */ + bool loginPASS( TDEIO::AuthInfo &ai ); + + int m_cmd; + unsigned short int m_iOldPort; + struct timeval m_tTimeout; + TQString m_sOldServer, m_sOldPass, m_sOldUser; + TQString m_sServer, m_sPass, m_sUser; + bool m_try_apop, m_try_sasl, opened, supports_apop; + TQString m_sError; + char readBuffer[MAX_PACKET_LEN]; + ssize_t readBufferLen; +}; + +#endif diff --git a/tdeioslave/pop3/pop3.protocol b/tdeioslave/pop3/pop3.protocol new file mode 100644 index 000000000..23560fa48 --- /dev/null +++ b/tdeioslave/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=tdeioslave/pop3.html +Icon=folder_inbox diff --git a/tdeioslave/pop3/pop3s.protocol b/tdeioslave/pop3/pop3s.protocol new file mode 100644 index 000000000..8bcf0c899 --- /dev/null +++ b/tdeioslave/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=tdeioslave/pop3s.html +Icon=folder_inbox diff --git a/tdeioslave/remote/CMakeLists.txt b/tdeioslave/remote/CMakeLists.txt new file mode 100644 index 000000000..fe4c29154 --- /dev/null +++ b/tdeioslave/remote/CMakeLists.txt @@ -0,0 +1,50 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +add_subdirectory( kdedmodule ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES remote.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kioremote (static) ######################## + +set( target kioremote ) + +tde_add_library( ${target} STATIC_PIC AUTOMOC + SOURCES kio_remote.cpp remoteimpl.cpp +) + + +##### kio_remote (module) ####################### + +set( target kio_remote ) + +configure_file( ${CMAKE_SOURCE_DIR}/cmake/modules/template_dummy_cpp.cmake dummy.cpp COPYONLY ) + +tde_add_kpart( ${target} + SOURCES dummy.cpp + EMBED kioremote-static + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/remote/Makefile.am b/tdeioslave/remote/Makefile.am new file mode 100644 index 000000000..4acb4e999 --- /dev/null +++ b/tdeioslave/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 = libtdeioremote.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 = libtdeioremote.la +libtdeioremote_la_SOURCES = kio_remote.cpp remoteimpl.cpp + +check_PROGRAMS = testremote +testremote_SOURCES = testremote.cpp +testremote_LDADD = libtdeioremote.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)/tdeio_remote.pot + diff --git a/tdeioslave/remote/dummy.cpp b/tdeioslave/remote/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tdeioslave/remote/dummy.cpp @@ -0,0 +1 @@ + diff --git a/tdeioslave/remote/kdedmodule/CMakeLists.txt b/tdeioslave/remote/kdedmodule/CMakeLists.txt new file mode 100644 index 000000000..2db56916c --- /dev/null +++ b/tdeioslave/remote/kdedmodule/CMakeLists.txt @@ -0,0 +1,41 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES remotedirnotify.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded ) + + +##### kded_remotedirnotify (module) ############# + +set( target kded_remotedirnotify ) + +set( ${target}_SRCS + remotedirnotify.cpp remotedirnotify.skel + remotedirnotifymodule.cpp remotedirnotifymodule.skel +) + +tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK tdeinit_kded-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/remote/kdedmodule/Makefile.am b/tdeioslave/remote/kdedmodule/Makefile.am new file mode 100644 index 000000000..61f4d2261 --- /dev/null +++ b/tdeioslave/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/tdeioslave/remote/kdedmodule/remotedirnotify.cpp b/tdeioslave/remote/kdedmodule/remotedirnotify.cpp new file mode 100644 index 000000000..fa880b78b --- /dev/null +++ b/tdeioslave/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 <tqdir.h> + +RemoteDirNotify::RemoteDirNotify() +{ + TDEGlobal::dirs()->addResourceType("remote_entries", + KStandardDirs::kde_default("data") + "remoteview"); + + TQString path = TDEGlobal::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) ) + { + TQString 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/tdeioslave/remote/kdedmodule/remotedirnotify.desktop b/tdeioslave/remote/kdedmodule/remotedirnotify.desktop new file mode 100644 index 000000000..24cc5e73f --- /dev/null +++ b/tdeioslave/remote/kdedmodule/remotedirnotify.desktop @@ -0,0 +1,117 @@ +[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 通知程式 +Comment=Provides change notification for network folders +Comment[ar]=يقدم إشعار عن التغيرات التي تحدث في مجلدات الشبكة +Comment[ca]=Proporciona notificacions de canvi en carpetes de xarxa +Comment[ca@valencia]=Proporciona notificacions de canvi en carpetes de xarxa +Comment[cs]=Poskytuje oznamování o změnách síťových složek +Comment[da]=Giver bekendtgørelse af ændringer af netværksmapper +Comment[de]=Benachrichtigt den Anwender über Änderungen an Dateien in Netzwerkordnern. +Comment[el]=Παρέχει ειδοποιήσεις για αλλαγές στους φακέλους δικτύου +Comment[en_GB]=Provides change notification for network folders +Comment[es]=Proporciona notificaciones visuales para carpetas de red +Comment[et]=Võrgukataloogide muutuste märguanded +Comment[eu]=Sareko karpeten aldaketa jakinarazpenak hornitzen ditu +Comment[fi]=Tarjoaa huomautuksia verkkokansioiden muutoksista +Comment[fr]=Fournit les notifications de modification pour les dossiers réseaux +Comment[fy]=Ferskaft feroaringsmelding foar netwurkmappen +Comment[ga]=Cuireann sé in iúl duit nuair a athraíonn fillteán líonra +Comment[he]=מספק שינוי התראות עבור תיקיות רשת +Comment[hr]=Omogućuje obavještavanje o promjenama na mrežnim mapama +Comment[ia]=Il forni notification de cambio pro dossieres de rete +Comment[id]=Menyediakan notifikasi pengubahan untuk folder jaringan +Comment[is]=Tilkynningaforrit fyrir breytingar á fjartengdum netmöppum +Comment[it]=Dà notifiche dei cambiamenti per le cartelle di rete +Comment[ja]=ネットワークフォルダの変更を通知 +Comment[kk]=Желідегі қапшықтар өзгерістері туралы құлақтандыру +Comment[km]=ផ្ដល់នូវការជូនដំណឹងផ្លាស់ប្ដូរសម្រាប់ថតបណ្ដាញ +Comment[kn]=ಜಾಲಭಂದ ಕಡತಕೋಶಗಳ ಬದಲಾವಣೆ ಸೂಚನೆಗಳನ್ನು ಒದಗಿಸುತ್ತದೆ. +Comment[ko]=네트워크 폴더의 변경 상황을 안내합니다 +Comment[lt]=Pranešimai vartotojui apie pasikeitimus tinklo aplankuose +Comment[lv]=Paziņo par izmaiņām tīkla mapēs +Comment[ml]=ശൃംഖലയിലുള്ള അറകളുടെ മാറ്റം അറിയിയ്ക്കുന്നു +Comment[nb]=Gir endringsvarslinger for nettverksmapper +Comment[nds]=Stellt Bescheden över Ännern vun Nettwarkornern praat +Comment[nl]=Levert notificatie voor wijzigingen in netwerkmappen +Comment[nn]=Gjev endringsvarsel for nettverksmapper +Comment[pa]=ਨੈੱਟਵਰਕ ਫੋਲਡਰਾਂ ਲਈ ਬਦਲਾਅ ਸੂਚਨਾ ਦਿੰਦਾ ਹੈ +Comment[pl]=Udostępnia powiadomienia o zmianach w katalogach sieciowych +Comment[pt]=Fornece notificações de alterações para as pastas de rede +Comment[pt_BR]=Fornece notificações de alterações para pastas de rede +Comment[ru]=Отслеживание изменений в сетевых папках +Comment[sk]=Poskytuje upozornenia o zmenách sieťových priečinkov +Comment[sl]=Ponuja obvestila o spremembah omrežnih map +Comment[sr]=Испоставља обавештења о изменама у мрежним фасциклама +Comment[sr@ijekavian]=Испоставља обавјештења о измјенама у мрежним фасциклама +Comment[sr@ijekavianlatin]=Ispostavlja obavještenja o izmjenama u mrežnim fasciklama +Comment[sr@latin]=Ispostavlja obaveštenja o izmenama u mrežnim fasciklama +Comment[sv]=Tillhandahåller ändringsunderrättelser för nätverkskataloger +Comment[tg]=Огоҳиҳои тағйиротро дар феҳрастҳои шабака нишон медиҳад +Comment[th]=ทำการแจ้งให้ทราบถึงความเปลี่ยนแปลงที่มีบนโฟลเดอร์บนเครือข่าย +Comment[tr]=Ağ dizinleri için değişim bildirimi sağlar. +Comment[uk]=Сповіщення про зміну стану мережевих тек +Comment[x-test]=xxProvides change notification for network foldersxx +Comment[zh_CN]=为网络文件夹提供更改通知 +Comment[zh_TW]=提供網路資料夾的變更通知 +ServiceTypes=KDEDModule +X-TDE-ModuleType=Library +X-TDE-Library=remotedirnotify +X-TDE-FactoryName=remotedirnotify +X-TDE-Kded-load-on-demand=true +X-TDE-Kded-autoload=true diff --git a/tdeioslave/remote/kdedmodule/remotedirnotify.h b/tdeioslave/remote/kdedmodule/remotedirnotify.h new file mode 100644 index 000000000..110d53a9d --- /dev/null +++ b/tdeioslave/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/tdeioslave/remote/kdedmodule/remotedirnotifymodule.cpp b/tdeioslave/remote/kdedmodule/remotedirnotifymodule.cpp new file mode 100644 index 000000000..13bfcfc29 --- /dev/null +++ b/tdeioslave/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 TQCString &obj) + : KDEDModule(obj) +{ +} + +extern "C" { + KDE_EXPORT KDEDModule *create_remotedirnotify(const TQCString &obj) + { + TDEGlobal::locale()->insertCatalogue("kio_remote"); + return new RemoteDirNotifyModule(obj); + } +} + diff --git a/tdeioslave/remote/kdedmodule/remotedirnotifymodule.h b/tdeioslave/remote/kdedmodule/remotedirnotifymodule.h new file mode 100644 index 000000000..e6cf0d720 --- /dev/null +++ b/tdeioslave/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 TQCString &obj); +private: + RemoteDirNotify notifier; +}; + +#endif diff --git a/tdeioslave/remote/kio_remote.cpp b/tdeioslave/remote/kio_remote.cpp new file mode 100644 index 000000000..87437700f --- /dev/null +++ b/tdeioslave/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 ) + { + // TDEApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + TDECmdLineArgs::init(argc, argv, "kio_remote", 0, 0, 0, 0); + TDECmdLineArgs::addCmdLineOptions( options ); + TDEApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs(); + RemoteProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +RemoteProtocol::RemoteProtocol(const TQCString &protocol, + const TQCString &pool, const TQCString &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 ); + TQString 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(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); +} + +void RemoteProtocol::listRoot() +{ + TDEIO::UDSEntry entry; + + TDEIO::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); + + TDEIO::UDSEntryListIterator it = remote_entries.begin(); + TDEIO::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; + + TQString path = url.path(); + if ( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + TDEIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + if (m_impl.isWizardURL(url)) + { + TDEIO::UDSEntry entry; + if (m_impl.createWizardEntry(entry)) + { + statEntry(entry); + finished(); + } + else + { + error(TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + } + return; + } + + int second_slash_idx = url.path().find( '/', 1 ); + TQString root_dirname = url.path().mid( 1, second_slash_idx-1 ); + + if ( second_slash_idx==-1 || ( (int)url.path().length() )==second_slash_idx+1 ) + { + TDEIO::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(TDEIO::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(TDEIO::ERR_CANNOT_DELETE, url.prettyURL()); +} + +void RemoteProtocol::get(const KURL &url) +{ + kdDebug(1220) << "RemoteProtocol::get: " << url << endl; + + TQString 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(TDEIO::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(TDEIO::ERR_UNSUPPORTED_ACTION, src.prettyURL()); + return; + } + + if (m_impl.renameFolders(src.fileName(), dest.fileName(), overwrite)) + { + finished(); + return; + } + + error(TDEIO::ERR_CANNOT_RENAME, src.prettyURL()); +} diff --git a/tdeioslave/remote/kio_remote.h b/tdeioslave/remote/kio_remote.h new file mode 100644 index 000000000..ebc6dd1ad --- /dev/null +++ b/tdeioslave/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 <tdeio/slavebase.h> +#include "remoteimpl.h" + +class RemoteProtocol : public TDEIO::SlaveBase +{ +public: + RemoteProtocol(const TQCString &protocol, const TQCString &pool, + const TQCString &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/tdeioslave/remote/remote.protocol b/tdeioslave/remote/remote.protocol new file mode 100644 index 000000000..69cc638aa --- /dev/null +++ b/tdeioslave/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=tdeioslave/file.html +Class=:local +deleteRecursive=true diff --git a/tdeioslave/remote/remoteimpl.cpp b/tdeioslave/remote/remoteimpl.cpp new file mode 100644 index 000000000..2ca263768 --- /dev/null +++ b/tdeioslave/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 <tqdir.h> +#include <tqfile.h> + +#include <sys/stat.h> + +#define WIZARD_URL "remote:/x-wizard_service.desktop" +#define WIZARD_SERVICE "knetattach" + +RemoteImpl::RemoteImpl() +{ + TDEGlobal::dirs()->addResourceType("remote_entries", + KStandardDirs::kde_default("data") + "remoteview"); + + TQString path = TDEGlobal::dirs()->saveLocation("remote_entries"); + + TQDir dir = path; + if (!dir.exists()) + { + dir.cdUp(); + dir.mkdir("remoteview"); + } +} + +void RemoteImpl::listRoot(TQValueList<TDEIO::UDSEntry> &list) const +{ + kdDebug(1220) << "RemoteImpl::listRoot" << endl; + + TQStringList names_found; + TQStringList dirList = TDEGlobal::dirs()->resourceDirs("remote_entries"); + + TQStringList::ConstIterator dirpath = dirList.begin(); + TQStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + TQDir dir = *dirpath; + if (!dir.exists()) continue; + + TQStringList filenames + = dir.entryList( TQDir::Files | TQDir::Readable ); + + + TDEIO::UDSEntry entry; + + TQStringList::ConstIterator name = filenames.begin(); + TQStringList::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 TQString &filename, TQString &directory) const +{ + kdDebug(1220) << "RemoteImpl::findDirectory" << endl; + + TQStringList dirList = TDEGlobal::dirs()->resourceDirs("remote_entries"); + + TQStringList::ConstIterator dirpath = dirList.begin(); + TQStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + TQDir dir = *dirpath; + if (!dir.exists()) continue; + + TQStringList filenames + = dir.entryList( TQDir::Files | TQDir::Readable ); + + + TDEIO::UDSEntry entry; + + TQStringList::ConstIterator name = filenames.begin(); + TQStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (*name==filename) + { + directory = *dirpath; + return true; + } + } + } + + return false; +} + +TQString RemoteImpl::findDesktopFile(const TQString &filename) const +{ + kdDebug(1220) << "RemoteImpl::findDesktopFile" << endl; + + TQString directory; + if (findDirectory(filename+".desktop", directory)) + { + return directory+filename+".desktop"; + } + + return TQString::null; +} + +KURL RemoteImpl::findBaseURL(const TQString &filename) const +{ + kdDebug(1220) << "RemoteImpl::findBaseURL" << endl; + + TQString file = findDesktopFile(filename); + if (!file.isEmpty()) + { + KDesktopFile desktop(file, true); + return desktop.readURL(); + } + + return KURL(); +} + + +static void addAtom(TDEIO::UDSEntry &entry, unsigned int ID, long l, + const TQString &s = TQString::null) +{ + TDEIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void RemoteImpl::createTopLevelEntry(TDEIO::UDSEntry &entry) const +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, "."); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_ACCESS, 0555); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, TDEIO::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(TDEIO::UDSEntry &entry) const +{ + entry.clear(); + + KURL url = findWizardRealURL(); + + if (!url.isValid()) + { + return false; + } + + addAtom(entry, TDEIO::UDS_NAME, 0, i18n("Add a Network Folder")); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFREG); + addAtom(entry, TDEIO::UDS_URL, 0, WIZARD_URL); + addAtom(entry, TDEIO::UDS_LOCAL_PATH, 0, url.path()); + addAtom(entry, TDEIO::UDS_ACCESS, 0500); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "application/x-desktop"); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, "wizard"); + + return true; +} + +bool RemoteImpl::isWizardURL(const KURL &url) const +{ + return url==KURL(WIZARD_URL); +} + + +void RemoteImpl::createEntry(TDEIO::UDSEntry &entry, + const TQString &directory, + const TQString &file) const +{ + kdDebug(1220) << "RemoteImpl::createEntry" << endl; + + KDesktopFile desktop(directory+file, true); + + kdDebug(1220) << "path = " << directory << file << endl; + + entry.clear(); + + TQString new_filename = file; + new_filename.truncate( file.length()-8); + + addAtom(entry, TDEIO::UDS_NAME, 0, desktop.readName()); + addAtom(entry, TDEIO::UDS_URL, 0, "remote:/"+new_filename); + + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/directory"); + + TQString icon = desktop.readIcon(); + + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, icon); + addAtom(entry, TDEIO::UDS_LINK_DEST, 0, desktop.readURL()); +} + +bool RemoteImpl::statNetworkFolder(TDEIO::UDSEntry &entry, const TQString &filename) const +{ + kdDebug(1220) << "RemoteImpl::statNetworkFolder: " << filename << endl; + + TQString directory; + if (findDirectory(filename+".desktop", directory)) + { + createEntry(entry, directory, filename+".desktop"); + return true; + } + + return false; +} + +bool RemoteImpl::deleteNetworkFolder(const TQString &filename) const +{ + kdDebug(1220) << "RemoteImpl::deleteNetworkFolder: " << filename << endl; + + TQString directory; + if (findDirectory(filename+".desktop", directory)) + { + kdDebug(1220) << "Removing " << directory << filename << ".desktop" << endl; + return TQFile::remove(directory+filename+".desktop"); + } + + return false; +} + +bool RemoteImpl::renameFolders(const TQString &src, const TQString &dest, + bool overwrite) const +{ + kdDebug(1220) << "RemoteImpl::renameFolders: " + << src << ", " << dest << endl; + + TQString directory; + if (findDirectory(src+".desktop", directory)) + { + if (!overwrite && TQFile::exists(directory+dest+".desktop")) + { + return false; + } + + kdDebug(1220) << "Renaming " << directory << src << ".desktop"<< endl; + TQDir 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/tdeioslave/remote/remoteimpl.h b/tdeioslave/remote/remoteimpl.h new file mode 100644 index 000000000..33aed3b0a --- /dev/null +++ b/tdeioslave/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 <tdeio/global.h> +#include <tdeio/job.h> +#include <kurl.h> + +#include <tqstring.h> + +class RemoteImpl +{ +public: + RemoteImpl(); + + void createTopLevelEntry(TDEIO::UDSEntry &entry) const; + bool createWizardEntry(TDEIO::UDSEntry &entry) const; + bool isWizardURL(const KURL &url) const; + bool statNetworkFolder(TDEIO::UDSEntry &entry, const TQString &filename) const; + + void listRoot(TQValueList<TDEIO::UDSEntry> &list) const; + + KURL findBaseURL(const TQString &filename) const; + TQString findDesktopFile(const TQString &filename) const; + + bool deleteNetworkFolder(const TQString &filename) const; + bool renameFolders(const TQString &src, const TQString &dest, + bool overwrite) const; + +private: + bool findDirectory(const TQString &filename, TQString &directory) const; + void createEntry(TDEIO::UDSEntry& entry, const TQString &directory, + const TQString &file) const; +}; + +#endif diff --git a/tdeioslave/remote/testremote.cpp b/tdeioslave/remote/testremote.cpp new file mode 100644 index 000000000..d091c5bc4 --- /dev/null +++ b/tdeioslave/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 TQString& txt, TQString a, TQString b) +{ + if (a.isEmpty()) + a = TQString::null; + if (b.isEmpty()) + b = TQString::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[]) +{ + TDEApplication::disableAutoDcopRegistration(); + TDECmdLineArgs::init(argc,argv,"testremote", 0, 0, 0, 0); + TDEApplication 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/tdeioslave/remote/testremote.h b/tdeioslave/remote/testremote.h new file mode 100644 index 000000000..ba51a8602 --- /dev/null +++ b/tdeioslave/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/tdeioslave/settings/CMakeLists.txt b/tdeioslave/settings/CMakeLists.txt new file mode 100644 index 000000000..c2a0d1d06 --- /dev/null +++ b/tdeioslave/settings/CMakeLists.txt @@ -0,0 +1,38 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES + settings.protocol programs.protocol applications.protocol + DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_settings (module) ##################### + +set( target kio_settings ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kio_settings.cc + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/settings/Makefile.am b/tdeioslave/settings/Makefile.am new file mode 100644 index 000000000..7daa4e009 --- /dev/null +++ b/tdeioslave/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)/tdeio_settings.pot + diff --git a/tdeioslave/settings/applications.protocol b/tdeioslave/settings/applications.protocol new file mode 100644 index 000000000..0193dfb8b --- /dev/null +++ b/tdeioslave/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/tdeioslave/settings/kio_settings.cc b/tdeioslave/settings/kio_settings.cc new file mode 100644 index 000000000..7060df177 --- /dev/null +++ b/tdeioslave/settings/kio_settings.cc @@ -0,0 +1,298 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Joseph Wenninger <[email protected]> + + 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 <tdeio/slavebase.h> +#include <kinstance.h> +#include <kdebug.h> +#include <tqtextstream.h> +#include <klocale.h> +#include <sys/stat.h> +#include <dcopclient.h> +#include <tqdatastream.h> +#include <time.h> +#include <kprocess.h> +#include <kservice.h> +#include <kservicegroup.h> +#include <kstandarddirs.h> + +class SettingsProtocol : public TDEIO::SlaveBase +{ +public: + enum RunMode { SettingsMode, ProgramsMode, ApplicationsMode }; + SettingsProtocol(const TQCString &protocol, const TQCString &pool, const TQCString &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 TQString &relPath); + +private: + DCOPClient *m_dcopClient; + RunMode m_runMode; +}; + +extern "C" { + KDE_EXPORT int kdemain( int, char **argv ) + { + kdDebug() << "kdemain for settings tdeioslave" << endl; + TDEInstance instance( "kio_settings" ); + SettingsProtocol slave(argv[1], argv[2], argv[3]); + slave.dispatchLoop(); + return 0; + } +} + + +static void addAtom(TDEIO::UDSEntry& entry, unsigned int ID, long l, const TQString& s = TQString::null) +{ + TDEIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + +static void createFileEntry(TDEIO::UDSEntry& entry, const TQString& name, const TQString& url, const TQString& mime, const TQString& iconName, const TQString& localPath) +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, TDEIO::encodeFileName(name)); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFREG); + addAtom(entry, TDEIO::UDS_URL, 0, url); + addAtom(entry, TDEIO::UDS_ACCESS, 0500); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, mime); + addAtom(entry, TDEIO::UDS_SIZE, 0); + addAtom(entry, TDEIO::UDS_LOCAL_PATH, 0, localPath); + addAtom(entry, TDEIO::UDS_CREATION_TIME, 1); + addAtom(entry, TDEIO::UDS_MODIFICATION_TIME, time(0)); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, iconName); +} + +static void createDirEntry(TDEIO::UDSEntry& entry, const TQString& name, const TQString& url, const TQString& mime,const TQString& iconName) +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, name); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_ACCESS, 0500); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, mime); + addAtom(entry, TDEIO::UDS_URL, 0, url); + addAtom(entry, TDEIO::UDS_SIZE, 0); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, iconName); +} + +SettingsProtocol::SettingsProtocol( const TQCString &protocol, const TQCString &pool, const TQCString &app): SlaveBase( protocol, pool, app ) +{ + // Adjusts which part of the TDE 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 TQString &relPath) +{ + TQString nextPart; + TQString alreadyFound("Settings/"); + TQStringList rest = TQStringList::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( TDEIO::ERR_IS_DIRECTORY, url.prettyURL() ); + } +} + + +void SettingsProtocol::stat(const KURL& url) +{ + TDEIO::UDSEntry entry; + + TQString 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(TDEIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); + return; + } + } + + statEntry(entry); + finished(); + return; +} + + +void SettingsProtocol::listDir(const KURL& url) +{ + TQString 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(TDEIO::ERR_SLAVE_DEFINED,i18n("Unknown settings folder")); + return; + } + } + + unsigned int count = 0; + TDEIO::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)); + TQString 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; + + TQString 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/tdeioslave/settings/kio_settings.h b/tdeioslave/settings/kio_settings.h new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tdeioslave/settings/kio_settings.h diff --git a/tdeioslave/settings/programs.protocol b/tdeioslave/settings/programs.protocol new file mode 100644 index 000000000..c09ac6372 --- /dev/null +++ b/tdeioslave/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/tdeioslave/settings/settings.protocol b/tdeioslave/settings/settings.protocol new file mode 100644 index 000000000..268bc0c69 --- /dev/null +++ b/tdeioslave/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/tdeioslave/sftp/AUTHORS b/tdeioslave/sftp/AUTHORS new file mode 100644 index 000000000..c763d00bc --- /dev/null +++ b/tdeioslave/sftp/AUTHORS @@ -0,0 +1,3 @@ +Dawit Alemayehu <[email protected]> +Lucas Fisher <[email protected]> + diff --git a/tdeioslave/sftp/CHANGELOG b/tdeioslave/sftp/CHANGELOG new file mode 100644 index 000000000..b60e7f44b --- /dev/null +++ b/tdeioslave/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 TDEProcess 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? TDEProcess doesn't give us access to env variables. + - Our askpass program can probably talk to the tdesu 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/tdeioslave/sftp/CMakeLists.txt b/tdeioslave/sftp/CMakeLists.txt new file mode 100644 index 000000000..cf07ed2b3 --- /dev/null +++ b/tdeioslave/sftp/CMakeLists.txt @@ -0,0 +1,37 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES sftp.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_sftp (module) ######################### + +set( target kio_sftp ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES process.cpp atomicio.cpp kio_sftp.cpp sftpfileattr.cpp ksshprocess.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/sftp/DEBUGGING b/tdeioslave/sftp/DEBUGGING new file mode 100644 index 000000000..89ae8fe18 --- /dev/null +++ b/tdeioslave/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/tdeioslave/sftp/Makefile.am b/tdeioslave/sftp/Makefile.am new file mode 100644 index 000000000..4bc14c203 --- /dev/null +++ b/tdeioslave/sftp/Makefile.am @@ -0,0 +1,25 @@ +## Makefile.am of tdebase/tdeioslave/sftp + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor +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)/tdeio_sftp.pot diff --git a/tdeioslave/sftp/TODO b/tdeioslave/sftp/TODO new file mode 100644 index 000000000..0f1411317 --- /dev/null +++ b/tdeioslave/sftp/TODO @@ -0,0 +1,5 @@ +TODO: +===== + +- Support for use of public keys, maybe ssh-agent, a key management app, etc. + diff --git a/tdeioslave/sftp/atomicio.cpp b/tdeioslave/sftp/atomicio.cpp new file mode 100644 index 000000000..057f20fe9 --- /dev/null +++ b/tdeioslave/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/tdeioslave/sftp/atomicio.h b/tdeioslave/sftp/atomicio.h new file mode 100644 index 000000000..4468757d5 --- /dev/null +++ b/tdeioslave/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/tdeioslave/sftp/kio_sftp.cpp b/tdeioslave/sftp/kio_sftp.cpp new file mode 100644 index 000000000..719fb9136 --- /dev/null +++ b/tdeioslave/sftp/kio_sftp.cpp @@ -0,0 +1,2282 @@ +/*************************************************************************** + sftp.cpp - description + ------------------- + begin : Fri Jun 29 23:45:40 CDT 2001 + copyright : (C) 2001 by Lucas Fisher + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 tdeinit 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 <tqcstring.h> +#include <tqstring.h> +#include <tqobject.h> +#include <tqstrlist.h> +#include <tqfile.h> +#include <tqbuffer.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 <tdeio/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 TDEIO; +extern "C" +{ + int KDE_EXPORT kdemain( int argc, char **argv ) + { + TDEInstance 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 TQCString &pool_socket, const TQCString &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, TQString::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; + TQCString dest_orig ( TQFile::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; + } + } + + TDEIO::filesize_t offset = 0; + TQCString 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 ?? + TDEIO::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(TQByteArray()); + kdDebug(KIO_SFTP_DB) << "sftpCopyGet(): emit finished()" << endl; + finished(); +} + +sftpProtocol::Status sftpProtocol::sftpGet( const KURL& src, TDEIO::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; + } + + TDEIO::filesize_t fileSize = attr.fileSize(); + TQ_UINT32 pflags = SSH2_FXF_READ; + attr.clear(); + + TQByteArray 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. + TQByteArray buff; + TQByteArray 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. + TQ_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 + TQ_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(TQByteArray()); + kdDebug(KIO_SFTP_DB) << "get(): emit finished()" << endl; + finished(); +} + + +void sftpProtocol::setHost (const TQString& h, int port, const TQString& user, const TQString& 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 { + mPort = -1; + } + + 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 + ":" + TQString::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 + TQString msg; // msg for dialog box + TQString 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, TQString::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, TQString::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, TQString::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, TQString::null); + return; + } + + // Now send init packet. + kdDebug(KIO_SFTP_DB) << "openConnection(): Sending SSH2_FXP_INIT packet." << endl; + TQByteArray p; + TQDataStream packet(p, IO_WriteOnly); + packet << (TQ_UINT32)5; // packet length + packet << (TQ_UINT8) SSH2_FXP_INIT; // packet type + packet << (TQ_UINT32)SSH2_FILEXFER_VERSION; // client version + + putPacket(p); + getPacket(p); + + TQDataStream s(p, IO_ReadOnly); + TQ_UINT32 version; + TQ_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; + TQCString file (TQFile::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" ); + + TQ_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); + + TQ_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()); + TQByteArray 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; + TQByteArray 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 = TDEIO::UDS_NAME; + atom.m_str = TQString::null; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + entry.append( atom ); + + atom.m_uds = TDEIO::UDS_USER; + atom.m_str = mUsername; + entry.append( atom ); + atom.m_uds = TDEIO::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; + + TQ_UINT32 pflags = SSH2_FXF_READ; + TQByteArray 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; + } + + TQ_UINT32 len = 1024; // Get first 1k for determining mimetype + TQ_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(TQByteArray()); + 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; + TQByteArray 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; + + TQCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + sftpFileAttr attr(remoteEncoding()); + + if (permissions != -1) + attr.setPermissions(permissions); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << TQ_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); + s << (TQ_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); + + TQ_UINT8 type; + TQDataStream 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( TDEIO::ERR_DIR_ALREADY_EXIST, dest.url() ); + else + error( TDEIO::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 TQString& 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){ + TQString 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, TQString::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 : TQString::null), mConnected); +} + +bool sftpProtocol::getPacket(TQByteArray& msg) { + TQByteArray 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; + TQDataStream s(buf, IO_ReadOnly); + s >> msgLen; + + //kdDebug(KIO_SFTP_DB) << "getPacket(): Message size = " << msgLen << endl; + + msg.resize(0); + + TQBuffer b( msg ); + b.open( IO_WriteOnly ); + + while( msgLen ) { + len = atomicio(ssh.stdioFd(), buf.data(), kMin((uint)buf.size(), msgLen), true /*read*/); + + if( len == 0 || len == -1) { + TQString 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(TQByteArray& 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; + + TQCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << TQ_UINT32(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (TQ_UINT8)SSH2_FXP_REALPATH; + s << id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + TQ_UINT8 type; + TQDataStream 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 ) { + TQ_UINT32 code; + r >> code; + return code; + } + + if( type != SSH2_FXP_NAME ) { + kdError(KIO_SFTP_DB) << "sftpRealPath(): unexpected packet type of " << type << endl; + return -1; + } + + TQ_UINT32 count; + r >> count; + if( count != 1 ) { + kdError(KIO_SFTP_DB) << "sftpRealPath(): Bad number of file attributes for realpath command" << endl; + return -1; + } + + TQCString newPath; + r >> newPath; + + newPath.truncate(newPath.size()); + if (newPath.isEmpty()) + newPath = "/"; + newUrl.setPath(newPath); + + return SSH2_FX_OK; +} + +sftpProtocol::Status sftpProtocol::doProcessStatus(TQ_UINT8 code, const TQString& 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(TQ_UINT8 code, const TQString& 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, TQByteArray& handle){ + + kdDebug(KIO_SFTP_DB) << "sftpOpenDirectory(" << url << ", handle)" << endl; + + TQCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (TQ_UINT8)SSH2_FXP_OPENDIR; + s << (TQ_UINT32)id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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 ) { + TQ_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 TQByteArray& handle){ + + kdDebug(KIO_SFTP_DB) << "sftpClose()" << endl; + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); + s << (TQ_UINT8)SSH2_FXP_CLOSE; + s << (TQ_UINT32)id; + s << handle; + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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; + } + + TQ_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; + + TQCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len + attr.size()); + s << (TQ_UINT8)SSH2_FXP_SETSTAT; + s << (TQ_UINT32)id; + s.writeBytes(path.data(), len); + s << attr; + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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; + } + + TQ_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; + + TQCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (TQ_UINT8)(isfile ? SSH2_FXP_REMOVE : SSH2_FXP_RMDIR); + s << (TQ_UINT32)id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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; + } + + TQ_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; + + TQCString srcPath = remoteEncoding()->encode(src.path()); + TQCString destPath = remoteEncoding()->encode(dest.path()); + + uint slen = srcPath.length(); + uint dlen = destPath.length(); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + slen + + 4 /*str length*/ + dlen); + s << (TQ_UINT8)SSH2_FXP_RENAME; + s << (TQ_UINT32)id; + s.writeBytes(srcPath.data(), slen); + s.writeBytes(destPath.data(), dlen); + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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 TQByteArray& handle, const KURL& url){ + // url is needed so we can lookup the link destination + kdDebug(KIO_SFTP_DB) << "sftpReadDir(): " << url << endl; + + TQ_UINT32 id, expectedId, count; + TQ_UINT8 type; + + sftpFileAttr attr (remoteEncoding()); + attr.setDirAttrsFlag(true); + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + id = expectedId = mMsgId++; + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + handle.size()); + s << (TQ_UINT8)SSH2_FXP_READDIR; + s << (TQ_UINT32)id; + s << handle; + + putPacket(p); + getPacket(p); + + TQDataStream 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, TQString& target){ + + kdDebug(KIO_SFTP_DB) << "sftpReadLink(): " << url << endl; + + TQCString 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; + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (TQ_UINT8)SSH2_FXP_READLINK; + s << id; + s.writeBytes(path.data(), len); + + + putPacket(p); + getPacket(p); + + TQ_UINT8 type; + TQDataStream 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 ) { + TQ_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; + } + + TQ_UINT32 count; + r >> count; + if( count != 1 ) { + kdError(KIO_SFTP_DB) << "sftpReadLink(): Bad number of file attributes for realpath command" << endl; + return -1; + } + + TQCString 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 TQString& _target, const KURL& dest){ + + TQCString destPath = remoteEncoding()->encode(dest.path()); + TQCString target = remoteEncoding()->encode(_target); + uint dlen = destPath.length(); + uint tlen = target.length(); + + kdDebug(KIO_SFTP_DB) << "sftpSymLink(" << target << " -> " << destPath << ")" << endl; + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + tlen + + 4 /*str length*/ + dlen); + s << (TQ_UINT8)SSH2_FXP_SYMLINK; + s << (TQ_UINT32)id; + s.writeBytes(target.data(), tlen); + s.writeBytes(destPath.data(), dlen); + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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; + } + + TQ_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; + + TQCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + 4 /*str length*/ + len); + s << (TQ_UINT8)SSH2_FXP_LSTAT; + s << (TQ_UINT32)id; + s.writeBytes(path.data(), len); + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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 ) { + TQ_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) ) { + + TQString 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 TQ_UINT32 pflags, + const sftpFileAttr& attr, TQByteArray& handle) { + kdDebug(KIO_SFTP_DB) << "sftpOpen(" << url << ", handle" << endl; + + TQCString path = remoteEncoding()->encode(url.path()); + uint len = path.length(); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + len + + 4 /*pflags*/ + attr.size()); + s << (TQ_UINT8)SSH2_FXP_OPEN; + s << (TQ_UINT32)id; + s.writeBytes(path.data(), len); + s << pflags; + s << attr; + + putPacket(p); + getPacket(p); + + TQDataStream r(p, IO_ReadOnly); + TQ_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 ) { + TQ_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 TQByteArray& handle, TDEIO::filesize_t offset, TQ_UINT32 len, TQByteArray& data) +{ + // kdDebug(KIO_SFTP_DB) << "sftpRead( offset = " << offset << ", len = " << len << ")" << endl; + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + handle.size() + + 8 /*offset*/ + 4 /*length*/); + s << (TQ_UINT8)SSH2_FXP_READ; + s << (TQ_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); + + TQDataStream r(p, IO_ReadOnly); + TQ_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 ) { + TQ_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 TQByteArray& handle, TDEIO::filesize_t offset, const TQByteArray& data){ +// kdDebug(KIO_SFTP_DB) << "sftpWrite( offset = " << offset << +// ", data sz = " << data.size() << ")" << endl; + TQByteArray p; + TQDataStream s(p, IO_WriteOnly); + + TQ_UINT32 id, expectedId; + id = expectedId = mMsgId++; + s << (TQ_UINT32)(1 /*type*/ + 4 /*id*/ + + 4 /*str length*/ + handle.size() + + 8 /*offset*/ + + 4 /* data size */ + data.size()); + s << (TQ_UINT8)SSH2_FXP_WRITE; + s << (TQ_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; + + TQDataStream r(p, IO_ReadOnly); + TQ_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; + } + + TQ_UINT32 code; + r >> code; + return code; +} + + diff --git a/tdeioslave/sftp/kio_sftp.h b/tdeioslave/sftp/kio_sftp.h new file mode 100644 index 000000000..1674221b8 --- /dev/null +++ b/tdeioslave/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 : [email protected] +***************************************************************************/ + +/*************************************************************************** + * * + * 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 <tqstring.h> +#include <tqcstring.h> +#include <tqobject.h> + +#include <kurl.h> +#include <tdeio/global.h> +#include <tdeio/slavebase.h> +#include <kdebug.h> + +#include "process.h" +#include "sftpfileattr.h" +#include "ksshprocess.h" + +#define KIO_SFTP_DB 7120 + + +class sftpProtocol : public TDEIO::SlaveBase +{ + +public: + sftpProtocol(const TQCString &pool_socket, const TQCString &app_socket); + virtual ~sftpProtocol(); + virtual void setHost(const TQString& h, int port, const TQString& user, const TQString& 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 TQString& 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. */ + TQString mHost; + + /** Port we are connected to. */ + int mPort; + + /** Ssh process to which we send the sftp packets. */ + KSshProcess ssh; + + /** Username to use when connecting */ + TQString mUsername; + + /** User's password */ + TQString 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; + TDEIO::filesize_t size; + TQString text; + }; + +private: // private methods + bool getPacket(TQByteArray& 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(TQByteArray& p); + /** Process SSH_FXP_STATUS packets. */ + void processStatus(TQ_UINT8, const TQString& message = TQString::null); + /** Process SSH_FXP_STATUS packes and return the result. */ + Status doProcessStatus(TQ_UINT8, const TQString& message = TQString::null); + /** Opens a directory handle for url.path. Returns true if succeeds. */ + int sftpOpenDirectory(const KURL& url, TQByteArray& handle); + /** Closes a directory or file handle. */ + int sftpClose(const TQByteArray& 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 TQString& target, const KURL& dest); + /** Get directory listings. */ + int sftpReadDir(const TQByteArray& handle, const KURL& url); + /** Retrieves the destination of a link. */ + int sftpReadLink(const KURL& url, TQString& target); + /** Stats a file. */ + int sftpStat(const KURL& url, sftpFileAttr& attr); + /** No descriptions */ + int sftpOpen(const KURL& url, const TQ_UINT32 pflags, const sftpFileAttr& attr, TQByteArray& handle); + /** No descriptions */ + int sftpRead(const TQByteArray& handle, TDEIO::filesize_t offset, TQ_UINT32 len, TQByteArray& data); + /** No descriptions */ + int sftpWrite(const TQByteArray& handle, TDEIO::filesize_t offset, const TQByteArray& 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, TDEIO::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/tdeioslave/sftp/ksshprocess.cpp b/tdeioslave/sftp/ksshprocess.cpp new file mode 100644 index 000000000..3393f8934 --- /dev/null +++ b/tdeioslave/sftp/ksshprocess.cpp @@ -0,0 +1,1114 @@ +/*************************************************************************** + ksshprocess.cpp - description + ------------------- + begin : Tue Jul 31 2001 + copyright : (C) 2001 by Lucas Fisher + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 tdelibs 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 <tqregexp.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. + */ +TQRegExp KSshProcess::versionStrs[] = { + TQRegExp("OpenSSH_3\\.[6-9]|OpenSSH_[1-9]*[4-9]\\.[0-9]"), + TQRegExp("OpenSSH"), + TQRegExp("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" +}; + +TQRegExp KSshProcess::hostKeyMissingMsg[] = { + TQRegExp("The authenticity of host|No (DSA|RSA) host key is known for"), + TQRegExp("The authenticity of host|No (DSA|RSA) host key is known for"), + TQRegExp("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!" +}; + +TQRegExp KSshProcess::keyFingerprintMsg[] = { + TQRegExp("..(:..){15}"), + TQRegExp("..(:..){15}"), + TQRegExp(".....(-.....){10}") +}; + +TQRegExp KSshProcess::knownHostsFileMsg[] = { + TQRegExp("Add correct host key in (.*) to get rid of this message."), + TQRegExp("Add correct host key in (.*) to get rid of this message."), + TQRegExp("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". +TQRegExp KSshProcess::hostKeyVerifyFailedMsg[] = { + TQRegExp("Host key verification failed\\."), + TQRegExp("Host key verification failed\\."), + TQRegExp("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(TQString::fromLatin1("ssh")); + kdDebug(KSSHPROC) << "KSshProcess::KSshProcess(): ssh path [" << + mSshPath << "]" << endl; + + installSignalHandlers(); +} + +KSshProcess::KSshProcess(TQString 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(TQString pathToSsh) { + mSshPath = pathToSsh; + version(); + if( mVersion == UNKNOWN_VER ) + return false; + + return true; +} + +KSshProcess::SshVersion KSshProcess::version() { + TQString 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'; + TQString 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; +} +/* +TQString KSshProcess::versionStr() { + if( mVersion == UNKNOWN_VER ) { + version(); + if( mVersion == UNKNOWN_VER ) + return TQString::null; + } + + return TQString::fromLatin1(versionStrs[mVersion]); +} +*/ + +bool KSshProcess::setOptions(const SshOptList& opts) { + kdDebug(KSSHPROC) << "KSshProcess::setOptions()" << endl; + mArgs.clear(); + SshOptListConstIterator it; + TQString cmd, subsystem; + mPassword = mUsername = mHost = TQString::null; + TQCString 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 += TQString::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() { + TQValueListIterator<TQCString> it; + for( it = mArgs.begin(); it != mArgs.end(); ++it) { + kdDebug(KSSHPROC) << "arg: " << *it << endl; + } +} + + +int KSshProcess::error(TQString& 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(TQString password) { + kdDebug(KSSHPROC) << "KSshProcess::setPassword(password:xxxxxxxx)" << endl; + mPassword = password; +} + +TQString KSshProcess::getLine() { + static TQStringList buffer; + TQString line = TQString::null; + TQCString 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(TQString(ptyLine)); + } + + if( ! errLine.isEmpty() ) { + buffer.prepend(TQString(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. + // Hack around select() failure on newer systems + unsigned long milliseconds = 0; + while ((buffer.size() == 0) && (milliseconds < (60*1000))) { + //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 TQString::null; + } + else if( ret == -1 ) { + kdDebug(KSSHPROC) << "KSshProcess::connect(): " + << "select error: " << strerror(errno) << endl; + mError = ERR_INTERNAL; + return TQString::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); + if (ptyLine.size() > 0) { + buffer.prepend(TQString(ptyLine)); + } + //kdDebug(KSSHPROC) << "KSshProcess::getLine(): " + // "line from pty -" << ptyLine << endl; + } + + if( FD_ISSET(errfd, &rfds) ) { + errLine = ssh.readLineFromStderr(false); + if (errLine.size() > 0) { + buffer.prepend(TQString(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; + } + + if (buffer.size() == 0) { + milliseconds++; + usleep(1000); + } + } + } + + // 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; + + TQString line; // a line from ssh + TQString 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 = TQString::null; + mKnownHostsFile = TQString::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(TQString::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(TQString) + // 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(TQChar('X')); + + // Set the password to null so we will request another + // password if this one fails. + mPassword = TQString::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(TQString) + // 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(TQChar('X')); + + // Set the password to null so we will request another + // password if this one fails. + mPassword = TQString::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/tdeioslave/sftp/ksshprocess.h b/tdeioslave/sftp/ksshprocess.h new file mode 100644 index 000000000..5130628e4 --- /dev/null +++ b/tdeioslave/sftp/ksshprocess.h @@ -0,0 +1,623 @@ +/*************************************************************************** + ksshprocess.h - description + ------------------- + begin : Tue Jul 31 2001 + copyright : (C) 2001 by Lucas Fisher + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <tqvaluelist.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; + * TQString 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 ([email protected]) + */ + class SshOpt { + public: + TQ_UINT32 opt; + TQString str; + TQ_INT32 num; + bool boolean; + }; + + /** + * List of SshOptions and associated iterators + */ + typedef TQValueList<SshOpt> SshOptList; + typedef TQValueListIterator<SshOpt> SshOptListIterator; + typedef TQValueListConstIterator<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(TQString 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(TQString 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 + */ + //TQString 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(TQString& msg); + + /** + * Get the last error encountered by KSshProcess. + * @return The error number. See SshError for descriptions. + */ + int error() { return mError; } + + TQString 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(TQString 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. + */ + TQString 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. + */ + TQString mPassword; + + /** + * User's username. + */ + TQString mUsername; + + /** + * Name of host we are connecting to. + */ + TQString 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. + */ + TQString mKeyFingerprint; + + /** + * The location of the known host key file. We grab this from + * any error messages ssh prints out. + */ + TQString 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. + */ + TQString 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(); + + TQString getLine(); + + static TQRegExp versionStrs[]; + static const char * const passwordPrompt[]; + static const char * const passphrasePrompt[]; + static const char * const authSuccessMsg[]; + static const char * const authFailedMsg[]; + static TQRegExp hostKeyMissingMsg[]; + static const char * const hostKeyChangedMsg[]; + static const char * const continuePrompt[]; + static const char * const hostKeyAcceptedMsg[]; + static const char * const tryAgainMsg[]; + static TQRegExp hostKeyVerifyFailedMsg[]; + static const char * const connectionClosedMsg[]; + static const char * const changeHostKeyOnDiskPrompt[]; + static TQRegExp keyFingerprintMsg[]; + static TQRegExp knownHostsFileMsg[]; +}; +#endif diff --git a/tdeioslave/sftp/ksshprocesstest.cpp b/tdeioslave/sftp/ksshprocesstest.cpp new file mode 100644 index 000000000..59dbf58c7 --- /dev/null +++ b/tdeioslave/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 = TQString(argv[2]); + opts.append(opt); + + opt.opt = KSshProcess::SSH_USERNAME; + opt.str = TQString(argv[3]); + opts.append(opt); + +// opt.opt = KSshProcess::SSH_PASSWD; +// opt.str = TQString(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(TQString(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/tdeioslave/sftp/process.cpp b/tdeioslave/sftp/process.cpp new file mode 100644 index 000000000..885fe7636 --- /dev/null +++ b/tdeioslave/sftp/process.cpp @@ -0,0 +1,493 @@ +/* vi: ts=8 sts=4 sw=4 + * + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + * + * This file contains code from TEShell.C of the KDE konsole. + * Copyright (c) 1997,1998 by Lars Doelle <[email protected]> + * + * 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 <tqglobal.h> +#include <tqcstring.h> +#include <tqfile.h> + +#include <kdebug.h> +#include <kstandarddirs.h> + +#include "process.h" +#include <tdesu/tdesu_pty.h> +#include <tdesu/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. + */ + + +TQCString MyPtyProcess::readLineFrom(int fd, TQCString& inbuf, bool block) +{ + int pos; + TQCString 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(TQCString line, bool addnl) +{ + if (!line.isEmpty()) + write(m_Fd, line, line.length()); + if (addnl) + write(m_Fd, "\n", 1); +} + +void MyPtyProcess::unreadLineFrom(TQCString inbuf, TQCString 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(TQCString 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. + TQCString path; + if (command.contains('/')) + path = command; + else + { + TQString file = KStandardDirs::findExe(command); + if (file.isEmpty()) + { + kdError(PTYPROC) << k_lineinfo << command << " not found\n"; + _exit(1); + } + path = TQFile::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) + { + TQCString line = readLine(false); + while (!line.isNull()) + { + if (!m_Exit.isEmpty() && !tqstrnicmp(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/tdeioslave/sftp/process.h b/tdeioslave/sftp/process.h new file mode 100644 index 000000000..215c51ea7 --- /dev/null +++ b/tdeioslave/sftp/process.h @@ -0,0 +1,148 @@ +/* vi: ts=8 sts=4 sw=4 + * + * + * This file is part of the KDE project, module tdesu. + * Copyright (C) 1999,2000 Geert Jansen <[email protected]> + * + * 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 <tqcstring.h> +#include <tqstring.h> +#include <tqstringlist.h> +#include <tqvaluelist.h> + +#define PTYPROC 7120 + +class PTY; +typedef TQValueList<TQCString> 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(TQCString 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. + */ + TQCString readLine(bool block = true) + { return readLineFrom(m_Fd, m_ptyBuf, block); } + + TQCString readLineFromPty(bool block = true) + { return readLineFrom(m_Fd, m_ptyBuf, block); } + + TQCString readLineFromStdout(bool block = true) + { return readLineFrom(m_stdinout, m_stdoutBuf, block); } + + TQCString 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(TQCString 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(TQCString line, bool addNewline = true) + { unreadLineFrom(m_ptyBuf, line, addNewline); } + + void unreadLineFromPty(TQCString line, bool addNewline = true) + { unreadLineFrom(m_ptyBuf, line, addNewline); } + + void unreadLineFromStderr(TQCString line, bool addNewline = true) + { unreadLineFrom(m_stderrBuf, line, addNewline); } + + void unreadLineFromStdout(TQCString 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(TQCString 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; + TQCString m_Command, m_Exit; + +private: + int init(); + int SetupTTY(int fd); + + PTY *m_pPTY; + TQCString m_TTY; + TQCString m_ptyBuf, m_stderrBuf, m_stdoutBuf; + + TQCString readLineFrom(int fd, TQCString& inbuf, bool block); + void unreadLineFrom(TQCString inbuf, TQCString line, bool addnl); + class PtyProcessPrivate; + PtyProcessPrivate *d; +}; + +#endif diff --git a/tdeioslave/sftp/sftp.h b/tdeioslave/sftp/sftp.h new file mode 100644 index 000000000..95518130d --- /dev/null +++ b/tdeioslave/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/tdeioslave/sftp/sftp.protocol b/tdeioslave/sftp/sftp.protocol new file mode 100644 index 000000000..3b915bff8 --- /dev/null +++ b/tdeioslave/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 tdeioslave for sftp +Description[af]='n Kioslave vir sftp +Description[be]=Kioslave для sftp +Description[bg]=tdeioslave за sftp +Description[bn]=এস.এফ.টি.পি-র জন্য একটি tdeioslave +Description[br]=Ur tdeioslave evit sftp +Description[bs]=tdeioslave za SFTP +Description[ca]=Un tdeioslave per a sftp +Description[cs]=Protokol KDE pro sftp +Description[csb]=Plugins dlô procedurë òbsłużënkù sftp +Description[da]=En tdeioslave for sftp +Description[de]=Ein-/Ausgabemodul für das sftp-Protokoll +Description[el]=Ένας tdeioslave για sftp +Description[eo]=kenelsklavo por sftp +Description[es]=Un tdeioslave para sftp +Description[et]=SFTP IO-moodul +Description[eu]=Sftp-rako tdeioslave bat +Description[fa]=یک tdeioslave برای sftp +Description[fi]=Sftp:n liitin +Description[fr]=Un module d'entrées / sorties pour le sftp +Description[fy]=Een tdeioslave Foar sftp +Description[ga]=tdeioslave le haghaidh sftp +Description[gl]=Un tdeioslave para sftp +Description[he]=ממשק tdeioslave עבור sftp +Description[hi]=एसएफटीपी के लिए केआईओस्लेव +Description[hr]=Kioslave za SFTP +Description[hu]=KDE-protokoll az sftp-hez +Description[is]=tdeioslave fyrir sftp +Description[it]=Un tdeioslave per sftp +Description[ja]=sftp のための tdeioslave +Description[kk]=sftp-ке арналған енгізу-шығару модулі +Description[km]=tdeioslave របស់ 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 tdeioslave for sftp +Description[nds]=En In-/Utgaavdeenst för sftpl +Description[ne]=sftp का लागि एउटा किओस्लेभ +Description[nl]=Een tdeioslave voor sftp +Description[nn]=Ein tdeioslave for sftp +Description[pa]=sftp ਲਈ tdeioslave +Description[pl]=Wtyczka protokołu sftp +Description[pt]=Um 'tdeioslave' 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]=tdeioslave ya sftp +Description[se]=ŠO-šláva sftp-protokolla várás +Description[sk]=tdeioslave pre sftp +Description[sl]=tdeioslave 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 tdeioslave +Description[tt]=sftp öçen tdeioslave +Description[uk]=Підлеглий В/В для sftp +Description[uz]=SFTP uchun KCH-sleyv +Description[uz@cyrillic]=SFTP учун КЧ-слейв +Description[vi]=A tdeioslave (đày tớ vào ra KDE) cho sftp +Description[wa]=On tdeioslave po sftp +Description[zh_CN]=sftp 的 tdeioslave +Description[zh_TW]=sftp 的 tdeioslave +DocPath=tdeioslave/sftp.html +Icon=ftp +Class=:internet diff --git a/tdeioslave/sftp/sftpfileattr.cpp b/tdeioslave/sftp/sftpfileattr.cpp new file mode 100644 index 000000000..4c25909d9 --- /dev/null +++ b/tdeioslave/sftp/sftpfileattr.cpp @@ -0,0 +1,346 @@ +/*************************************************************************** + sftpfileattr.cpp - description + ------------------- + begin : Sat Jun 30 2001 + copyright : (C) 2001 by Lucas Fisher + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <tqstring.h> +#include <tqdatastream.h> + +#include <tdeio/global.h> +#include <kremoteencoding.h> + +using namespace TDEIO; + +sftpFileAttr::sftpFileAttr(){ + clear(); + mDirAttrs = false; +} + +sftpFileAttr::sftpFileAttr(KRemoteEncoding* encoding){ + clear(); + mDirAttrs = false; + mEncoding = encoding; +} + + +/** Constructor to initialize the file attributes on declaration. */ +sftpFileAttr::sftpFileAttr(TQ_ULLONG size, uid_t uid, gid_t gid, + mode_t permissions, time_t atime, + time_t mtime, TQ_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 */ +TQDataStream& operator<< (TQDataStream& s, const sftpFileAttr& fa) { + s << (TQ_UINT32)fa.mFlags; + + if( fa.mFlags & SSH2_FILEXFER_ATTR_SIZE ) + { s << (TQ_ULLONG)fa.mSize; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_UIDGID ) + { s << (TQ_UINT32)fa.mUid << (TQ_UINT32)fa.mGid; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_PERMISSIONS ) + { s << (TQ_UINT32)fa.mPermissions; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_ACMODTIME ) + { s << (TQ_UINT32)fa.mAtime << (TQ_UINT32)fa.mMtime; } + + if( fa.mFlags & SSH2_FILEXFER_ATTR_EXTENDED ) { + s << (TQ_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 */ +TQDataStream& operator>> (TQDataStream& s, sftpFileAttr& fa) { + + // XXX Add some error checking in here in case + // we get a bad sftp packet. + + fa.clear(); + + if( fa.mDirAttrs ) { + TQCString 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 ) { + TQ_ULLONG fileSize; + s >> fileSize; + fa.setFileSize(fileSize); + } + + TQ_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. + TQString 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(); + + TQString 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 = TQString::null; + mGroupName = TQString::null; + mUserName = TQString::null; + mLinkDestination = TQString::null; + mFlags = 0; + mLongname = "\0"; + mLinkType = 0; +} + +/** Return the size of the sftp attribute. */ +TQ_UINT32 sftpFileAttr::size() const{ + TQ_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/tdeioslave/sftp/sftpfileattr.h b/tdeioslave/sftp/sftpfileattr.h new file mode 100644 index 000000000..43b56979d --- /dev/null +++ b/tdeioslave/sftp/sftpfileattr.h @@ -0,0 +1,261 @@ +/*************************************************************************** + sftpfileattr.h - description + ------------------- + begin : Sat Jun 30 2001 + copyright : (C) 2001 by Lucas Fisher + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <tqglobal.h> +#include <tqstring.h> +#include <tqdatastream.h> + +#include <tdeio/global.h> +#include <kdebug.h> + +#include "sftp.h" + +/** + *@author Lucas Fisher + */ + +class KRemoteEncoding; + +class sftpFileAttr { + +private: // Private attributes + /** Name of file. */ + TQString mFilename; + + /** Specifies which fields of the file attribute are available. */ + TQ_UINT32 mFlags; + + /** Size of the file in bytes. Should be 64 bit safe. */ + TQ_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 */ + TQ_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. */ + TQCString mLongname; + + TQString mUserName; + TQString mGroupName; + + /** If file is a link, contains the destination of the link */ + TQString 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(TQ_ULLONG size_, uid_t uid_, gid_t gid_, mode_t permissions_, + time_t atime_, time_t mtime_, TQ_UINT32 extendedCount_ = 0); + + /** Return the size of the sftp attribute not including filename or longname*/ + TQ_UINT32 size() const; + + /** Clear all attributes and flags. */ + void clear(); + + /** Set the size of the file. */ + void setFileSize(TQ_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. */ + TQ_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(TQString 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'. */ + TQString longname() const { return mLongname; } + + void setLinkDestination(const TQString& target) + { mLinkDestination = target; } + + TQString 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 TQString& fn) + { mFilename = fn; } + + TQString filename() const + { return mFilename; } + + /** Returns a UDSEntry describing the file. + The UDSEntry is generated from the sftp file attributes. */ + TDEIO::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 TQDataStream& operator<< (TQDataStream&, 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 TQDataStream& operator>> (TQDataStream&, 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/tdeioslave/smb/CMakeLists.txt b/tdeioslave/smb/CMakeLists.txt new file mode 100644 index 000000000..2493c2a85 --- /dev/null +++ b/tdeioslave/smb/CMakeLists.txt @@ -0,0 +1,43 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include( ConfigureChecks.cmake ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} + ${SMBCLIENT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES smb.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) +install( FILES smb-network.desktop DESTINATION ${DATA_INSTALL_DIR}/konqueror/dirtree/remote ) +install( FILES smb-network.desktop DESTINATION ${DATA_INSTALL_DIR}/remoteview ) +install( FILES x-smb-workgroup.desktop x-smb-server.desktop DESTINATION ${MIME_INSTALL_DIR}/application ) + + +##### kio_smb (module) ########################## + +tde_add_kpart( kio_smb AUTOMOC + 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 + LINK tdeio-shared ${SMBCLIENT_LIBRARIES} + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/smb/ConfigureChecks.cmake b/tdeioslave/smb/ConfigureChecks.cmake new file mode 100644 index 000000000..da4d1a942 --- /dev/null +++ b/tdeioslave/smb/ConfigureChecks.cmake @@ -0,0 +1,26 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +pkg_search_module ( SMBCLIENT smbclient ) +if( SMBCLIENT_FOUND ) + set( HAVE_LIBSMBCLIENT_H 1 ) +else( ) + check_include_file( libsmbclient.h HAVE_LIBSMBCLIENT_H ) +endif( ) + +if( HAVE_LIBSMBCLIENT_H ) + set( SMBCLIENT_LIBRARIES smbclient ) + check_library_exists( ${SMBCLIENT_LIBRARIES} smbc_new_context "" HAVE_SMBCLIENT ) +endif( ) + +if( NOT HAVE_LIBSMBCLIENT_H OR NOT HAVE_SMBCLIENT ) + tde_message_fatal( "smbclient is requested, but was not found on your system." ) +endif( ) diff --git a/tdeioslave/smb/Makefile.am b/tdeioslave/smb/Makefile.am new file mode 100644 index 000000000..97a4034da --- /dev/null +++ b/tdeioslave/smb/Makefile.am @@ -0,0 +1,37 @@ +## Makefile.am of tdebase/tdeioslave/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)/tdeio_smb.pot diff --git a/tdeioslave/smb/configure.in.bot b/tdeioslave/smb/configure.in.bot new file mode 100644 index 000000000..a8a95566c --- /dev/null +++ b/tdeioslave/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 tdeioslave/smb/libsmbclient-HOWTO.txt" + echo "" + all_tests=bad +fi diff --git a/tdeioslave/smb/configure.in.in b/tdeioslave/smb/configure.in.in new file mode 100644 index 000000000..59b8272c8 --- /dev/null +++ b/tdeioslave/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_tdeioslave_smb, test "x$have_libsmbclient" = xyes) + +]) + +SMB_CHECK diff --git a/tdeioslave/smb/kio_smb.cpp b/tdeioslave/smb/kio_smb.cpp new file mode 100644 index 000000000..193df965f --- /dev/null +++ b/tdeioslave/smb/kio_smb.cpp @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: Top level implementation file for kio_smb.cpp +// +// Abstract: member function implementations for SMBSlave +// +// Author(s): Matthew Peterson <[email protected]> +// +//--------------------------------------------------------------------------- +// +// 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 TQCString& pool, const TQCString& 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 ) +{ + + TDEInstance 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/tdeioslave/smb/kio_smb.h b/tdeioslave/smb/kio_smb.h new file mode 100644 index 000000000..34ad0200a --- /dev/null +++ b/tdeioslave/smb/kio_smb.h @@ -0,0 +1,301 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave 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 <[email protected]> +// +//--------------------------------------------------------------------------- +// +// 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 <tqstring.h> +#include <tqptrlist.h> +#include <tqstringlist.h> +#include <tqtextstream.h> +#include <tqstrlist.h> + +//-------------- +// KDE includes +//-------------- +#include <kdebug.h> +#include <kinstance.h> +#include <tdeio/global.h> +#include <tdeio/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 <tqobject.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 TDEIO; +class TDEProcess; + +//=========================================================================== + + +class SMBSlave : public TQObject, public TDEIO::SlaveBase +{ + Q_OBJECT + +private: + //--------------------------------------------------------------------- + // please make sure your private data does not duplicate existing data + //--------------------------------------------------------------------- + bool m_initialized_smbc; + + /** + * From Controlcenter + */ + TQString m_default_user; +// TQString m_default_workgroup; //currently unused, Alex <[email protected]> + TQString m_default_password; + TQString 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 <[email protected]> + + /** + * 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 TQCString& pool, const TQCString& 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 TQString& host, int port, const TQString& user, const TQString& pass); + //virtual void openConnection(); + //virtual void closeConnection(); + //virtual void slave_status(); + virtual void special( const TQByteArray & ); + +private slots: + void readOutput(TDEProcess *proc, char *buffer, int buflen); + void readStdErr(TDEProcess *proc, char *buffer, int buflen); + +private: + TQString 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/tdeioslave/smb/kio_smb_auth.cpp b/tdeioslave/smb/kio_smb_auth.cpp new file mode 100644 index 000000000..f11d80b3a --- /dev/null +++ b/tdeioslave/smb/kio_smb_auth.cpp @@ -0,0 +1,207 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: kio_smb_auth.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB directory access +// +// Author(s): Matthew Peterson <[email protected]> +// +//--------------------------------------------------------------------------- +// +// 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 <tqdir.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; + + TQString s_server = TQString::fromUtf8(server); + TQString s_share = TQString::fromUtf8(share); + workgroup[wgmaxlen - 1] = 0; + TQString s_workgroup = TQString::fromUtf8(workgroup); + username[unmaxlen - 1] = 0; + TQString s_username = TQString::fromUtf8(username); + password[pwmaxlen - 1] = 0; + TQString s_password = TQString::fromUtf8(password); + + TDEIO::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 = TQString::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; + + TDEIO::AuthInfo info; + info.url = KURL("smb:///"); + info.url.setHost(url.host()); + + TQString 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); + url.setPass(info.password); + 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( "tdeioslaverc", 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/tdeioslave/smb/kio_smb_browse.cpp b/tdeioslave/smb/kio_smb_browse.cpp new file mode 100644 index 000000000..26c379bde --- /dev/null +++ b/tdeioslave/smb/kio_smb_browse.cpp @@ -0,0 +1,476 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: kio_smb_browse.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB browsing +// +// Author(s): Matthew Peterson <[email protected]> +// +//--------------------------------------------------------------------------- +// +// 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 <tqtextcodec.h> + +#include <kglobal.h> + +#include "kio_smb.h" +#include "kio_smb_internal.h" + +using namespace TDEIO; + +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 " << (TDEIO::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 = TDEIO::UDS_FILE_TYPE; + udsatom.m_long = st.st_mode & S_IFMT; + udsentry.append(udsatom); + + udsatom.m_uds = TDEIO::UDS_SIZE; + udsatom.m_long = st.st_size; + udsentry.append(udsatom); + + udsatom.m_uds = TDEIO::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 = TQString::number( uid ); + udsentry.append(udsatom); + + udsatom.m_uds = TDEIO::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 = TQString::number( gid ); + udsentry.append(udsatom); + + udsatom.m_uds = TDEIO::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 = TDEIO::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 = TDEIO::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; + TQString 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)); + TQString 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(TQString::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 = TDEIO::UDS_NAME; + TQString dirpName = TQString::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 + TQString comment = TQString::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 = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + udsentry.append( atom ); + + // Set permissions + atom.m_uds = TDEIO::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 = TDEIO::UDS_URL; + // TQString workgroup = m_current_url.host().upper(); + KURL u("smb:/"); + u.setHost(dirpName); + atom.m_str = u.url(); + + // when libsmbclient knows + // atom.m_str = TQString("smb://%1?WORKGROUP=%2").arg(dirpName).arg(workgroup.upper()); + kdDebug(KIO_SMB) << "list item " << atom.m_str << endl; + udsentry.append(atom); + + atom.m_uds = TDEIO::UDS_MIME_TYPE; + atom.m_str = TQString::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 = TDEIO::UDS_FILE_TYPE; + atom.m_long = S_IFDIR; + udsentry.append( atom ); + + // Set permissions + atom.m_uds = TDEIO::UDS_ACCESS; + atom.m_long = (S_IRUSR | S_IRGRP | S_IROTH | S_IXUSR | S_IXGRP | S_IXOTH); + udsentry.append(atom); + + atom.m_uds = TDEIO::UDS_MIME_TYPE; + atom.m_str = TQString::fromLatin1("application/x-smb-workgroup"); + udsentry.append(atom); + + atom.m_uds = TDEIO::UDS_URL; + // TQString 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/tdeioslave/smb/kio_smb_config.cpp b/tdeioslave/smb/kio_smb_config.cpp new file mode 100644 index 000000000..8179371e7 --- /dev/null +++ b/tdeioslave/smb/kio_smb_config.cpp @@ -0,0 +1,66 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: kio_smb_config.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// KDE/SMB slave configuration +// +// Author(s): Matthew Peterson <[email protected]> +// +//--------------------------------------------------------------------------- +// +// 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 <tdeconfig.h> + +#include <tqtextcodec.h> +//=========================================================================== +void SMBSlave::reparseConfiguration() +{ + TDEConfig *cfg = new TDEConfig("tdeioslaverc", true); + cfg->setGroup("Browser Settings/SMBro"); + m_default_user=cfg->readEntry("User"); +// m_default_workgroup=cfg->readEntry("Workgroup"); +// m_showHiddenShares=cfg->readBoolEntry("ShowHiddenShares",false); + + TQString m_encoding = TQTextCodec::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 + TQString scrambled = cfg->readEntry( "Password" ); + m_default_password = ""; + for (uint i=0; i<scrambled.length()/3; i++) + { + TQChar qc1 = scrambled[i*3]; + TQChar qc2 = scrambled[i*3+1]; + TQChar 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] = TQChar((uchar)((num - 17) ^ 173)); // restore + } + + delete cfg; +} diff --git a/tdeioslave/smb/kio_smb_dir.cpp b/tdeioslave/smb/kio_smb_dir.cpp new file mode 100644 index 000000000..02dfc8100 --- /dev/null +++ b/tdeioslave/smb/kio_smb_dir.cpp @@ -0,0 +1,345 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: kio_smb_dir.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB directory access +// +// Author(s): Matthew Peterson <[email protected]> +// +////--------------------------------------------------------------------------- +// +// 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; + TDEIO::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( TDEIO::ERR_ACCESS_DENIED, src.prettyURL()); + } + else + { + error( TDEIO::ERR_DOES_NOT_EXIST, src.prettyURL()); + } + return; + } + if ( S_ISDIR( st.st_mode ) ) + { + error( TDEIO::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( TDEIO::ERR_DIR_ALREADY_EXIST, dst.prettyURL()); + return; + } + if(!overwrite) + { + error( TDEIO::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( TDEIO::ERR_ACCESS_DENIED, src.prettyURL() ); + } + else + { + error( TDEIO::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(TDEIO::ERR_WRITE_ACCESS_DENIED, dst.prettyURL()); + } + else + { + error(TDEIO::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 TDEIO::ERR_COULD_NOT_WRITE" << endl; + error( TDEIO::ERR_COULD_NOT_WRITE, dst.prettyURL()); + break; + } + + processed_size += n; + processedSize(processed_size); + } + else if(n == 0) + { + break; // finished + } + else + { + error( TDEIO::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( TDEIO::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( TDEIO::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( TDEIO::ERR_DIR_ALREADY_EXIST, m_current_url.prettyURL()); + } + } + else + { + error( TDEIO::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 TDEIO::ERR_DIR_ALREADY_EXIST" << endl; + error( TDEIO::ERR_DIR_ALREADY_EXIST, dst.prettyURL()); + finished(); + return; + } + if(!overwrite) + { + kdDebug(KIO_SMB) << "SMBSlave::rename TDEIO::ERR_FILE_ALREADY_EXIST" << endl; + error( TDEIO::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 TDEIO::ERR_ACCESS_DENIED" << endl; + error(TDEIO::ERR_ACCESS_DENIED, src.prettyURL()); + } + else + { + kdDebug(KIO_SMB) << "SMBSlave::rename TDEIO::ERR_DOES_NOT_EXIST" << endl; + error(TDEIO::ERR_DOES_NOT_EXIST, src.prettyURL()); + } + } + break; + + case EACCES: + case EPERM: + kdDebug(KIO_SMB) << "SMBSlave::rename TDEIO::ERR_ACCESS_DENIED" << endl; + error( TDEIO::ERR_ACCESS_DENIED, dst.prettyURL() ); + break; + + default: + kdDebug(KIO_SMB) << "SMBSlave::rename TDEIO::ERR_CANNOT_RENAME" << endl; + error( TDEIO::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/tdeioslave/smb/kio_smb_file.cpp b/tdeioslave/smb/kio_smb_file.cpp new file mode 100644 index 000000000..b65f79f42 --- /dev/null +++ b/tdeioslave/smb/kio_smb_file.cpp @@ -0,0 +1,279 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: kio_smb_file.cpp +// +// Abstract: member function implementations for SMBSlave that deal with +// SMB file access +// +// Author(s): Matthew Peterson <[email protected]> +// +//--------------------------------------------------------------------------- +// +// 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; + TDEIO::filesize_t totalbytesread = 0; + TQByteArray 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( TDEIO::ERR_ACCESS_DENIED, url.prettyURL()); + else + error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL()); + return; + } + if ( S_ISDIR( st.st_mode ) ) { + error( TDEIO::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( TDEIO::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( TQByteArray() ); + processedSize(static_cast<TDEIO::filesize_t>(st.st_size)); + + } + else + { + error( TDEIO::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; + TQByteArray 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( TDEIO::ERR_DIR_ALREADY_EXIST, m_current_url.prettyURL()); + } + else + { + kdDebug(KIO_SMB) << "SMBSlave::put on " << kurl <<" already exist !!"<< endl; + error( TDEIO::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( TDEIO::ERR_WRITE_ACCESS_DENIED, m_current_url.prettyURL()); + } + else + { + kdDebug(KIO_SMB) << "SMBSlave::put error " << kurl <<" can not open for writing !!"<< endl; + error( TDEIO::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( TDEIO::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( TDEIO::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/tdeioslave/smb/kio_smb_internal.cpp b/tdeioslave/smb/kio_smb_internal.cpp new file mode 100644 index 000000000..8f56a8f62 --- /dev/null +++ b/tdeioslave/smb/kio_smb_internal.cpp @@ -0,0 +1,135 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: kio_smb_internal.cpp +// +// Abstract: Utility class implementation used by SMBSlave +// +// Author(s): Matthew Peterson <[email protected]> +// +//--------------------------------------------------------------------------- +// +// 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 <tqtextcodec.h> + +#include <tdeconfig.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 TQString &filedir) +{ + KURL::addPath(filedir); + updateCache(); +} + +//----------------------------------------------------------------------- +bool SMBUrl::cd(const TQString &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 { + TQString 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/tdeioslave/smb/kio_smb_internal.h b/tdeioslave/smb/kio_smb_internal.h new file mode 100644 index 000000000..2d97aee31 --- /dev/null +++ b/tdeioslave/smb/kio_smb_internal.h @@ -0,0 +1,118 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project: SMB tdeioslave for KDE2 +// +// File: kio_smb_internal.h +// +// Abstract: Utility classes used by SMBSlave +// +// Author(s): Matthew Peterson <[email protected]> +// Frank Schwanz <[email protected]> +//--------------------------------------------------------------------------- +// +// 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 <tdeio/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 TQString &filedir); + + bool cd(const TQString &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 TQString& _txt ) { KURL::setPass(_txt); updateCache(); } + void setUser( const TQString& _txt ) { KURL::setUser(_txt); updateCache(); } + void setHost( const TQString& _txt ) { KURL::setHost(_txt); updateCache(); } + + /** + * Returns the workgroup if it given in url + */ +// TQString getWorkgroup() const; + + /** + * Returns path after workgroup + */ +// TQString getServerShareDir() const; + + /** + * Return a URL that is suitable for libsmbclient + */ + TQCString toSmbcUrl() const { return m_surl; } + +private: + /** + * Change from TQString to TQCString (MS Windows's character encoding) + */ + TQCString fromUnicode( const TQString &_str ) const; + + void updateCache(); + TQCString m_surl; + + /** + * Type of URL + * @see _SMBUrlType + */ + mutable SMBUrlType m_type; +}; + + +#endif + diff --git a/tdeioslave/smb/kio_smb_mount.cpp b/tdeioslave/smb/kio_smb_mount.cpp new file mode 100644 index 000000000..ad421f631 --- /dev/null +++ b/tdeioslave/smb/kio_smb_mount.cpp @@ -0,0 +1,211 @@ +/* This file is part of the KDE project + + Copyright (C) 2000 Alexander Neundorf <[email protected]> + + 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 <tqcstring.h> +#include <unistd.h> +#include <tqdir.h> +#include <kprocess.h> + +void SMBSlave::readOutput(TDEProcess *, char *buffer, int buflen) +{ + mybuf += TQString::fromLocal8Bit(buffer, buflen); +} + +void SMBSlave::readStdErr(TDEProcess *, char *buffer, int buflen) +{ + mystderr += TQString::fromLocal8Bit(buffer, buflen); +} + +void SMBSlave::special( const TQByteArray & data) +{ + kdDebug(KIO_SMB)<<"Smb::special()"<<endl; + int tmp; + TQDataStream 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: + { + TQString remotePath, mountPoint, user; + stream >> remotePath >> mountPoint; + + TQStringList sl=TQStringList::split("/",remotePath); + TQString 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(TDEIO::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 + + TDEProcess proc; + proc.setUseShell(true); // to have the path to smbmnt (which is used by smbmount); see man smbmount + proc << "smbmount"; + + TQString options; + + if ( smburl.user().isEmpty() ) + { + user = "guest"; + options = "-o guest"; + } + else + { + options = "-o username=" + TDEProcess::quote(smburl.user()); + user = smburl.user(); + + if ( ! smburl.pass().isEmpty() ) + options += ",password=" + TDEProcess::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=" + TDEProcess::quote(m_default_encoding); + + proc << TDEProcess::quote(remotePath.local8Bit()); + proc << TDEProcess::quote(mountPoint.local8Bit()); + proc << options; + + connect(&proc, TQT_SIGNAL( receivedStdout(TDEProcess *, char *, int )), + TQT_SLOT(readOutput(TDEProcess *, char *, int))); + + connect(&proc, TQT_SIGNAL( receivedStderr(TDEProcess *, char *, int )), + TQT_SLOT(readStdErr(TDEProcess *, char *, int))); + + if (!proc.start( TDEProcess::Block, TDEProcess::AllOutput )) + { + error(TDEIO::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( TDEIO::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: + { + TQString mountPoint; + stream >> mountPoint; + + TDEProcess proc; + proc.setUseShell(true); + proc << "smbumount"; + proc << TDEProcess::quote(mountPoint); + + mybuf.truncate(0); + mystderr.truncate(0); + + connect(&proc, TQT_SIGNAL( receivedStdout(TDEProcess *, char *, int )), + TQT_SLOT(readOutput(TDEProcess *, char *, int))); + + connect(&proc, TQT_SIGNAL( receivedStderr(TDEProcess *, char *, int )), + TQT_SLOT(readStdErr(TDEProcess *, char *, int))); + + if ( !proc.start( TDEProcess::Block, TDEProcess::AllOutput ) ) + { + error(TDEIO::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(TDEIO::ERR_COULD_NOT_UNMOUNT, + i18n("Unmounting of mountpoint \"%1\" failed.\n%2") + .arg(mountPoint).arg(mybuf + "\n" + mystderr)); + return; + } + + if ( tmp == 4 ) + { + bool ok; + + TQDir dir(mountPoint); + dir.cdUp(); + ok = dir.rmdir(mountPoint); + if ( ok ) + { + TQString p=dir.path(); + dir.cdUp(); + ok = dir.rmdir(p); + } + + if ( !ok ) + { + error(TDEIO::ERR_COULD_NOT_RMDIR, mountPoint); + return; + } + } + + finished(); + } + break; + default: + break; + } + finished(); +} + +#include "kio_smb.moc" diff --git a/tdeioslave/smb/libsmbclient-HOWTO.txt b/tdeioslave/smb/libsmbclient-HOWTO.txt new file mode 100644 index 000000000..65ed7d086 --- /dev/null +++ b/tdeioslave/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:[email protected]:/cvsroot login + use "cvs" as the password + cvs -z5 -d :pserver:[email protected]:/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/tdeioslave/smb/smb-network.desktop b/tdeioslave/smb/smb-network.desktop new file mode 100644 index 000000000..de5406775 --- /dev/null +++ b/tdeioslave/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/tdeioslave/smb/smb.protocol b/tdeioslave/smb/smb.protocol new file mode 100644 index 000000000..2c0bda020 --- /dev/null +++ b/tdeioslave/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=tdeioslave/smb.html +Icon=samba diff --git a/tdeioslave/smb/x-smb-server.desktop b/tdeioslave/smb/x-smb-server.desktop new file mode 100644 index 000000000..6990e1634 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/smb/x-smb-workgroup.desktop b/tdeioslave/smb/x-smb-workgroup.desktop new file mode 100644 index 000000000..2b6d571b9 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/smtp/CMakeLists.txt b/tdeioslave/smtp/CMakeLists.txt new file mode 100644 index 000000000..57095a364 --- /dev/null +++ b/tdeioslave/smtp/CMakeLists.txt @@ -0,0 +1,37 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES smtp.protocol smtps.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_smtp (module) ######################### + +set( target kio_smtp ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES smtp.cc request.cc response.cc capabilities.cc command.cc transactionstate.cc + LINK tdeio-shared ${SASL_LIBRARIES} + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/smtp/Makefile.am b/tdeioslave/smtp/Makefile.am new file mode 100644 index 000000000..7808a24db --- /dev/null +++ b/tdeioslave/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_TDECORE) +test_headergeneration_LDFLAGS = $(all_libraries) + +test_responseparser_SOURCES = test_responseparser.cc +test_responseparser_LDADD = $(LIB_TDECORE) +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)/tdeio_smtp.pot diff --git a/tdeioslave/smtp/TODO b/tdeioslave/smtp/TODO new file mode 100644 index 000000000..cad79f139 --- /dev/null +++ b/tdeioslave/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/tdeioslave/smtp/capabilities.cc b/tdeioslave/smtp/capabilities.cc new file mode 100644 index 000000000..bf91071b3 --- /dev/null +++ b/tdeioslave/smtp/capabilities.cc @@ -0,0 +1,143 @@ +/* -*- c++ -*- + capabilities.cc + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tqstrlist.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 TQString & cap, bool replace ) { + TQStringList tokens = TQStringList::split( ' ', cap.upper() ); + if ( tokens.empty() ) + return; + TQString name = tokens.front(); tokens.pop_front(); + add( name, tokens, replace ); + } + + void Capabilities::add( const TQString & name, const TQStringList & args, bool replace ) { + if ( replace ) + mCapabilities[name] = args; + else + mCapabilities[name] += args; + } + + TQString Capabilities::asMetaDataString() const { + TQString result; + for ( TQMap<TQString,TQStringList>::const_iterator it = mCapabilities.begin() ; it != mCapabilities.end() ; ++it ) { + result += it.key(); + if ( !it.data().empty() ) + result += ' ' + it.data().join( " " ); + result += '\n'; + } + return result; + } + + TQString Capabilities::authMethodMetaData() const { + TQStringList sl = saslMethodsQSL(); + TQString result; + for ( TQStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) + result += "SASL/" + *it + '\n'; + return result; + } + + TQStrIList Capabilities::saslMethods() const { + TQStrIList result( true ); // deep copies to be safe + TQStringList sl = saslMethodsQSL(); + for ( TQStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it ) + result.append( (*it).latin1() ); + return result; + } + + TQString Capabilities::createSpecialResponse( bool tls ) const { + TQStringList 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=" + TQString::number( size ) ); // fixed max + else + result.push_back( "SIZE" ); // indetermined + } + return result.join( " " ); + } + + TQStringList Capabilities::saslMethodsQSL() const { + TQStringList result; + for ( TQMap<TQString,TQStringList>::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( tqstrlen("AUTH=") ) ); + result += it.data(); + } + } + result.sort(); + TQStringList::iterator it = result.begin(); + for (TQStringList::iterator ot = it++; it != result.end(); ot = it++) + if (*ot == *it) result.remove(ot); + return result; + } + + + +} // namespace KioSMTP + diff --git a/tdeioslave/smtp/capabilities.h b/tdeioslave/smtp/capabilities.h new file mode 100644 index 000000000..5004cbaac --- /dev/null +++ b/tdeioslave/smtp/capabilities.h @@ -0,0 +1,77 @@ +/* -*- c++ -*- + capabilities.h + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tqmap.h> +#include <tqcstring.h> +#include <tqstring.h> +#include <tqstringlist.h> + +class TQStrIList; + +namespace KioSMTP { + + class Response; + + class Capabilities { + public: + Capabilities() {} + + static Capabilities fromResponse( const Response & response ); + + void add( const TQString & cap, bool replace=false ); + void add( const TQString & name, const TQStringList & args, bool replace=false ); + void clear() { mCapabilities.clear(); } + + bool have( const TQString & cap ) const { + return mCapabilities.find( cap.upper() ) != mCapabilities.end(); + } + bool have( const TQCString & cap ) const { return have( TQString( cap.data() ) ); } + bool have( const char * cap ) const { return have( TQString::fromLatin1( cap ) ); } + + TQString asMetaDataString() const; + + TQString authMethodMetaData() const; + TQStrIList saslMethods() const; + + TQString createSpecialResponse( bool tls ) const; + + TQStringList saslMethodsQSL() const; + private: + + TQMap<TQString,TQStringList> mCapabilities; + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_CAPABILITIES_H__ diff --git a/tdeioslave/smtp/command.cc b/tdeioslave/smtp/command.cc new file mode 100644 index 000000000..3817d5fd6 --- /dev/null +++ b/tdeioslave/smtp/command.cc @@ -0,0 +1,606 @@ +/* -*- c++ -*- + command.cc + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tdeio/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 TQCString &, 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 + // + + TQCString 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( TDEIO::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( TDEIO::ERR_UNKNOWN, + i18n("Unexpected server response to %1 command.\n%2") + .arg( mEHLONotSupported ? "HELO" : "EHLO" ) + .arg( r.errorMessage() ) ); + return false; + } + + // + // STARTTLS - rfc 3207 + // + + TQCString 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(TDEIO::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(TDEIO::ERR_COULD_NOT_AUTHENTICATE, \ + i18n("An error occured during authentication: %1").arg \ + ( TQString::fromUtf8( sasl_errdetail( conn ) ))); + + // + // AUTH - rfc 2554 + // + AuthCommand::AuthCommand( SMTPProtocol * smtp, + const char *mechanisms, + const TQString &aFQDN, + TDEIO::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(TDEIO::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(TDEIO::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 TQCString & s, TransactionState * ) { + mUngetSASLResponse = s; + mComplete = false; + } + + TQCString AuthCommand::nextCommandLine( TransactionState * ) { + mNeedResponse = true; + TQCString cmd; +#ifdef HAVE_LIBSASL2 + TQByteArray tmp, challenge; + if ( !mUngetSASLResponse.isNull() ) { + // implement un-ungetCommandLine + cmd = mUngetSASLResponse; + mUngetSASLResponse = 0; + } else if ( mFirstTime ) { + TQString firstCommand = "AUTH " + TQString::fromLatin1( mMechusing ); + + tmp.setRawData( mOut, mOutlen ); + KCodecs::base64Encode( tmp, challenge ); + tmp.resetRawData( mOut, mOutlen ); + if ( !challenge.isEmpty() ) { + firstCommand += " "; + firstCommand += TQString::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( TDEIO::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( TDEIO::ERR_COULD_NOT_LOGIN, + i18n("Your SMTP server does not support authentication.\n" + " %2").arg( r.errorMessage() ) ); + else + mSMTP->error( TDEIO::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: + // + + TQCString MailFromCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + TQCString cmdLine = "MAIL FROM:<" + mAddr + '>'; + if ( m8Bit && haveCapability("8BITMIME") ) + cmdLine += " BODY=8BITMIME"; + if ( mSize && haveCapability("SIZE") ) + cmdLine += " SIZE=" + TQCString().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: + // + + TQCString 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!) + // + + TQCString DataCommand::nextCommandLine( TransactionState * ts ) { + assert( ts ); + mComplete = true; + mNeedResponse = true; + ts->setDataCommandIssued( true ); + return "DATA\r\n"; + } + + void DataCommand::ungetCommandLine( const TQCString &, 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 TQCString & 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(); + } + + TQCString TransferCommand::nextCommandLine( TransactionState * ts ) { + assert( ts ); // let's rely on it ( at least for the moment ) + assert( !isComplete() ); + assert( !ts->failed() ); + + static const TQCString dotCRLF = ".\r\n"; + static const TQCString CRLFdotCRLF = "\r\n.\r\n"; + + if ( !mUngetBuffer.isEmpty() ) { + const TQCString 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(); + TQByteArray ba; + int result = mSMTP->readData( ba ); + kdDebug(7112) << "got " << result << " bytes" << endl; + if ( result > 0 ) + return prepare( ba ); + else if ( result < 0 ) { + ts->setFailedFatally( TDEIO::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 TQCString dotstuff_lf2crlf( const TQByteArray & ba, char & last ) { + TQCString 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; + } + + TQCString TransferCommand::prepare( const TQByteArray & 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 TQCString( ba.data(), ba.size() + 1 ); + } + } + + // + // NOOP + // + + TQCString NoopCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "NOOP\r\n"; + } + + // + // RSET + // + + TQCString RsetCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "RSET\r\n"; + } + + // + // QUIT + // + + TQCString QuitCommand::nextCommandLine( TransactionState * ) { + mComplete = true; + mNeedResponse = true; + return "QUIT\r\n"; + } + +} // namespace KioSMTP + diff --git a/tdeioslave/smtp/command.h b/tdeioslave/smtp/command.h new file mode 100644 index 000000000..48d08df22 --- /dev/null +++ b/tdeioslave/smtp/command.h @@ -0,0 +1,283 @@ +/* -*- c++ -*- + command.h + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tqstring.h> +#include <tqcstring.h> + +#ifdef HAVE_LIBSASL2 +extern "C" { +#include <sasl/sasl.h> +} +#endif + +#include <tdeio/authinfo.h> + +class SMTPProtocol; +class TQStrIList; + +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 TQCString 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 TQCString & 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 TQString & hostname ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ), + mEHLONotSupported( false ), + mHostname( hostname.stripWhiteSpace() ) {} + + TQCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + private: + bool mEHLONotSupported; + TQString mHostname; + }; + + class StartTLSCommand : public Command { + public: + StartTLSCommand( SMTPProtocol * smtp ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {} + + TQCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + }; + + class AuthCommand : public Command { + public: + AuthCommand( SMTPProtocol * smtp, const char *mechanisms, + const TQString &aFQDN, TDEIO::AuthInfo &ai ); + ~AuthCommand(); + bool doNotExecute( const TransactionState * ts ) const; + TQCString nextCommandLine( TransactionState * ); + void ungetCommandLine( const TQCString & 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; + + TDEIO::AuthInfo *mAi; + TQCString mLastChallenge; + TQCString mUngetSASLResponse; + bool mFirstTime; + }; + + class MailFromCommand : public Command { + public: + MailFromCommand( SMTPProtocol * smtp, const TQCString & addr, + bool eightBit=false, unsigned int size=0 ) + : Command( smtp ), mAddr( addr ), m8Bit( eightBit ), mSize( size ) {} + + TQCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + private: + TQCString mAddr; + bool m8Bit; + unsigned int mSize; + }; + + class RcptToCommand : public Command { + public: + RcptToCommand( SMTPProtocol * smtp, const TQCString & addr ) + : Command( smtp ), mAddr( addr ) {} + + TQCString nextCommandLine( TransactionState * ); + bool processResponse( const Response & response, TransactionState * ); + private: + TQCString 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 ) {} + + TQCString nextCommandLine( TransactionState * ); + void ungetCommandLine( const TQCString & 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 TQCString & initialBuffer ) + : Command( smtp, OnlyFirstInPipeline ), + mUngetBuffer( initialBuffer ), mLastChar( '\n' ), mWasComplete( false ) {} + + bool doNotExecute( const TransactionState * ts ) const; + TQCString nextCommandLine( TransactionState * ); + void ungetCommandLine( const TQCString & cmd, TransactionState * ts ); + bool processResponse( const Response & response, TransactionState * ); + private: + TQCString prepare( const TQByteArray & ba ); + TQCString mUngetBuffer; + char mLastChar; + bool mWasComplete; // ... before ungetting + }; + + class NoopCommand : public Command { + public: + NoopCommand( SMTPProtocol * smtp ) + : Command( smtp, OnlyLastInPipeline ) {} + + TQCString nextCommandLine( TransactionState * ); + }; + + class RsetCommand : public Command { + public: + RsetCommand( SMTPProtocol * smtp ) + : Command( smtp, CloseConnectionOnError ) {} + + TQCString nextCommandLine( TransactionState * ); + }; + + class QuitCommand : public Command { + public: + QuitCommand( SMTPProtocol * smtp ) + : Command( smtp, CloseConnectionOnError|OnlyLastInPipeline ) {} + + TQCString nextCommandLine( TransactionState * ); + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_COMMAND_H__ diff --git a/tdeioslave/smtp/compliance.txt b/tdeioslave/smtp/compliance.txt new file mode 100644 index 000000000..5b305a765 --- /dev/null +++ b/tdeioslave/smtp/compliance.txt @@ -0,0 +1,33 @@ +The SMTP tdeioslave 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/tdeioslave/smtp/interactivesmtpserver.cc b/tdeioslave/smtp/interactivesmtpserver.cc new file mode 100644 index 000000000..7e84b8d3e --- /dev/null +++ b/tdeioslave/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 tdeioslave. + Copyright (c) 2004 Marc Mutz <[email protected]> + + 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 <tqserversocket.h> +#include <tqsocket.h> +#include <tqwidget.h> +#include <tqapplication.h> +#include <tqhostaddress.h> +#include <tqtextedit.h> +#include <tqlineedit.h> +#include <tqlabel.h> +#include <tqstring.h> +#include <tqlayout.h> +#include <tqpushbutton.h> + +#include <cassert> + +#include "interactivesmtpserver.h" + +static const TQHostAddress localhost( 0x7f000001 ); // 127.0.0.1 + +InteractiveSMTPServerWindow::~InteractiveSMTPServerWindow() { + if ( mSocket ) { + mSocket->close(); + if ( mSocket->state() == TQSocket::Closing ) + connect( mSocket, TQT_SIGNAL(delayedCloseFinished()), + mSocket, TQT_SLOT(deleteLater()) ); + else + mSocket->deleteLater(); + mSocket = 0; + } +} + +void InteractiveSMTPServerWindow::slotSendResponse() +{ + const TQString line = mLineEdit->text(); + mLineEdit->clear(); + TQTextStream s( mSocket ); + s << line + "\r\n"; + slotDisplayServer( line ); +} + +InteractiveSMTPServer::InteractiveSMTPServer( TQObject* parent ) + : TQServerSocket( localhost, 2525, 1, parent ) +{ +} + +int main( int argc, char * argv[] ) { + TQApplication app( argc, argv ); + + InteractiveSMTPServer server; + + tqDebug( "Server should now listen on localhost:2525" ); + tqDebug( "Hit CTRL-C to quit." ); + return app.exec(); +}; + + +InteractiveSMTPServerWindow::InteractiveSMTPServerWindow( TQSocket * socket, TQWidget * parent, const char * name, WFlags f ) + : TQWidget( parent, name, f ), mSocket( socket ) +{ + TQPushButton * but; + assert( socket ); + + TQVBoxLayout * vlay = new TQVBoxLayout( this, 6 ); + + mTextEdit = new TQTextEdit( this ); + mTextEdit->setTextFormat( TQTextEdit::LogText ); + vlay->addWidget( mTextEdit, 1 ); + + TQHBoxLayout * hlay = new TQHBoxLayout( vlay ); + + mLineEdit = new TQLineEdit( this ); + but = new TQPushButton( "&Send", this ); + hlay->addWidget( new TQLabel( mLineEdit, "&Response:", this ) ); + hlay->addWidget( mLineEdit, 1 ); + hlay->addWidget( but ); + + connect( mLineEdit, TQT_SIGNAL(returnPressed()), TQT_SLOT(slotSendResponse()) ); + connect( but, TQT_SIGNAL(clicked()), TQT_SLOT(slotSendResponse()) ); + + but = new TQPushButton( "&Close Connection", this ); + vlay->addWidget( but ); + + connect( but, TQT_SIGNAL(clicked()), TQT_SLOT(slotConnectionClosed()) ); + + connect( socket, TQT_SIGNAL(connectionClosed()), TQT_SLOT(slotConnectionClosed()) ); + connect( socket, TQT_SIGNAL(error(int)), TQT_SLOT(slotError(int)) ); + connect( socket, TQT_SIGNAL(readyRead()), TQT_SLOT(slotReadyRead()) ); + + mLineEdit->setText( "220 hi there" ); + mLineEdit->setFocus(); +} + +#include "interactivesmtpserver.moc" diff --git a/tdeioslave/smtp/interactivesmtpserver.h b/tdeioslave/smtp/interactivesmtpserver.h new file mode 100644 index 000000000..3e4b4b29d --- /dev/null +++ b/tdeioslave/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 tdeioslave. + Copyright (c) 2004 Marc Mutz <[email protected]> + + 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 <tqwidget.h> + + +static TQString err2str( int err ) { + switch ( err ) { + case TQSocket::ErrConnectionRefused: return "Connection refused"; + case TQSocket::ErrHostNotFound: return "Host not found"; + case TQSocket::ErrSocketRead: return "Failed to read from socket"; + default: return "Unknown error"; + } +} + + +static TQString escape( TQString s ) { + return s + .replace( '&', "&" ) + .replace( '>', ">" ) + .replace( '<', "<" ) + .replace( '"', """ ) + ; +} + + +static TQString trim( const TQString & 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 TQWidget { + Q_OBJECT +public: + InteractiveSMTPServerWindow( TQSocket * socket, TQWidget * parent=0, const char * name=0, WFlags f=0 ); + ~InteractiveSMTPServerWindow(); + +public slots: + void slotSendResponse(); + void slotDisplayClient( const TQString & s ) { + mTextEdit->append( "C:" + escape(s) ); + } + void slotDisplayServer( const TQString & s ) { + mTextEdit->append( "S:" + escape(s) ); + } + void slotDisplayMeta( const TQString & s ) { + mTextEdit->append( "<font color=\"red\">" + escape(s) + "</font>" ); + } + void slotReadyRead() { + while ( mSocket->canReadLine() ) + slotDisplayClient( trim( mSocket->readLine() ) ); + } + void slotError( int err ) { + slotDisplayMeta( TQString( "E: %1 (%2)" ).arg( err2str( err ) ).arg( err ) ); + } + void slotConnectionClosed() { + slotDisplayMeta( "Connection closed by peer" ); + } + void slotCloseConnection() { + mSocket->close(); + } +private: + TQSocket * mSocket; + TQTextEdit * mTextEdit; + TQLineEdit * mLineEdit; +}; + +class InteractiveSMTPServer : public TQServerSocket { + Q_OBJECT +public: + InteractiveSMTPServer( TQObject * parent=0 ); + ~InteractiveSMTPServer() {} + + /*! \reimp */ + void newConnection( int fd ) { + TQSocket * socket = new TQSocket(); + socket->setSocket( fd ); + InteractiveSMTPServerWindow * w = new InteractiveSMTPServerWindow( socket ); + w->show(); + } +}; + + +#endif diff --git a/tdeioslave/smtp/request.cc b/tdeioslave/smtp/request.cc new file mode 100644 index 000000000..8019a04f7 --- /dev/null +++ b/tdeioslave/smtp/request.cc @@ -0,0 +1,189 @@ +/* -*- c++ -*- + request.cc + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 TQStringList query = TQStringList::split( '&', url.query().mid(1) ); +#ifndef NDEBUG + kdDebug(7112) << "Parsing request from query:\n" + query.join("\n" ) << endl; +#endif + for ( TQStringList::const_iterator it = query.begin() ; it != query.end() ; ++it ) { + int equalsPos = (*it).find( '=' ); + if ( equalsPos <= 0 ) + continue; + + const TQString key = (*it).left( equalsPos ).lower(); + const TQString 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; + } + + TQCString Request::heloHostnameCString() const { + return KIDNA::toAsciiCString( heloHostname() ); + } + + static bool isUsAscii( const TQString & 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 TQCString specials = "()<>[]:;@\\,.\""; + return specials.find( ch ) >= 0; + } + + + + static inline bool needsQuoting( char ch ) { + return ch == '\\' || ch == '"' || ch == '\n' ; + } + + + + static inline TQCString rfc2047Encode( const TQString & s ) { + TQCString r = KCodecs::base64Encode( s.stripWhiteSpace().utf8(), false ); + return "=?utf-8?b?" + r + "?=" ; // use base64 since that always gives a valid encoded-word + } + + + + static TQCString quote( const TQString & s ) { + assert( isUsAscii( s ) ); + + TQCString 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 TQCString formatFromAddress( const TQString & fromRealName, const TQString & fromAddress ) { + if ( fromRealName.isEmpty() ) + return fromAddress.latin1(); // no real name: return "[email protected]" + + // return "Joe User <[email protected]>", "\"User, Joe\" <[email protected]>" + // or "=?utf-8?q?Joe_User?= <[email protected]>", depending on real name's nature. + TQCString r = isUsAscii( fromRealName ) ? quote( fromRealName ) : rfc2047Encode( fromRealName ); + return r + " <" + fromAddress.latin1() + '>'; + } + + + + static TQCString formatSubject( TQString s ) { + if ( isUsAscii( s ) ) + return TQString(s.remove( '\n' )).latin1(); // don't break header folding, + // so remove any line break + // that happen to be around + else + return rfc2047Encode( s ); + } + + + + TQCString Request::headerFields( const TQString & fromRealName ) const { + if ( !emitHeaders() ) + return 0; + + assert( hasFromAddress() ); // should have been checked for by + // caller (MAIL FROM comes before DATA) + + TQCString result = "From: " + formatFromAddress( fromRealName, fromAddress() ) + "\r\n"; + + if ( !subject().isEmpty() ) + result += "Subject: " + formatSubject( subject() ) + "\r\n"; + if ( !to().empty() ) + result += TQCString( "To: " ) + to().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; + if ( !cc().empty() ) + result += TQCString( "Cc: " ) + cc().join( ",\r\n\t" /* line folding */ ).latin1() + "\r\n"; + return result; + } + +} // namespace KioSMTP diff --git a/tdeioslave/smtp/request.h b/tdeioslave/smtp/request.h new file mode 100644 index 000000000..21e2067fb --- /dev/null +++ b/tdeioslave/smtp/request.h @@ -0,0 +1,100 @@ +/* -*- c++ -*- + request.h + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tqstring.h> +#include <tqstringlist.h> + +class KURL; + +namespace KioSMTP { + + class Request { + public: + Request() + : mSubject( "missing subject" ), mEmitHeaders( true ), + m8Bit( false ), mSize( 0 ) {} + + static Request fromURL( const KURL & url ); + + TQString profileName() const { return mProfileName; } + void setProfileName( const TQString & profileName ) { mProfileName = profileName; } + bool hasProfile() const { return !profileName().isNull(); } + + TQString subject() const { return mSubject; } + void setSubject( const TQString & subject ) { mSubject = subject; } + + TQString fromAddress() const { return mFromAddress; } + void setFromAddress( const TQString & fromAddress ) { mFromAddress = fromAddress; } + bool hasFromAddress() const { return !mFromAddress.isEmpty(); } + + TQStringList recipients() const { return to() + cc() + bcc() ; } + bool hasRecipients() const { return !to().empty() || !cc().empty() || !bcc().empty() ; } + + TQStringList to() const { return mTo; } + TQStringList cc() const { return mCc; } + TQStringList bcc() const { return mBcc; } + void addTo( const TQString & to ) { mTo.push_back( to ); } + void addCc( const TQString & cc ) { mCc.push_back( cc ); } + void addBcc( const TQString & bcc ) { mBcc.push_back( bcc ); } + + TQString heloHostname() const { return mHeloHostname; } + TQCString heloHostnameCString() const; + void setHeloHostname( const TQString & 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. */ + TQCString headerFields( const TQString & fromRealName=TQString::null ) const; + + private: + TQStringList mTo, mCc, mBcc; + TQString mProfileName, mSubject, mFromAddress, mHeloHostname; + bool mEmitHeaders; + bool m8Bit; + unsigned int mSize; + }; + +} // namespace KioSMTP + +#endif // __KIOSMTP_REQUEST_H__ diff --git a/tdeioslave/smtp/response.cc b/tdeioslave/smtp/response.cc new file mode 100644 index 000000000..da5569cf7 --- /dev/null +++ b/tdeioslave/smtp/response.cc @@ -0,0 +1,160 @@ +/* -*- c++ -*- + response.cc + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tdeio/global.h> + +#include <tqstring.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 = TQCString( 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 ? TQCString( line+4, len-4+1 ).stripWhiteSpace() : TQCString() ); + } + + + // hackishly fixing QCStringList flaws... + static TQCString join( char sep, const QCStringList & list ) { + if ( list.empty() ) + return TQCString(); + TQCString result = list.front(); + for ( QCStringList::const_iterator it = ++list.begin() ; it != list.end() ; ++it ) + result += sep + *it; + return result; + } + + TQString Response::errorMessage() const { + TQString msg; + if ( lines().count() > 1 ) + msg = i18n("The server responded:\n%1") + .arg( static_cast<const char *>(join( '\n', lines()) ) ); + else + msg = i18n("The server responded: \"%1\"") + .arg( static_cast<const char *>(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 TDEIO::ERR_SERVICE_NOT_AVAILABLE; + + case 451: // Requested action aborted: local error in processing + return TDEIO::ERR_INTERNAL_SERVER; + + case 452: // Requested action not taken: insufficient system storage + case 552: // Requested mail action aborted: exceeded storage allocation + return TDEIO::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 TDEIO::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 TDEIO::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 TDEIO::ERR_UPGRADE_REQUIRED; + + case 432: // A password transition is needed + return TDEIO::ERR_COULD_NOT_AUTHENTICATE; + + default: + if ( isPositive() ) + return 0; + else + return TDEIO::ERR_UNKNOWN; + } + } + +} // namespace KioSMTP diff --git a/tdeioslave/smtp/response.h b/tdeioslave/smtp/response.h new file mode 100644 index 000000000..6c749b9e4 --- /dev/null +++ b/tdeioslave/smtp/response.h @@ -0,0 +1,125 @@ +/* -*- c++ -*- + response.h + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tqcstring.h> +#include <tqvaluelist.h> +typedef TQValueList<TQCString> QCStringList; + +class TQString; + +namespace KioSMTP { + + class Response { + public: + Response() + : mCode(0), + mValid(true), + mSawLastLine(false), + mWellFormed(true) {} + + void parseLine( const char * line ) { + parseLine( line, tqstrlen( line ) ); + } + void parseLine( const char * line, int len ); + + /** Return an internationalized error message according to the + response's code. */ + TQString 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/tdeioslave/smtp/smtp.cc b/tdeioslave/smtp/smtp.cc new file mode 100644 index 000000000..aa3ebfe75 --- /dev/null +++ b/tdeioslave/smtp/smtp.cc @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2000, 2001 Alex Zepeda <[email protected]> + * Copyright (c) 2001 Michael H�ckel <[email protected]> + * Copyright (c) 2002 Aaron J. Seigo <[email protected]> + * Copyright (c) 2003 Marc Mutz <[email protected]> + * 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 <tdeio/connection.h> +#include <tdeio/slaveinterface.h> +#include <klocale.h> + +#include <tqstring.h> +#include <tqstringlist.h> +#include <tqcstring.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) +{ + TDEInstance 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], tqstricmp( argv[1], "smtps" ) == 0 ); + slave.dispatchLoop(); +#ifdef HAVE_LIBSASL2 + sasl_done(); +#endif + return 0; +} + +SMTPProtocol::SMTPProtocol(const TQCString & pool, const TQCString & 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 TQByteArray & aData ) { + TQDataStream 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( TDEIO::ERR_INTERNAL, + i18n("The application sent an invalid request.") ); + return; + } + finished(); +} + + +// Usage: smtp://smtphost:port/[email protected]&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 = TQString::null; + if (m_sPass.isEmpty()) + m_sPass = TQString::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: [email protected] or + // Real Name <[email protected]>) + if ( !request.hasFromAddress() ) { + const TQString from = mset.getSetting( KEMailSettings::EmailAddress ); + if ( !from.isNull() ) + request.setFromAddress( from ); + else if ( request.emitHeaders() ) { + error(TDEIO::ERR_NO_CONTENT, i18n("The sender address is missing.")); + return; + } + } + + if ( !smtp_open( request.heloHostname() ) ) + { + error(TDEIO::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( TDEIO::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. + TQStringList recipients = request.recipients(); + for ( TQStringList::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 TQString & host, int port, + const TQString & user, const TQString & pass) +{ + m_sServer = host; + m_iPort = port; + m_sUser = user; + m_sPass = pass; +} + +bool SMTPProtocol::sendCommandLine( const TQCString & 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( TDEIO::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( TDEIO::ERR_SERVER_TIMEOUT, m_sServer ); + return response; + } + + // ...read data... + recv_len = readLine( buf, sizeof(buf) - 1 ); + if ( recv_len < 1 && !isConnectionValid() ) { + error( TDEIO::ERR_CONNECTION_BROKEN, m_sServer ); + return response; + } + + kdDebug(7112) << "S: " << TQCString( 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( TDEIO::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() ) { + TQCString 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; +} + +TQCString SMTPProtocol::collectPipelineCommands( TransactionState * ts ) { + assert( ts ); + + TQCString 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 TQCString 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 TQCString 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 TQString& 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( TDEIO::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 + { + TQString tmpPort; + TDESocketAddress* 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; + + TDEIO::AuthInfo authInfo; + authInfo.username = m_sUser; + authInfo.password = m_sPass; + authInfo.prompt = i18n("Username and password for your SMTP account:"); + + TQStringList 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 ); + + TQString 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 = TQString::null; + m_sOldUser = TQString::null; + m_sOldPass = TQString::null; + + mCapabilities.clear(); + mPendingCommandQueue.clear(); + mSentCommandQueue.clear(); + + m_opened = false; +} + +void SMTPProtocol::stat(const KURL & url) +{ + TQString path = url.path(); + error(TDEIO::ERR_DOES_NOT_EXIST, url.path()); +} + diff --git a/tdeioslave/smtp/smtp.h b/tdeioslave/smtp/smtp.h new file mode 100644 index 000000000..e9d9b833b --- /dev/null +++ b/tdeioslave/smtp/smtp.h @@ -0,0 +1,148 @@ +/* -*- c++ -*- + * Copyright (c) 2000, 2001 Alex Zepeda <[email protected]> + * Copyright (c) 2001 Michael H�ckel <[email protected]> + * 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 <tdeio/tcpslavebase.h> + +#include "capabilities.h" + +#include <tqstring.h> +#include <tqptrqueue.h> + +class KURL; +class TQCString; +template <typename T> class TQMemArray; +#ifdef USE_QT3 +typedef TQMemArray<char> TQByteArray; +#endif // USE_QT3 + +namespace KioSMTP { + class Response; + class TransactionState; + class Command; +} + +class SMTPProtocol : public TDEIO::TCPSlaveBase { + friend class KioSMTP::Command; +public: + SMTPProtocol(const TQCString & pool, const TQCString & app, bool useSSL); + virtual ~ SMTPProtocol(); + + virtual void setHost(const TQString & host, int port, + const TQString & user, const TQString & pass); + + virtual void special(const TQByteArray & 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 TQString& fakeHostname = TQString::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 TQCString & cmd ); + TQCString 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 */ + TQString 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; + TQString m_sServer, m_sOldServer; + TQString m_sUser, m_sOldUser; + TQString m_sPass, m_sOldPass; + TQString m_hostname; + + KioSMTP::Capabilities mCapabilities; + + typedef TQPtrQueue<KioSMTP::Command> CommandQueue; + CommandQueue mPendingCommandQueue; + CommandQueue mSentCommandQueue; +}; + +#endif // _SMTP_H diff --git a/tdeioslave/smtp/smtp.protocol b/tdeioslave/smtp/smtp.protocol new file mode 100644 index 000000000..9a90cc397 --- /dev/null +++ b/tdeioslave/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=tdeioslave/smtp.html +Icon=folder_outbox diff --git a/tdeioslave/smtp/smtps.protocol b/tdeioslave/smtp/smtps.protocol new file mode 100644 index 000000000..2087845f2 --- /dev/null +++ b/tdeioslave/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/tdeioslave/smtp/test_commands.cc b/tdeioslave/smtp/test_commands.cc new file mode 100644 index 000000000..d8a5f0d4e --- /dev/null +++ b/tdeioslave/smtp/test_commands.cc @@ -0,0 +1,728 @@ +#include <tdeio/global.h> +#include <kdebug.h> + +#include <tqstring.h> +#include <tqcstring.h> +#include <tqstringlist.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; + TQString lastErrorMessage; + int lastMessageBoxCode; + TQString lastMessageBoxText; + TQByteArray nextData; + int nextDataReturnCode; + TQStringList caps; + TDEIO::MetaData metadata; + + void clear() { + startTLSReturnCode = 1; + usesSSL = usesTLS = false; + lastErrorCode = lastMessageBoxCode = 0; + lastErrorMessage = lastMessageBoxText = TQString::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 TQString & msg ) { + lastErrorCode = id; + lastErrorMessage = msg; + } + void messageBox( int id, const TQString & msg, const TQString & ) { + lastMessageBoxCode = id; + lastMessageBoxText = msg; + } + void dataReq() { /* noop */ } + int readData( TQByteArray & ba ) { ba = nextData; return nextDataReturnCode; } + TQString metaData( const TQString & 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 = tqstrlen( foobarbaz ); + +static const char * foobarbaz_dotstuffed = "..Foo bar baz"; +static const unsigned int foobarbaz_dotstuffed_len = tqstrlen( foobarbaz_dotstuffed ); + +static const char * foobarbaz_lf = ".Foo bar baz\n"; +static const unsigned int foobarbaz_lf_len = tqstrlen( foobarbaz_lf ); + +static const char * foobarbaz_crlf = "..Foo bar baz\r\n"; +static const unsigned int foobarbaz_crlf_len = tqstrlen( 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 == TDEIO::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 == TDEIO::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 == TDEIO::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(); + TQStrIList 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, "[email protected]" ); + // 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:<[email protected]>\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, "[email protected]", true, 500 ); + ts.clear(); + ts2 = ts; + assert( mail2.nextCommandLine( &ts ) == "MAIL FROM:<[email protected]>\r\n" ); + assert( ts == ts2 ); + + // dynamics: success, size, 8bit, SIZE, 8BITMIME caps + smtp.clear(); + MailFromCommand mail3( &smtp, "[email protected]", true, 500 ); + ts.clear(); + ts2 = ts; + smtp.caps << "SIZE" << "8BITMIME" ; + assert( mail3.nextCommandLine( &ts ) == "MAIL FROM:<[email protected]> BODY=8BITMIME SIZE=500\r\n" ); + assert( ts == ts2 ); + + // dynamics: failure + smtp.clear(); + MailFromCommand mail4( &smtp, "[email protected]" ); + 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, "[email protected]" ); + // 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:<[email protected]>\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, "[email protected]" ); + 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 == "[email protected]" ); + assert( ts.failed() ); + assert( !ts.failedFatally() ); + assert( smtp.lastErrorCode == 0 ); + + // dynamics: success and failure combined + smtp.clear(); + RcptToCommand rcpt3( &smtp, "[email protected]" ); + RcptToCommand rcpt4( &smtp, "[email protected]" ); + RcptToCommand rcpt5( &smtp, "[email protected]" ); + 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( "[email protected]", "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 = tqstrlen( s_pre ); + + const char * s_post = mailEndsInNewline ? foobarbaz_crlf : foobarbaz_dotstuffed ; + //const unsigned int s_post_len = tqstrlen( 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 == TDEIO::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/tdeioslave/smtp/test_headergeneration.cc b/tdeioslave/smtp/test_headergeneration.cc new file mode 100644 index 000000000..83d999c4a --- /dev/null +++ b/tdeioslave/smtp/test_headergeneration.cc @@ -0,0 +1,86 @@ +#include "request.h" + +//#include <iostream> + +//using std::cout; +//using std::endl; + +int main( int , char ** ) { + static TQCString expected = + "From: [email protected]\r\n" + "Subject: missing subject\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n" + "From: Marc Mutz <[email protected]>\r\n" + "Subject: missing subject\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n" + "From: \"Mutz, Marc\" <[email protected]>\r\n" + "Subject: missing subject\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n" + "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= <[email protected]>\r\n" + "Subject: missing subject\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n" + "From: [email protected]\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n" + "From: Marc Mutz <[email protected]>\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n" + "From: \"Mutz, Marc\" <[email protected]>\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n" + "From: =?utf-8?b?TWFyYyBNw7Z0eg==?= <[email protected]>\r\n" + "Subject: =?utf-8?b?QmzDtmRlcyBTdWJqZWN0?=\r\n" + "To: [email protected],\r\n" + "\[email protected]\r\n" + "Cc: [email protected]\r\n" + "\n"; + + KioSMTP::Request request; + TQCString result; + + request.setEmitHeaders( true ); + request.setFromAddress( "[email protected]" ); + request.addTo( "[email protected]" ); + request.addTo( "[email protected]" ); + request.addCc( "[email protected]" ); + + 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/tdeioslave/smtp/test_responseparser.cc b/tdeioslave/smtp/test_responseparser.cc new file mode 100644 index 000000000..e251aa291 --- /dev/null +++ b/tdeioslave/smtp/test_responseparser.cc @@ -0,0 +1,107 @@ +#include "response.h" +#include <assert.h> + +static const TQCString singleLineResponseCRLF = "250 OK\r\n"; +static const TQCString singleLineResponse = "250 OK"; + +static const TQCString 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/tdeioslave/smtp/transactionstate.cc b/tdeioslave/smtp/transactionstate.cc new file mode 100644 index 000000000..886fe35b5 --- /dev/null +++ b/tdeioslave/smtp/transactionstate.cc @@ -0,0 +1,114 @@ +/* -*- c++ -*- + transactionstate.cc + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tdeio/global.h> +#include <klocale.h> + +#include <tqstringlist.h> + +namespace KioSMTP { + + void TransactionState::setFailedFatally( int code, const TQString & msg ) { + mFailed = mFailedFatally = true; + mErrorCode = code; + mErrorMessage = msg; + } + + void TransactionState::setMailFromFailed( const TQString & addr, const Response & r ) { + setFailed(); + mErrorCode = TDEIO::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 TDEIO::ERR_NO_CONTENT; + // ### what else? + return TDEIO::ERR_INTERNAL; + } + + TQString TransactionState::errorMessage() const { + if ( !failed() ) + return TQString::null; + + if ( !mErrorMessage.isEmpty() ) + return mErrorMessage; + + if ( haveRejectedRecipients() ) { + TQString msg = i18n("Message sending failed since the following recipients were rejected by the server:\n" + "%1"); + TQStringList 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/tdeioslave/smtp/transactionstate.h b/tdeioslave/smtp/transactionstate.h new file mode 100644 index 000000000..c62f52ae0 --- /dev/null +++ b/tdeioslave/smtp/transactionstate.h @@ -0,0 +1,185 @@ +/* -*- c++ -*- + transactionstate.h + + This file is part of kio_smtp, the KDE SMTP tdeioslave. + Copyright (c) 2003 Marc Mutz <[email protected]> + + 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 <tqstring.h> +#include <tqvaluelist.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 TQString & who=TQString::null, + const TQString & why=TQString::null ) + : recipient( who ), reason( why ) {} + TQString recipient; + TQString reason; +#ifdef KIOSMTP_COMPARATORS + bool operator==( const RecipientRejection & other ) const { + return recipient == other.recipient && reason == other.reason; + } +#endif + }; + typedef TQValueList<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 TQString & msg=TQString::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 TQString::null otherwise */ + TQString errorMessage() const; + + void setMailFromFailed( const TQString & 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 TQString & who, const TQString & 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; + TQString 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/tdeioslave/system/CMakeLists.txt b/tdeioslave/system/CMakeLists.txt new file mode 100644 index 000000000..b9fea814d --- /dev/null +++ b/tdeioslave/system/CMakeLists.txt @@ -0,0 +1,50 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +add_subdirectory( kdedmodule ) +add_subdirectory( entries ) +add_subdirectory( mimetypes ) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES system.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kiosystem (static) ######################## + +set( target kiosystem ) + +tde_add_library( ${target} STATIC_PIC AUTOMOC + SOURCES kio_system.cpp systemimpl.cpp +) + + +##### kio_system (module) ####################### + +set( target kio_system ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES dummy.cpp + EMBED kiosystem-static + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/system/Makefile.am b/tdeioslave/system/Makefile.am new file mode 100644 index 000000000..5fc63eb5c --- /dev/null +++ b/tdeioslave/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 = libtdeiosystem.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 = libtdeiosystem.la +libtdeiosystem_la_SOURCES = kio_system.cpp systemimpl.cpp + +check_PROGRAMS = testsystem +testsystem_SOURCES = testsystem.cpp +testsystem_LDADD = libtdeiosystem.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)/tdeio_system.pot + diff --git a/tdeioslave/system/dummy.cpp b/tdeioslave/system/dummy.cpp new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tdeioslave/system/dummy.cpp @@ -0,0 +1 @@ + diff --git a/tdeioslave/system/entries/CMakeLists.txt b/tdeioslave/system/entries/CMakeLists.txt new file mode 100644 index 000000000..a1d15fa53 --- /dev/null +++ b/tdeioslave/system/entries/CMakeLists.txt @@ -0,0 +1,17 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +##### other data ################################ + +install( FILES + home.desktop documents.desktop users.desktop + media.desktop remote.desktop trash.desktop + DESTINATION ${DATA_INSTALL_DIR}/systemview ) diff --git a/tdeioslave/system/entries/Makefile.am b/tdeioslave/system/entries/Makefile.am new file mode 100644 index 000000000..5a666650f --- /dev/null +++ b/tdeioslave/system/entries/Makefile.am @@ -0,0 +1,3 @@ +systemviewdir = $(kde_datadir)/systemview +systemview_DATA = home.desktop documents.desktop users.desktop media.desktop remote.desktop trash.desktop + diff --git a/tdeioslave/system/entries/documents.desktop b/tdeioslave/system/entries/documents.desktop new file mode 100644 index 000000000..ae651c541 --- /dev/null +++ b/tdeioslave/system/entries/documents.desktop @@ -0,0 +1,58 @@ +[Desktop Entry] +Type=Link +URL=$( kxdglauncher --getpath --xdgname DOCUMENTS ) +Icon=folder_man +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[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[hr]=Mapa dokumenata +Name[hu]=Dokumentumok könyvtár +Name[is]=Skjalamappa +Name[it]=Cartella documenti +Name[ja]=ドキュメントフォルダ +Name[kk]=Құжаттар қапшығы +Name[lt]=Dokumentų aplankai +Name[nb]=Dokumentmappe +Name[nds]=Dokmenten-Orner +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[sl]=Mapa z dokumenti +Name[sr@Latn]=Fascikla sa dokumentima +Name[sv]=Dokumentkatalog +Name[tg]=Ҳуҷҷатҳо +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/tdeioslave/system/entries/home.desktop b/tdeioslave/system/entries/home.desktop new file mode 100644 index 000000000..56c07117a --- /dev/null +++ b/tdeioslave/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/tdeioslave/system/entries/media.desktop b/tdeioslave/system/entries/media.desktop new file mode 100644 index 000000000..8ad83923a --- /dev/null +++ b/tdeioslave/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/tdeioslave/system/entries/remote.desktop b/tdeioslave/system/entries/remote.desktop new file mode 100644 index 000000000..8f064a83b --- /dev/null +++ b/tdeioslave/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/tdeioslave/system/entries/trash.desktop b/tdeioslave/system/entries/trash.desktop new file mode 100644 index 000000000..7de5f21b6 --- /dev/null +++ b/tdeioslave/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/tdeioslave/system/entries/users.desktop b/tdeioslave/system/entries/users.desktop new file mode 100644 index 000000000..3ae601b0b --- /dev/null +++ b/tdeioslave/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/tdeioslave/system/kdedmodule/CMakeLists.txt b/tdeioslave/system/kdedmodule/CMakeLists.txt new file mode 100644 index 000000000..6df004620 --- /dev/null +++ b/tdeioslave/system/kdedmodule/CMakeLists.txt @@ -0,0 +1,41 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES systemdirnotify.desktop DESTINATION ${SERVICES_INSTALL_DIR}/kded ) + + +##### kded_systemdirnotify (module) ############# + +set( target kded_systemdirnotify ) + +set( ${target}_SRCS + systemdirnotify.cpp systemdirnotify.skel + systemdirnotifymodule.cpp systemdirnotifymodule.skel +) + +tde_add_kpart( ${target} AUTOMOC + SOURCES ${${target}_SRCS} + LINK tdeinit_kded-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/system/kdedmodule/Makefile.am b/tdeioslave/system/kdedmodule/Makefile.am new file mode 100644 index 000000000..bc368f9b1 --- /dev/null +++ b/tdeioslave/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/tdeioslave/system/kdedmodule/systemdirnotify.cpp b/tdeioslave/system/kdedmodule/systemdirnotify.cpp new file mode 100644 index 000000000..3a6f029e6 --- /dev/null +++ b/tdeioslave/system/kdedmodule/systemdirnotify.cpp @@ -0,0 +1,187 @@ +/* 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 <tqdir.h> + +SystemDirNotify::SystemDirNotify() +: mInited( false ) +{ +} + +void SystemDirNotify::init() +{ + if( mInited ) + // FIXME Work around a probable race condition by inserting printf delay before following + // code is executed -- the root cause of the race needs investigation and resolution. + printf("[systemdirnotify] SystemDirNotify::init(mInited)"); + return; + mInited = true; + TDEGlobal::dirs()->addResourceType("system_entries", + KStandardDirs::kde_default("data") + "systemview"); + + TQStringList names_found; + TQStringList dirList = TDEGlobal::dirs()->resourceDirs("system_entries"); + + TQStringList::ConstIterator dirpath = dirList.begin(); + TQStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + TQDir dir = *dirpath; + if (!dir.exists()) continue; + + TQStringList filenames + = dir.entryList( TQDir::Files | TQDir::Readable ); + + TQStringList::ConstIterator name = filenames.begin(); + TQStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (!names_found.contains(*name)) + { + KDesktopFile desktop(*dirpath+*name, true); + + TQString 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(); + TQMap<KURL,KURL>::const_iterator it = m_urlMap.begin(); + TQMap<KURL,KURL>::const_iterator end = m_urlMap.end(); + + for (; it!=end; ++it) + { + KURL base = it.key(); + + if ( base.isParentOf(url) ) + { + TQString 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/tdeioslave/system/kdedmodule/systemdirnotify.desktop b/tdeioslave/system/kdedmodule/systemdirnotify.desktop new file mode 100644 index 000000000..31379df76 --- /dev/null +++ b/tdeioslave/system/kdedmodule/systemdirnotify.desktop @@ -0,0 +1,65 @@ +[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 通知程式 +Comment=Provides change notification for folders monitored by the system:/ kio plugin +ServiceTypes=KDEDModule +X-TDE-ModuleType=Library +X-TDE-Library=systemdirnotify +X-TDE-FactoryName=systemdirnotify +X-TDE-Kded-load-on-demand=true +X-TDE-Kded-autoload=true diff --git a/tdeioslave/system/kdedmodule/systemdirnotify.h b/tdeioslave/system/kdedmodule/systemdirnotify.h new file mode 100644 index 000000000..b743ddb42 --- /dev/null +++ b/tdeioslave/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 <tqmap.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); + + TQMap<KURL,KURL> m_urlMap; + bool mInited; +}; + +#endif diff --git a/tdeioslave/system/kdedmodule/systemdirnotifymodule.cpp b/tdeioslave/system/kdedmodule/systemdirnotifymodule.cpp new file mode 100644 index 000000000..64e8c8d62 --- /dev/null +++ b/tdeioslave/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 TQCString &obj) + : KDEDModule(obj) +{ +} + +extern "C" { + KDE_EXPORT KDEDModule *create_systemdirnotify(const TQCString &obj) + { + TDEGlobal::locale()->insertCatalogue("kio_system"); + return new SystemDirNotifyModule(obj); + } +} + diff --git a/tdeioslave/system/kdedmodule/systemdirnotifymodule.h b/tdeioslave/system/kdedmodule/systemdirnotifymodule.h new file mode 100644 index 000000000..e979ba098 --- /dev/null +++ b/tdeioslave/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 TQCString &obj); +private: + SystemDirNotify notifier; +}; + +#endif diff --git a/tdeioslave/system/kio_system.cpp b/tdeioslave/system/kio_system.cpp new file mode 100644 index 000000000..61f724093 --- /dev/null +++ b/tdeioslave/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 <tqeventloop.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 ) + { + // TDEApplication is necessary to use other ioslaves + putenv(strdup("SESSION_MANAGER=")); + TDECmdLineArgs::init(argc, argv, "kio_system", 0, 0, 0, 0); + TDECmdLineArgs::addCmdLineOptions( options ); + TDEApplication app( false, false ); + // We want to be anonymous even if we use DCOP + app.dcopClient()->attach(); + + TDECmdLineArgs *args = TDECmdLineArgs::parsedArgs(); + SystemProtocol slave( args->arg(0), args->arg(1), args->arg(2) ); + slave.dispatchLoop(); + return 0; + } +} + + +SystemProtocol::SystemProtocol(const TQCString &protocol, + const TQCString &pool, const TQCString &app) + : ForwardingSlaveBase(protocol, pool, app) +{ +} + +SystemProtocol::~SystemProtocol() +{ +} + +bool SystemProtocol::rewriteURL(const KURL &url, KURL &newUrl) +{ + TQString name, path; + + if ( !m_impl.parseURL(url, name, path) ) + { + error(TDEIO::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; + + TQString path = url.path(); + if ( path.isEmpty() || path == "/" ) + { + // The root is "virtual" - it's not a single physical directory + TDEIO::UDSEntry entry; + m_impl.createTopLevelEntry( entry ); + statEntry( entry ); + finished(); + return; + } + + TQString name; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + if( path.isEmpty() ) + { + TDEIO::UDSEntry entry; + + if ( m_impl.statByName(name, entry) ) + { + statEntry(entry); + finished(); + } + else + { + error(TDEIO::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; + } + + TQString name, path; + bool ok = m_impl.parseURL(url, name, path); + + if ( !ok ) + { + error(TDEIO::ERR_MALFORMED_URL, url.prettyURL()); + return; + } + + ForwardingSlaveBase::listDir(url); +} + +void SystemProtocol::listRoot() +{ + TDEIO::UDSEntry entry; + + TDEIO::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); + + TDEIO::UDSEntryListIterator it = system_entries.begin(); + TDEIO::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/tdeioslave/system/kio_system.h b/tdeioslave/system/kio_system.h new file mode 100644 index 000000000..fb3423889 --- /dev/null +++ b/tdeioslave/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 <tdeio/forwardingslavebase.h> + +#include "systemimpl.h" + +class SystemProtocol : public TDEIO::ForwardingSlaveBase +{ +public: + SystemProtocol(const TQCString &protocol, const TQCString &pool, + const TQCString &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/tdeioslave/system/mimetypes/CMakeLists.txt b/tdeioslave/system/mimetypes/CMakeLists.txt new file mode 100644 index 000000000..8695a7cd3 --- /dev/null +++ b/tdeioslave/system/mimetypes/CMakeLists.txt @@ -0,0 +1,14 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +##### other data ################################ + +install( FILES system_directory.desktop DESTINATION ${MIME_INSTALL_DIR}/inode ) diff --git a/tdeioslave/system/mimetypes/Makefile.am b/tdeioslave/system/mimetypes/Makefile.am new file mode 100644 index 000000000..4f58439a7 --- /dev/null +++ b/tdeioslave/system/mimetypes/Makefile.am @@ -0,0 +1,6 @@ +mimetypedir = $(kde_mimedir)/inode + +mimetype_DATA = system_directory.desktop + + +EXTRA_DIST = $(mimetype_DATA) diff --git a/tdeioslave/system/mimetypes/system_directory.desktop b/tdeioslave/system/mimetypes/system_directory.desktop new file mode 100644 index 000000000..57af9b112 --- /dev/null +++ b/tdeioslave/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-TDE-AutoEmbed=true +X-TDE-IsAlso=inode/directory diff --git a/tdeioslave/system/system.protocol b/tdeioslave/system/system.protocol new file mode 100644 index 000000000..9aa6fbd6c --- /dev/null +++ b/tdeioslave/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=tdeioslave/file.html +Class=:local +deleteRecursive=true +fileNameUsedForCopying=Name + diff --git a/tdeioslave/system/systemimpl.cpp b/tdeioslave/system/systemimpl.cpp new file mode 100644 index 000000000..12732767c --- /dev/null +++ b/tdeioslave/system/systemimpl.cpp @@ -0,0 +1,312 @@ +/* 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 <tqapplication.h> +#include <tqeventloop.h> +#include <tqdir.h> +#include <tqfile.h> + +#include <sys/stat.h> + +SystemImpl::SystemImpl() : TQObject() +{ + TDEGlobal::dirs()->addResourceType("system_entries", + KStandardDirs::kde_default("data") + "systemview"); +} + +bool SystemImpl::listRoot(TQValueList<TDEIO::UDSEntry> &list) +{ + kdDebug() << "SystemImpl::listRoot" << endl; + + TQStringList names_found; + TQStringList dirList = TDEGlobal::dirs()->resourceDirs("system_entries"); + + TQStringList::ConstIterator dirpath = dirList.begin(); + TQStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + TQDir dir = *dirpath; + if (!dir.exists()) continue; + + TQStringList filenames + = dir.entryList( TQDir::Files | TQDir::Readable ); + + + TDEIO::UDSEntry entry; + + TQStringList::ConstIterator filename = filenames.begin(); + TQStringList::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, TQString &name, TQString &path) const +{ + TQString 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 = TQString::null; + } + + return name != TQString::null; +} + +bool SystemImpl::realURL(const TQString &name, const TQString &path, + KURL &url) const +{ + url = findBaseURL(name); + if (!url.isValid()) + { + return false; + } + + url.addPath(path); + return true; +} + +bool SystemImpl::statByName(const TQString &filename, TDEIO::UDSEntry& entry) +{ + kdDebug() << "SystemImpl::statByName" << endl; + + TQStringList dirList = TDEGlobal::dirs()->resourceDirs("system_entries"); + + TQStringList::ConstIterator dirpath = dirList.begin(); + TQStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + TQDir dir = *dirpath; + if (!dir.exists()) continue; + + TQStringList filenames + = dir.entryList( TQDir::Files | TQDir::Readable ); + + + TQStringList::ConstIterator name = filenames.begin(); + TQStringList::ConstIterator endf = filenames.end(); + + for(; name!=endf; ++name) + { + if (*name==filename+".desktop") + { + createEntry(entry, *dirpath, *name); + return true; + } + } + } + + return false; +} + +KURL SystemImpl::findBaseURL(const TQString &filename) const +{ + kdDebug() << "SystemImpl::findBaseURL" << endl; + + TQStringList dirList = TDEGlobal::dirs()->resourceDirs("system_entries"); + + TQStringList::ConstIterator dirpath = dirList.begin(); + TQStringList::ConstIterator end = dirList.end(); + for(; dirpath!=end; ++dirpath) + { + TQDir dir = *dirpath; + if (!dir.exists()) continue; + + TQStringList filenames + = dir.entryList( TQDir::Files | TQDir::Readable ); + + + TDEIO::UDSEntry entry; + + TQStringList::ConstIterator name = filenames.begin(); + TQStringList::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(TDEIO::UDSEntry &entry, unsigned int ID, long l, + const TQString &s = TQString::null) +{ + TDEIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + + +void SystemImpl::createTopLevelEntry(TDEIO::UDSEntry &entry) const +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, "."); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_ACCESS, 0555); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/system_directory"); + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, "system"); +} + +TQString SystemImpl::readPathINL(TQString filename) +{ + bool isPathExpanded = false; + TQString unexpandedPath; + TQFile f( filename ); + if (!f.open(IO_ReadOnly)) + return TQString(); + // set the codec for the current locale + TQTextStream s(&f); + TQString line = s.readLine(); + while (!line.isNull()) + { + if (line.startsWith("Path=$(")) { + isPathExpanded = true; + unexpandedPath = line.remove("Path="); + } + line = s.readLine(); + } + if (isPathExpanded == false) { + KDesktopFile desktop(filename, true); + return desktop.readPath(); + } + else { + return unexpandedPath; + } +} + +void SystemImpl::createEntry(TDEIO::UDSEntry &entry, + const TQString &directory, + const TQString &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() && readPathINL(directory+file).isEmpty() ) + { + return; + } + + addAtom(entry, TDEIO::UDS_NAME, 0, desktop.readName()); + + TQString new_filename = file; + new_filename.truncate(file.length()-8); + + if ( desktop.readURL().isEmpty() ) + { + addAtom(entry, TDEIO::UDS_URL, 0, readPathINL(directory+file)); + } + else + { + addAtom(entry, TDEIO::UDS_URL, 0, "system:/"+new_filename); + } + + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/directory"); + + TQString icon = desktop.readIcon(); + TQString empty_icon = desktop.readEntry("EmptyIcon"); + + if (!empty_icon.isEmpty()) + { + KURL url = desktop.readURL(); + + m_lastListingEmpty = true; + + TDEIO::ListJob *job = TDEIO::listDir(url, false, false); + connect( job, TQT_SIGNAL( entries(TDEIO::Job *, + const TDEIO::UDSEntryList &) ), + this, TQT_SLOT( slotEntries(TDEIO::Job *, + const TDEIO::UDSEntryList &) ) ); + connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), + this, TQT_SLOT( slotResult(TDEIO::Job *) ) ); + tqApp->eventLoop()->enterLoop(); + + if (m_lastListingEmpty) icon = empty_icon; + } + + addAtom(entry, TDEIO::UDS_ICON_NAME, 0, icon); +} + +void SystemImpl::slotEntries(TDEIO::Job *job, const TDEIO::UDSEntryList &list) +{ + if (list.size()>0) + { + job->kill(true); + m_lastListingEmpty = false; + tqApp->eventLoop()->exitLoop(); + } +} + +void SystemImpl::slotResult(TDEIO::Job *) +{ + tqApp->eventLoop()->exitLoop(); +} + + +#include "systemimpl.moc" diff --git a/tdeioslave/system/systemimpl.h b/tdeioslave/system/systemimpl.h new file mode 100644 index 000000000..6270489c8 --- /dev/null +++ b/tdeioslave/system/systemimpl.h @@ -0,0 +1,68 @@ +/* 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 <tdeio/global.h> +#include <tdeio/job.h> +#include <kdesktopfile.h> +#include <kurl.h> +#include <dcopobject.h> + +#include <tqobject.h> +#include <tqstring.h> + +class SystemImpl : public TQObject +{ +Q_OBJECT +public: + SystemImpl(); + + void createTopLevelEntry(TDEIO::UDSEntry& entry) const; + bool statByName(const TQString &filename, TDEIO::UDSEntry& entry); + + bool listRoot(TQValueList<TDEIO::UDSEntry> &list); + + bool parseURL(const KURL &url, TQString &name, TQString &path) const; + bool realURL(const TQString &name, const TQString &path, KURL &url) const; + + int lastErrorCode() const { return m_lastErrorCode; } + TQString lastErrorMessage() const { return m_lastErrorMessage; } + +private slots: + KURL findBaseURL(const TQString &filename) const; + void slotEntries(TDEIO::Job *job, const TDEIO::UDSEntryList &list); + void slotResult(TDEIO::Job *job); + +private: + void createEntry(TDEIO::UDSEntry& entry, const TQString &directory, + const TQString &file); + + bool m_lastListingEmpty; + + TQString readPathINL(TQString filename); + + /// Last error code stored in class to simplify API. + /// Note that this means almost no method can be const. + int m_lastErrorCode; + TQString m_lastErrorMessage; +}; + +#endif diff --git a/tdeioslave/system/testsystem.cpp b/tdeioslave/system/testsystem.cpp new file mode 100644 index 000000000..7be993ff8 --- /dev/null +++ b/tdeioslave/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 TQString& txt, TQString a, TQString b) +{ + if (a.isEmpty()) + a = TQString::null; + if (b.isEmpty()) + b = TQString::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[]) +{ + TDEApplication::disableAutoDcopRegistration(); + TDECmdLineArgs::init(argc,argv,"testsystem", 0, 0, 0, 0); + TDEApplication 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/tdeioslave/system/testsystem.h b/tdeioslave/system/testsystem.h new file mode 100644 index 000000000..2418e66cb --- /dev/null +++ b/tdeioslave/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/tdeioslave/tar/CMakeLists.txt b/tdeioslave/tar/CMakeLists.txt new file mode 100644 index 000000000..0e5ce7684 --- /dev/null +++ b/tdeioslave/tar/CMakeLists.txt @@ -0,0 +1,37 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES tar.protocol ar.protocol zip.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### kio_tar (module) ####################### + +set( target kio_tar ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES tar.cc + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/tar/Makefile.am b/tdeioslave/tar/Makefile.am new file mode 100644 index 000000000..31fd98ebf --- /dev/null +++ b/tdeioslave/tar/Makefile.am @@ -0,0 +1,24 @@ +## Makefile.am of tdebase/tdeioslave/tar + +INCLUDES = $(all_includes) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor +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)/tdeio_tar.pot diff --git a/tdeioslave/tar/ar.protocol b/tdeioslave/tar/ar.protocol new file mode 100644 index 000000000..31e765a7a --- /dev/null +++ b/tdeioslave/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=tdeioslave/ar.html +Icon=ar +Class=:local diff --git a/tdeioslave/tar/ktartest.cpp b/tdeioslave/tar/ktartest.cpp new file mode 100644 index 000000000..efb1e42fe --- /dev/null +++ b/tdeioslave/tar/ktartest.cpp @@ -0,0 +1,201 @@ +#include "ktar.h" +#include <stdio.h> +#include <tqfile.h> +#include <kinstance.h> +#include <kdebug.h> + +void recursive_print( const KTarDirectory * dir, const TQString & path ) +{ + TQStringList l = dir->entries(); + TQStringList::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; + } + TDEInstance instance("ktartest"); + + TQString 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; + + TQByteArray arr( f->data() ); + printf("SIZE=%i\n",arr.size() ); + TQString str( arr ); + printf("DATA=%s\n", str.latin1()); + + /* + // This is what KGzipDev::readAll could do, if TQIODevice::readAll was virtual.... + TQByteArray array(1024); + int n; + while ( ( n = dev.readBlock( array.data(), array.size() ) ) ) + { + kdDebug() << "readBlock returned " << n << endl << endl; + TQCString 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; + + TQByteArray arr( f->data() ); + printf("SIZE=%i\n",arr.size() ); + TQString 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++ ) + { + TQString 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" ) + { + TQFile 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/tdeioslave/tar/tar.cc b/tdeioslave/tar/tar.cc new file mode 100644 index 000000000..bce89644d --- /dev/null +++ b/tdeioslave/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 <tqfile.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 <tdeio/global.h> +#include <kremoteencoding.h> + +#include <errno.h> // to be removed + +#include "tar.h" + +using namespace TDEIO; + +extern "C" { int KDE_EXPORT kdemain(int argc, char **argv); } + +int kdemain( int argc, char **argv ) +{ + TDEInstance 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 TQCString &pool, const TQCString &app ) : SlaveBase( "tar", pool, app ) +{ + kdDebug( 7109 ) << "ArchiveProtocol::ArchiveProtocol" << endl; + m_archiveFile = 0L; +} + +ArchiveProtocol::~ArchiveProtocol() +{ + delete m_archiveFile; +} + +bool ArchiveProtocol::checkNewFile( const KURL & url, TQString & path, TDEIO::Error& errorNum ) +{ + TQString 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( TQFile::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; + TQString archiveFile; + path = TQString::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 ) + { + TQString tryPath = fullPath.left( pos ); + kdDebug(7109) << fullPath << " trying " << tryPath << endl; + if ( KDE_stat( TQFile::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 = TQString::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 = TDEIO::ERR_IS_DIRECTORY; + } + else + errorNum = TDEIO::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 = TDEIO::ERR_UNSUPPORTED_PROTOCOL; + return false; + } + + if ( !m_archiveFile->open( IO_ReadOnly ) ) + { + kdDebug(7109) << "Opening " << archiveFile << "failed." << endl; + delete m_archiveFile; + m_archiveFile = 0L; + errorNum = TDEIO::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; + + TQString path; + TDEIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + if ( errorNum == TDEIO::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( TDEIO::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() + TQString::fromLatin1( ":/") ); + kdDebug( 7109 ) << "url.path()==" << url.path() << endl; + redir.setPath( url.path() + TQString::fromLatin1("/") ); + kdDebug( 7109 ) << "ArchiveProtocol::listDir: redirection " << redir.url() << endl; + redirection( redir ); + finished(); + return; + } + + path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); + + kdDebug( 7109 ) << "checkNewFile done" << endl; + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveDirectory* dir; + if (!path.isEmpty() && path != "/") + { + kdDebug(7109) << TQString(TQString("Looking for entry %1").arg(path)) << endl; + const KArchiveEntry* e = root->entry( path ); + if ( !e ) + { + error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + if ( ! e->isDirectory() ) + { + error( TDEIO::ERR_IS_FILE, url.prettyURL() ); + return; + } + dir = (KArchiveDirectory*)e; + } else { + dir = root; + } + + TQStringList l = dir->entries(); + totalSize( l.count() ); + + UDSEntry entry; + TQStringList::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 ) +{ + TQString path; + UDSEntry entry; + TDEIO::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 == TDEIO::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( TDEIO::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 = TDEIO::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( TQFile::encodeName( url.path() ), &buff ) == -1 ) + { + // Should not happen, as the file was already stated by checkNewFile + error( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL() ); + return; + } + + atom.m_uds = TDEIO::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 = TQString::fromLatin1( "/" ); + archiveEntry = root; + } else { + path = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); + archiveEntry = root->entry( path ); + } + if ( !archiveEntry ) + { + error( TDEIO::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; + + TQString path; + TDEIO::Error errorNum; + if ( !checkNewFile( url, path, errorNum ) ) + { + if ( errorNum == TDEIO::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( TDEIO::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 = TQString::fromLocal8Bit(remoteEncoding()->encode(path)); + + const KArchiveDirectory* root = m_archiveFile->directory(); + const KArchiveEntry* archiveEntry = root->entry( path ); + + if ( !archiveEntry ) + { + error( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + return; + } + if ( archiveEntry->isDirectory() ) + { + error( TDEIO::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 + */ + + TQIODevice* 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( TDEIO::ERR_UNSUPPORTED_PROTOCOL, url.protocol() ); + return; + } + + if (!io) + { + error( TDEIO::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( TDEIO::ERR_CANNOT_OPEN_FOR_READING, url.prettyURL() ); + return; + } + + totalSize( archiveFileEntry->size() ); + + // Size of a TQIODevice 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() ); + TQByteArray buffer ( bufferSize ); + if ( buffer.isEmpty() && bufferSize > 0 ) + { + // Something went wrong + error( TDEIO::ERR_OUT_OF_MEMORY, url.prettyURL() ); + return; + } + + bool firstRead = true; + + // How much file do we still have to process? + int fileSize = archiveFileEntry->size(); + TDEIO::filesize_t processed = 0; + + while ( !io->atEnd() && fileSize > 0 ) + { + if ( !firstRead ) + { + bufferSize = kMin( maxSize, fileSize ); + buffer.resize( bufferSize, TQGArray::SpeedOptim ); + } + const TQ_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( TDEIO::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( TQByteArray() ); + + 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/tdeioslave/tar/tar.h b/tdeioslave/tar/tar.h new file mode 100644 index 000000000..42cb2457a --- /dev/null +++ b/tdeioslave/tar/tar.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 David Faure <[email protected]> + + 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 <tdeio/global.h> +#include <tdeio/slavebase.h> + +class ArchiveProtocol : public TDEIO::SlaveBase +{ +public: + ArchiveProtocol( const TQCString &pool, const TQCString &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, TDEIO::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, TQString & path, TDEIO::Error& errorNum ); + + KArchive * m_archiveFile; + TQString m_archiveName; + time_t m_mtime; +}; + +#endif diff --git a/tdeioslave/tar/tar.protocol b/tdeioslave/tar/tar.protocol new file mode 100644 index 000000000..bd97ba6bb --- /dev/null +++ b/tdeioslave/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=tdeioslave/tar.html +Icon=tar +Class=:local diff --git a/tdeioslave/tar/zip.protocol b/tdeioslave/tar/zip.protocol new file mode 100644 index 000000000..d67d5b2ff --- /dev/null +++ b/tdeioslave/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=tdeioslave/zip.html +Icon=zip +Class=:local diff --git a/tdeioslave/thumbnail/CMakeLists.txt b/tdeioslave/thumbnail/CMakeLists.txt new file mode 100644 index 000000000..9836c76af --- /dev/null +++ b/tdeioslave/thumbnail/CMakeLists.txt @@ -0,0 +1,135 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +##### check for openexr ######################### + +if( WITH_OPENEXR ) + pkg_search_module( OPENEXR OpenEXR ) + if( NOT OPENEXR_FOUND ) + message(FATAL_ERROR "\nopenexr are requested, but not found on your system" ) + endif( NOT OPENEXR_FOUND ) + set( HAVE_EXR 1 ) +endif( WITH_OPENEXR ) + + +##### compiler/linker flags ##################### + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} + ${XCURSOR_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES + thumbnail.protocol imagethumbnail.desktop textthumbnail.desktop + htmlthumbnail.desktop djvuthumbnail.desktop + DESTINATION ${SERVICES_INSTALL_DIR} ) + +install( FILES thumbcreator.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR} ) + + +##### kio_thumbnail (module) #################### + +set( target kio_thumbnail ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES thumbnail.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### imagethumbnail (module) #################### + +set( target imagethumbnail ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES imagecreator.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### textthumbnail (module) #################### + +set( target textthumbnail ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES textcreator.cpp + LINK tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### htmlthumbnail (module) #################### + +set( target htmlthumbnail ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES htmlcreator.cpp + LINK tdehtml-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### djvuthumbnail (module) #################### + +set( target djvuthumbnail ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES djvucreator.cpp + LINK tdecore-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### cursorthumbnail (module) ################## + +if( WITH_XCURSOR ) + + set( target cursorthumbnail ) + + tde_add_kpart( ${target} AUTOMOC + SOURCES cursorcreator.cpp + LINK tdecore-shared ${XCURSOR_LIBRARIES} + DESTINATION ${PLUGIN_INSTALL_DIR} + ) + + install( FILES cursorthumbnail.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +endif( WITH_XCURSOR ) + + +##### exrthumbnail (module) ##################### + +if( WITH_OPENEXR ) + + set( target exrthumbnail ) + + tde_add_kpart( ${target} AUTOMOC + SOURCES exrcreator.cpp + LINK tdecore-shared ${OPENEXR_LIBRARIES} + DESTINATION ${PLUGIN_INSTALL_DIR} + ) + + install( FILES exrthumbnail.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) + +endif( WITH_OPENEXR ) diff --git a/tdeioslave/thumbnail/Makefile.am b/tdeioslave/thumbnail/Makefile.am new file mode 100644 index 000000000..93697f148 --- /dev/null +++ b/tdeioslave/thumbnail/Makefile.am @@ -0,0 +1,69 @@ +## Makefile.am of tdebase/tdeioslave/thumbnail + +INCLUDES = $(all_includes) $(EXR_FLAGS) +AM_LDFLAGS = $(all_libraries) $(KDE_RPATH) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor +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_TDECORE) +djvuthumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +cursorthumbnail_la_SOURCES = cursorcreator.cpp +cursorthumbnail_la_LIBADD = $(LIB_TDECORE) $(LIB_XCURSOR) +cursorthumbnail_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) + +exrthumbnail_la_SOURCES = exrcreator.cpp +exrthumbnail_la_LIBADD = $(LIB_TDECORE) $(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)/tdeio_thumbnail.pot + diff --git a/tdeioslave/thumbnail/configure.in.in b/tdeioslave/thumbnail/configure.in.in new file mode 100644 index 000000000..829727b69 --- /dev/null +++ b/tdeioslave/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/tdeioslave/thumbnail/cursorcreator.cpp b/tdeioslave/thumbnail/cursorcreator.cpp new file mode 100644 index 000000000..a8f6a2eb9 --- /dev/null +++ b/tdeioslave/thumbnail/cursorcreator.cpp @@ -0,0 +1,69 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Fredrik H�glund <[email protected]> + + 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 <tqimage.h> +#include <tqfile.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 TQString &path, int width, int height, TQImage &img ) +{ + XcursorImage *cursor = XcursorFilenameLoadImage( + TQFile::encodeName( path ).data(), + width > height ? height : width ); + + if ( cursor ) { + img = TQImage( reinterpret_cast<uchar *>( cursor->pixels ), + cursor->width, cursor->height, 32, + NULL, 0, TQImage::BigEndian ); + img.setAlphaBuffer( true ); + + // Convert the image to non-premultiplied alpha + TQ_UINT32 *pixels = reinterpret_cast<TQ_UINT32 *>( img.bits() ); + for ( int i = 0; i < (img.width() * img.height()); i++ ) { + float alpha = tqAlpha( pixels[i] ) / 255.0; + if ( alpha > 0.0 && alpha < 1.0 ) + pixels[i] = tqRgba( int( tqRed(pixels[i]) / alpha ), + int( tqGreen(pixels[i]) / alpha ), + int( tqBlue(pixels[i]) / alpha ), + tqAlpha(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/tdeioslave/thumbnail/cursorcreator.h b/tdeioslave/thumbnail/cursorcreator.h new file mode 100644 index 000000000..a9b845fd0 --- /dev/null +++ b/tdeioslave/thumbnail/cursorcreator.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Fredrik H�glund <[email protected]> + + 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 <tdeio/thumbcreator.h> + +class CursorCreator : public ThumbCreator +{ + public: + CursorCreator() {} + bool create( const TQString &path, int, int, TQImage &img ); +}; + +#endif + diff --git a/tdeioslave/thumbnail/cursorthumbnail.desktop b/tdeioslave/thumbnail/cursorthumbnail.desktop new file mode 100644 index 000000000..8aead0601 --- /dev/null +++ b/tdeioslave/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-TDE-Library=cursorthumbnail +CacheThumbnail=false diff --git a/tdeioslave/thumbnail/djvucreator.cpp b/tdeioslave/thumbnail/djvucreator.cpp new file mode 100644 index 000000000..20a32fb26 --- /dev/null +++ b/tdeioslave/thumbnail/djvucreator.cpp @@ -0,0 +1,139 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Malte Starostik <[email protected]> + Copyright (C) 2001 Leon Bottou <[email protected]> + + 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 <tqfile.h> +#include <tqimage.h> + +#include <kdemacros.h> + +#include "djvucreator.h" + + + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new DjVuCreator; + } +} + +bool DjVuCreator::create(const TQString &path, int width, int height, TQImage &img) +{ + int output[2]; + TQByteArray data(1024); + bool ok = false; + + if (pipe(output) == -1) + return false; + + const char* argv[8]; + TQCString sizearg, fnamearg; + sizearg.sprintf("%dx%d", width, height); + fnamearg = TQFile::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/tdeioslave/thumbnail/djvucreator.h b/tdeioslave/thumbnail/djvucreator.h new file mode 100644 index 000000000..b1335ac7c --- /dev/null +++ b/tdeioslave/thumbnail/djvucreator.h @@ -0,0 +1,35 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <[email protected]> + Copyright (C) 2003 Leon Bottou <[email protected]> + + 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 <tdeio/thumbcreator.h> + +class DjVuCreator : public ThumbCreator +{ +public: + DjVuCreator() {} + virtual bool create(const TQString &path, int, int, TQImage &img); + virtual Flags flags() const; + +}; + +#endif diff --git a/tdeioslave/thumbnail/djvuthumbnail.desktop b/tdeioslave/thumbnail/djvuthumbnail.desktop new file mode 100644 index 000000000..8608da662 --- /dev/null +++ b/tdeioslave/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-TDE-Library=djvuthumbnail +CacheThumbnail=true +IgnoreMaximumSize=true diff --git a/tdeioslave/thumbnail/exrcreator.cpp b/tdeioslave/thumbnail/exrcreator.cpp new file mode 100644 index 000000000..59c1fba76 --- /dev/null +++ b/tdeioslave/thumbnail/exrcreator.cpp @@ -0,0 +1,85 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Brad Hards <[email protected]> + + 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 <tqimage.h> + +#include <kimageio.h> +#include <kdebug.h> +#include <tdeconfig.h> +#include <kglobal.h> +#include <tqfile.h> + +#include <ImfInputFile.h> +#include <ImfPreviewImage.h> + +#include "exrcreator.h" + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + return new EXRCreator; + } +} + +bool EXRCreator::create(const TQString &path, int, int, TQImage &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(); + TQImage qpreview(preview.width(), preview.height(), 32, 0, TQImage::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, tqRgba(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; + TDEConfig * config = TDEGlobal::config(); + TDEConfigGroupSaver cgs( config, "PreviewSettings" ); + unsigned long long maxSize = config->readNumEntry( "MaximumSize", 1024*1024 /* 1MB */ ); + unsigned long long fileSize = TQFile( 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/tdeioslave/thumbnail/exrcreator.h b/tdeioslave/thumbnail/exrcreator.h new file mode 100644 index 000000000..8fb831bec --- /dev/null +++ b/tdeioslave/thumbnail/exrcreator.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (C) 2004 Brad Hards <[email protected]> + + 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 <tdeio/thumbcreator.h> + +class EXRCreator : public ThumbCreator +{ +public: + EXRCreator() {}; + virtual bool create(const TQString &path, int, int, TQImage &img); + virtual Flags flags() const; +}; + +#endif diff --git a/tdeioslave/thumbnail/exrthumbnail.desktop b/tdeioslave/thumbnail/exrthumbnail.desktop new file mode 100644 index 000000000..a1ec416a1 --- /dev/null +++ b/tdeioslave/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-TDE-Library=exrthumbnail +CacheThumbnail=true +IgnoreMaximumSize=true diff --git a/tdeioslave/thumbnail/htmlcreator.cpp b/tdeioslave/thumbnail/htmlcreator.cpp new file mode 100644 index 000000000..a928e4253 --- /dev/null +++ b/tdeioslave/thumbnail/htmlcreator.cpp @@ -0,0 +1,118 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <[email protected]> + Copyright (C) 2006 Roberto Cappuccio <[email protected]> + + 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 <tqpixmap.h> +#include <tqimage.h> +#include <tqpainter.h> +#include <kapplication.h> +#include <tdehtml_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 TQString &path, int width, int height, TQImage &img) +{ + if (!m_html) + { + m_html = new KHTMLPart; + connect(m_html, TQT_SIGNAL(completed()), TQT_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); + + tqApp->enter_loop(); + + killTimer(t); + + // render the HTML page on a bigger pixmap and use smoothScale, + // looks better than directly scaling with the TQPainter (malte) + TQPixmap 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( TQColor( 245, 245, 245 ) ); + + int borderX = pix.width() / width, borderY = pix.height() / height; + TQRect rc(borderX, borderY, pix.width() - borderX * 2, + pix.height() - borderY * 2); + + TQPainter p; + p.begin(&pix); + m_html->paint(&p, rc); + p.end(); + + img = pix.convertToImage(); + + m_html->closeURL(); + + return true; +} + +void HTMLCreator::timerEvent(TQTimerEvent *) +{ + tqApp->exit_loop(); +} + +void HTMLCreator::slotCompleted() +{ + tqApp->exit_loop(); +} + +ThumbCreator::Flags HTMLCreator::flags() const +{ + return DrawFrame; +} + +#include "htmlcreator.moc" + diff --git a/tdeioslave/thumbnail/htmlcreator.h b/tdeioslave/thumbnail/htmlcreator.h new file mode 100644 index 000000000..31ef0f398 --- /dev/null +++ b/tdeioslave/thumbnail/htmlcreator.h @@ -0,0 +1,47 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <[email protected]> + Copyright (C) 2006 Roberto Cappuccio <[email protected]> + + 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 <tdeio/thumbcreator.h> + +class KHTMLPart; + +class HTMLCreator : public TQObject, public ThumbCreator +{ + Q_OBJECT +public: + HTMLCreator(); + virtual ~HTMLCreator(); + virtual bool create(const TQString &path, int width, int height, TQImage &img); + virtual Flags flags() const; + +protected: + virtual void timerEvent(TQTimerEvent *); + +private slots: + void slotCompleted(); + +private: + KHTMLPart *m_html; +}; + +#endif diff --git a/tdeioslave/thumbnail/htmlthumbnail.desktop b/tdeioslave/thumbnail/htmlthumbnail.desktop new file mode 100644 index 000000000..084673f57 --- /dev/null +++ b/tdeioslave/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-TDE-Library=htmlthumbnail +CacheThumbnail=true diff --git a/tdeioslave/thumbnail/imagecreator.cpp b/tdeioslave/thumbnail/imagecreator.cpp new file mode 100644 index 000000000..259a8928f --- /dev/null +++ b/tdeioslave/thumbnail/imagecreator.cpp @@ -0,0 +1,51 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Carsten Pfeiffer <[email protected]> + 2000 Malte Starostik <[email protected]> + + 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 <tqimage.h> + +#include <kimageio.h> + +#include "imagecreator.h" + +extern "C" +{ + KDE_EXPORT ThumbCreator *new_creator() + { + KImageIO::registerFormats(); + return new ImageCreator; + } +} + +bool ImageCreator::create(const TQString &path, int, int, TQImage &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/tdeioslave/thumbnail/imagecreator.h b/tdeioslave/thumbnail/imagecreator.h new file mode 100644 index 000000000..05278611d --- /dev/null +++ b/tdeioslave/thumbnail/imagecreator.h @@ -0,0 +1,33 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <[email protected]> + + 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 <tdeio/thumbcreator.h> + +class ImageCreator : public ThumbCreator +{ +public: + ImageCreator() {} + virtual bool create(const TQString &path, int, int, TQImage &img); + virtual Flags flags() const; +}; + +#endif diff --git a/tdeioslave/thumbnail/imagethumbnail.desktop b/tdeioslave/thumbnail/imagethumbnail.desktop new file mode 100644 index 000000000..ca76f4d59 --- /dev/null +++ b/tdeioslave/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-TDE-Library=imagethumbnail +CacheThumbnail=true diff --git a/tdeioslave/thumbnail/textcreator.cpp b/tdeioslave/thumbnail/textcreator.cpp new file mode 100644 index 000000000..322c944a2 --- /dev/null +++ b/tdeioslave/thumbnail/textcreator.cpp @@ -0,0 +1,195 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000,2002 Carsten Pfeiffer <[email protected]> + 2000 Malte Starostik <[email protected]> + + 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 <tqfile.h> +#include <tqpixmap.h> +#include <tqimage.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 TQString &path, int width, int height, TQImage &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; + TQString pixmap = locate( "data", "konqueror/pics/thumbnailfont_7x4.png" ); + if ( !pixmap.isEmpty() ) + { + // FIXME: make font/glyphsize configurable... + m_splitter->setPixmap( TQPixmap( pixmap )); + m_splitter->setItemSize( TQSize( 4, 7 )); + } + } + + bool ok = false; + + // determine some sizes... + // example: width: 60, height: 64 + TQSize 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 + + TQSize 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 + TQFile 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'; + TQString text = TQString::fromLocal8Bit( m_data ); + // FIXME: maybe strip whitespace and read more? + + m_pixmap.fill( TQColor( 245, 245, 245 ) ); // light-grey background + + TQRect 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 TQPixmap *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) + TQChar 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, TQPoint(x,y), fontPixmap, rect, TQt::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/tdeioslave/thumbnail/textcreator.h b/tdeioslave/thumbnail/textcreator.h new file mode 100644 index 000000000..e3ab984d1 --- /dev/null +++ b/tdeioslave/thumbnail/textcreator.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <[email protected]> + + 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 <tqpixmap.h> +#include <tdeio/thumbcreator.h> + +class KPixmapSplitter; + +class TextCreator : public ThumbCreator +{ +public: + TextCreator(); + virtual ~TextCreator(); + virtual bool create(const TQString &path, int width, int height, TQImage &img); + virtual Flags flags() const; + +private: + KPixmapSplitter *m_splitter; + char *m_data; + int m_dataSize; + TQPixmap m_pixmap; +}; + +#endif diff --git a/tdeioslave/thumbnail/textthumbnail.desktop b/tdeioslave/thumbnail/textthumbnail.desktop new file mode 100644 index 000000000..16fa73a8b --- /dev/null +++ b/tdeioslave/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-TDE-Library=textthumbnail +CacheThumbnail=false diff --git a/tdeioslave/thumbnail/thumbcreator.desktop b/tdeioslave/thumbnail/thumbcreator.desktop new file mode 100644 index 000000000..e3151bf77 --- /dev/null +++ b/tdeioslave/thumbnail/thumbcreator.desktop @@ -0,0 +1,86 @@ +[Desktop Entry] +Type=ServiceType +X-TDE-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=TQStringList + +[PropertyDef::CacheThumbnail] +Type=bool + +[PropertyDef::IgnoreMaximumSize] +Type=bool diff --git a/tdeioslave/thumbnail/thumbnail.cpp b/tdeioslave/thumbnail/thumbnail.cpp new file mode 100644 index 000000000..1d5d4c929 --- /dev/null +++ b/tdeioslave/thumbnail/thumbnail.cpp @@ -0,0 +1,440 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <[email protected]> + 2000 Carsten Pfeiffer <[email protected]> + + 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 <tqfile.h> +#include <tqbitmap.h> +#include <tqpixmap.h> +#include <tqpainter.h> +#include <tqimage.h> +#include <tqbuffer.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 <tdefilemetainfo.h> +#include <klocale.h> + +#include <config.h> // For HAVE_NICE +#include "thumbnail.h" +#include <tdeio/thumbcreator.h> + +// Use correctly TDEInstance instead of TDEApplication (but then no TQPixmap) +#undef USE_KINSTANCE +// Fix thumbnail: protocol +#define THUMBNAIL_HACK (1) + +#ifdef THUMBNAIL_HACK +# include <tqfileinfo.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 TDEIO; + +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 + TDEInstance instance("kio_thumbnail"); +#else + // creating TDEApplication 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 TDEApplication :( + putenv(strdup("SESSION_MANAGER=")); + TDEApplication::disableAutoDcopRegistration(); + + TDEApplication 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 TQCString &pool, const TQCString &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; + TQFileInfo info(url.path()); + if (info.isDir()) + { + // We cannot process a directory + error(TDEIO::ERR_IS_DIRECTORY,url.path()); + return; + } + else if (!info.exists()) + { + // The file does not exist + error(TDEIO::ERR_DOES_NOT_EXIST,url.path()); + return; + } + else if (!info.isReadable()) + { + // The file is not readable! + error(TDEIO::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(TDEIO::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(TDEIO::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 = TDEGlobal::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; + + TQImage img; + + TDEConfigGroup group( TDEGlobal::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() == TQVariant::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"; + TQString plugin = metaData("plugin"); +#ifdef THUMBNAIL_HACK + if (plugin.isEmpty()) + { + KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator"); + TQMap<TQString, KService::Ptr> mimeMap; + + for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it) + { + TQStringList mimeTypes = (*it)->property("MimeTypes").toStringList(); + for (TQStringList::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(TDEIO::ERR_INTERNAL, i18n("No plugin specified.")); + return; + } + + ThumbCreator *creator = m_creators[plugin]; + if (!creator) + { + // Don't use KLibFactory here, this is not a TQObject and + // neither is ThumbCreator + KLibrary *library = KLibLoader::self()->library(TQFile::encodeName(plugin)); + if (library) + { + newCreator create = (newCreator)library->symbol("new_creator"); + if (create) + creator = create(); + } + if (!creator) + { + error(TDEIO::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(TDEIO::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(TQMAX((double)m_height / imgRatio, 1)), m_height); + else + img = img.smoothScale(m_width, int(TQMAX((double)m_width * imgRatio, 1))); + } + +// ### FIXME +#ifndef USE_KINSTANCE + if (flags & ThumbCreator::DrawFrame) + { + TQPixmap pix; + pix.convertFromImage(img); + int x2 = pix.width() - 1; + int y2 = pix.height() - 1; + // paint a black rectangle around the "page" + TQPainter p; + p.begin( &pix ); + p.setPen( TQColor( 48, 48, 48 )); + p.drawLine( x2, 0, x2, y2 ); + p.drawLine( 0, y2, x2, y2 ); + p.setPen( TQColor( 215, 215, 215 )); + p.drawLine( 0, 0, x2, 0 ); + p.drawLine( 0, 0, 0, y2 ); + p.end(); + + const TQBitmap *mask = pix.mask(); + if ( mask ) // need to update it so we can see the frame + { + TQBitmap bitmap( *mask ); + TQPainter 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) && TDEGlobal::iconLoader()->alphaBlending(KIcon::Desktop)) + { + // blending the mimetype icon in + TQImage 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(TDEIO::ERR_INTERNAL, i18n("Failed to create a thumbnail.")); + return; + } + + const TQString 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; + TQBuffer buf; + if (!buf.open(IO_WriteOnly)) + { + error(TDEIO::ERR_INTERNAL, i18n("Could not write image.")); + return; + } + img.save(&buf,"PNG"); + buf.close(); + data(buf.buffer()); + } + else +#endif + { + TQByteArray imgData; + TQDataStream stream( imgData, IO_WriteOnly ); + //kdDebug(7115) << "IMAGE TO STREAM" << endl; + stream << img; + data(imgData); + } + } + else + { + TQByteArray imgData; + TQDataStream stream( imgData, IO_WriteOnly ); + //kdDebug(7115) << "IMAGE TO SHMID" << endl; + void *shmaddr = shmat(shmid.toInt(), 0, 0); + if (shmaddr == (void *)-1) + { + error(TDEIO::ERR_INTERNAL, i18n("Failed to attach to shared memory segment %1").arg(shmid)); + return; + } + if (img.width() * img.height() > m_width * m_height) + { + error(TDEIO::ERR_INTERNAL, i18n("Image is too big for the shared memory segment")); + shmdt((char*)shmaddr); + return; + } + if( img.depth() != 32 ) // TDEIO::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 TQImage& ThumbnailProtocol::getIcon() +{ + TQImage* icon = m_iconDict.find(m_mimeType); + if ( !icon ) // generate it! + { + icon = new TQImage( 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/tdeioslave/thumbnail/thumbnail.h b/tdeioslave/thumbnail/thumbnail.h new file mode 100644 index 000000000..760d3b151 --- /dev/null +++ b/tdeioslave/thumbnail/thumbnail.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Malte Starostik <[email protected]> + + 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 <tqdict.h> + +#include <tdeio/slavebase.h> + +class ThumbCreator; +class TQImage; + +class ThumbnailProtocol : public TDEIO::SlaveBase +{ +public: + ThumbnailProtocol(const TQCString &pool, const TQCString &app); + virtual ~ThumbnailProtocol(); + + virtual void get(const KURL &url); + +protected: + const TQImage& getIcon(); + +private: + TQString m_mimeType; + int m_width; + int m_height; + bool m_keepAspectRatio; + int m_iconSize; + int m_iconAlpha; + // Thumbnail creators + TQDict<ThumbCreator> m_creators; + // transparent icon cache + TQDict<TQImage> m_iconDict; +}; + +#endif diff --git a/tdeioslave/thumbnail/thumbnail.protocol b/tdeioslave/thumbnail/thumbnail.protocol new file mode 100644 index 000000000..b1426c893 --- /dev/null +++ b/tdeioslave/thumbnail/thumbnail.protocol @@ -0,0 +1,9 @@ +[Protocol] +exec=kio_thumbnail +protocol=thumbnail +input=stream +output=stream +reading=true +source=false +DocPath=tdeioslave/thumbnail.html +Icon=thumbnail diff --git a/tdeioslave/trash/CMakeLists.txt b/tdeioslave/trash/CMakeLists.txt new file mode 100644 index 000000000..e54fba381 --- /dev/null +++ b/tdeioslave/trash/CMakeLists.txt @@ -0,0 +1,59 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +add_subdirectory( tdefile-plugin ) + + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES trash.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### trashcommon (static) ###################### + +set( target trashcommon ) + +tde_add_library( ${target} STATIC_PIC AUTOMOC + SOURCES trashimpl.cpp +) + + +##### kio_trash (module) ######################## + +set( target kio_trash ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES kio_trash.cpp + LINK trashcommon-static tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) + + +##### ktrash (executable) ####################### + +set( target ktrash ) + +tde_add_executable( ${target} + SOURCES ktrash.cpp + LINK tdeio-shared + DESTINATION ${BIN_INSTALL_DIR} +) diff --git a/tdeioslave/trash/DESIGN b/tdeioslave/trash/DESIGN new file mode 100644 index 000000000..63179da08 --- /dev/null +++ b/tdeioslave/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/tdeioslave/trash/Makefile.am b/tdeioslave/trash/Makefile.am new file mode 100644 index 000000000..dae7debd5 --- /dev/null +++ b/tdeioslave/trash/Makefile.am @@ -0,0 +1,31 @@ +INCLUDES = $(all_includes) +METASOURCES = AUTO + +SUBDIRS = . tdefile-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) $(LIB_QT) -lDCOP $(LIB_TDECORE) $(LIB_TDEUI) -ltdefx $(LIB_KIO) -ltdetexteditor + +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)/tdeio_trash.pot + diff --git a/tdeioslave/trash/kio_trash.cpp b/tdeioslave/trash/kio_trash.cpp new file mode 100644 index 000000000..26cfb4c6b --- /dev/null +++ b/tdeioslave/trash/kio_trash.cpp @@ -0,0 +1,596 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <[email protected]> + + 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 <tdeio/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 <tqdatastream.h> +#include <tqtextstream.h> +#include <tqfile.h> +#include <tqeventloop.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 ) + { + //TDEInstance instance( "kio_trash" ); + // TDEApplication is necessary to use kio_file + putenv(strdup("SESSION_MANAGER=")); + TDEApplication::disableAutoDcopRegistration(); + TDECmdLineArgs::init(argc, argv, "kio_trash", 0, 0, 0, 0); + TDECmdLineArgs::addCmdLineOptions( options ); + TDEApplication app( false, false ); + + TDECmdLineArgs *args = TDECmdLineArgs::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 TQCString& protocol, const TQCString &pool, const TQCString &app) + : SlaveBase(protocol, pool, app ) +{ + struct passwd *user = getpwuid( getuid() ); + if ( user ) + m_userName = TQString::fromLatin1(user->pw_name); + struct group *grp = getgrgid( getgid() ); + if ( grp ) + m_groupName = TQString::fromLatin1(grp->gr_name); +} + +TrashProtocol::~TrashProtocol() +{ +} + +void TrashProtocol::restore( const KURL& trashURL ) +{ + int trashId; + TQString fileId, relativePath; + bool ok = TrashImpl::parseURL( trashURL, trashId, fileId, relativePath ); + if ( !ok ) { + error( TDEIO::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 TQString destDir = dest.directory(); + KDE_struct_stat buff; + if ( KDE_lstat( TQFile::encodeName( destDir ), &buff ) == -1 ) { + error( TDEIO::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( TDEIO::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( TDEIO::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; + TQString fileId, relativePath; + bool ok = TrashImpl::parseURL( src, trashId, fileId, relativePath ); + if ( !ok ) { + error( TDEIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( src.prettyURL() ) ); + return; + } + const TQString destPath = dest.path(); + if ( TQFile::exists( destPath ) ) { + if ( overwrite ) { + ok = TQFile::remove( destPath ); + Q_ASSERT( ok ); // ### TODO + } else { + error( TDEIO::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" ) { + TQString 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 TQString 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; + TQString 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, TQString::null ); + setMetaData( "trashURL-" + srcPath, url.url() ); + finished(); + } + } + return; + } else { + kdDebug() << "returning TDEIO::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( TDEIO::ERR_ACCESS_DENIED, dest.prettyURL() ); + return; + } + } else + error( TDEIO::ERR_UNSUPPORTED_ACTION, "should never happen" ); +} + +static void addAtom(TDEIO::UDSEntry& entry, unsigned int ID, long long l, const TQString& s = TQString::null) +{ + TDEIO::UDSAtom atom; + atom.m_uds = ID; + atom.m_long = l; + atom.m_str = s; + entry.append(atom); +} + +void TrashProtocol::createTopLevelDirEntry(TDEIO::UDSEntry& entry) +{ + entry.clear(); + addAtom(entry, TDEIO::UDS_NAME, 0, "."); + addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR); + addAtom(entry, TDEIO::UDS_ACCESS, 0700); + addAtom(entry, TDEIO::UDS_MIME_TYPE, 0, "inode/directory"); + addAtom(entry, TDEIO::UDS_USER, 0, m_userName); + addAtom(entry, TDEIO::UDS_GROUP, 0, m_groupName); +} + +void TrashProtocol::stat(const KURL& url) +{ + INIT_IMPL; + const TQString path = url.path(); + if( path.isEmpty() || path == "/" ) { + // The root is "virtual" - it's not a single physical directory + TDEIO::UDSEntry entry; + createTopLevelDirEntry( entry ); + statEntry( entry ); + finished(); + } else { + int trashId; + TQString 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( TDEIO::ERR_DOES_NOT_EXIST, url.prettyURL() ); + //error( TDEIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + + const TQString filePath = impl.physicalPath( trashId, fileId, relativePath ); + if ( filePath.isEmpty() ) { + error( impl.lastErrorCode(), impl.lastErrorMessage() ); + return; + } + + TQString fileName = filePath.section('/', -1, -1, TQString::SectionSkipEmpty); + + TQString fileURL = TQString::null; + if ( url.path().length() > 1 ) { + fileURL = url.url(); + } + + TDEIO::UDSEntry entry; + TrashedFileInfo info; + ok = impl.infoForFile( trashId, fileId, info ); + if ( ok ) + ok = createUDSEntry( filePath, fileName, fileURL, entry, info ); + + if ( !ok ) { + error( TDEIO::ERR_COULD_NOT_STAT, url.prettyURL() ); + } + + statEntry( entry ); + finished(); + } +} + +void TrashProtocol::del( const KURL &url, bool /*isfile*/ ) +{ + int trashId; + TQString fileId, relativePath; + + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + if ( !ok ) { + error( TDEIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + + ok = relativePath.isEmpty(); + if ( !ok ) { + error( TDEIO::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; + TQString fileId; + TQString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + if ( !ok ) { + error( TDEIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + //was: const TQString 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; + TQStrList entryNames = impl.listDir( info.physicalPath ); + totalSize( entryNames.count() ); + TDEIO::UDSEntry entry; + TQStrListIterator entryIt( entryNames ); + for (; entryIt.current(); ++entryIt) { + TQString fileName = TQFile::decodeName( entryIt.current() ); + if ( fileName == ".." ) + continue; + const TQString filePath = info.physicalPath + "/" + fileName; + // shouldn't be necessary + //const TQString url = TrashImpl::makeURL( trashId, fileId, relativePath + "/" + fileName ); + entry.clear(); + TrashedFileInfo infoForItem( info ); + infoForItem.origPath += '/'; + infoForItem.origPath += fileName; + if ( ok && createUDSEntry( filePath, fileName, TQString::null /*url*/, entry, infoForItem ) ) { + listEntry( entry, false ); + } + } + entry.clear(); + listEntry( entry, true ); + finished(); +} + +bool TrashProtocol::createUDSEntry( const TQString& physicalPath, const TQString& fileName, const TQString& url, TDEIO::UDSEntry& entry, const TrashedFileInfo& info ) +{ + TQCString physicalPath_c = TQFile::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, TDEIO::UDS_LINK_DEST, 0, TQFile::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, TDEIO::UDS_NAME, 0, fileName ); + addAtom( entry, TDEIO::UDS_FILE_TYPE, type ); + if ( !url.isEmpty() ) + addAtom( entry, TDEIO::UDS_URL, 0, url ); + + KMimeType::Ptr mt = KMimeType::findByPath( physicalPath, buff.st_mode ); + addAtom( entry, TDEIO::UDS_MIME_TYPE, 0, mt->name() ); + addAtom( entry, TDEIO::UDS_ACCESS, access ); + addAtom( entry, TDEIO::UDS_SIZE, buff.st_size ); + addAtom( entry, TDEIO::UDS_USER, 0, m_userName ); // assumption + addAtom( entry, TDEIO::UDS_GROUP, 0, m_groupName ); // assumption + addAtom( entry, TDEIO::UDS_MODIFICATION_TIME, buff.st_mtime ); + addAtom( entry, TDEIO::UDS_ACCESS_TIME, buff.st_atime ); // ## or use it for deletion time? + addAtom( entry, TDEIO::UDS_EXTRA, 0, info.origPath ); + addAtom( entry, TDEIO::UDS_EXTRA, 0, info.deletionDate.toString( Qt::ISODate ) ); + return true; +} + +void TrashProtocol::listRoot() +{ + INIT_IMPL; + const TrashedFileInfoList lst = impl.list(); + totalSize( lst.count() ); + TDEIO::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, TQString::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 TQByteArray & data ) +{ + INIT_IMPL; + TQDataStream 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( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::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( TDEIO::ERR_ACCESS_DENIED, url.prettyURL() ); +} + +void TrashProtocol::get( const KURL& url ) +{ + INIT_IMPL; + kdDebug() << "get() : " << url << endl; + if ( !url.isValid() ) { + kdDebug() << kdBacktrace() << endl; + error( TDEIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.url() ) ); + return; + } + if ( url.path().length() <= 1 ) { + error( TDEIO::ERR_IS_DIRECTORY, url.prettyURL() ); + return; + } + int trashId; + TQString fileId; + TQString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + if ( !ok ) { + error( TDEIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) ); + return; + } + const TQString 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 ); + TDEIO::Job* job = TDEIO::get( fileURL ); + connect( job, TQT_SIGNAL( data( TDEIO::Job*, const TQByteArray& ) ), + this, TQT_SLOT( slotData( TDEIO::Job*, const TQByteArray& ) ) ); + connect( job, TQT_SIGNAL( mimetype( TDEIO::Job*, const TQString& ) ), + this, TQT_SLOT( slotMimetype( TDEIO::Job*, const TQString& ) ) ); + connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), + this, TQT_SLOT( jobFinished(TDEIO::Job *) ) ); + tqApp->eventLoop()->enterLoop(); +} + +void TrashProtocol::slotData( TDEIO::Job*, const TQByteArray&arr ) +{ + data( arr ); +} + +void TrashProtocol::slotMimetype( TDEIO::Job*, const TQString& mt ) +{ + mimeType( mt ); +} + +void TrashProtocol::jobFinished( TDEIO::Job* job ) +{ + if ( job->error() ) + error( job->error(), job->errorText() ); + else + finished(); + tqApp->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; + TQString dir = url.directory(); + + if ( dir.length() <= 1 ) // new toplevel entry + { + // ## we should use TrashImpl::parseURL to give the right filename to createInfo + int trashId; + TQString 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( TDEIO::ERR_ACCESS_DENIED, url.prettyURL() ); + } +} +#endif + +#include "kio_trash.moc" diff --git a/tdeioslave/trash/kio_trash.h b/tdeioslave/trash/kio_trash.h new file mode 100644 index 000000000..ceaa27cfb --- /dev/null +++ b/tdeioslave/trash/kio_trash.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <[email protected]> + + 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 <tdeio/slavebase.h> +#include "trashimpl.h" +namespace TDEIO { class Job; } + +typedef TrashImpl::TrashedFileInfo TrashedFileInfo; +typedef TrashImpl::TrashedFileInfoList TrashedFileInfoList; + +class TrashProtocol : public TQObject, public TDEIO::SlaveBase +{ + Q_OBJECT +public: + TrashProtocol( const TQCString& protocol, const TQCString &pool, const TQCString &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 TQByteArray & data ); + +private slots: + void slotData( TDEIO::Job*, const TQByteArray& ); + void slotMimetype( TDEIO::Job*, const TQString& ); + void jobFinished( TDEIO::Job* job ); + +private: + typedef enum CopyOrMove { Copy, Move }; + void copyOrMove( const KURL& src, const KURL& dest, bool overwrite, CopyOrMove action ); + void createTopLevelDirEntry(TDEIO::UDSEntry& entry); + bool createUDSEntry( const TQString& physicalPath, const TQString& fileName, const TQString& url, + TDEIO::UDSEntry& entry, const TrashedFileInfo& info ); + void listRoot(); + void restore( const KURL& trashURL ); + + TrashImpl impl; + TQString m_userName; + TQString m_groupName; +}; + +#endif diff --git a/tdeioslave/trash/ktrash.cpp b/tdeioslave/trash/ktrash.cpp new file mode 100644 index 000000000..395aabb6b --- /dev/null +++ b/tdeioslave/trash/ktrash.cpp @@ -0,0 +1,102 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <[email protected]> + + 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 <tdeio/netaccess.h> +#include <tdeio/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[]) +{ + TDEApplication::disableAutoDcopRegistration(); + TDECmdLineArgs::init( argc, argv, "ktrash", + I18N_NOOP( "ktrash" ), + I18N_NOOP( "Helper program to handle the TDE trash can\n" + "Note: to move files to the trash, do not use ktrash, but \"kfmclient move 'url' trash:/\"" ), + TDE_VERSION_STRING ); + TDECmdLineArgs::addCmdLineOptions( options ); + TDEApplication app; + + TDECmdLineArgs* args = TDECmdLineArgs::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) + TQByteArray packedArgs; + TQDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)1; + TDEIO::Job* job = TDEIO::special( "trash:/", packedArgs ); + (void)TDEIO::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" ) ) { + TQByteArray packedArgs; + TQDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)2; + TDEIO::Job* job = TDEIO::special( "trash:/", packedArgs ); + (void)TDEIO::NetAccess::synchronousRun( job, 0 ); + return 0; + } +#endif + + TQCString 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; + } + + TQByteArray packedArgs; + TQDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << trashURL; + TDEIO::Job* job = TDEIO::special( trashURL, packedArgs ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0 ); + if ( !ok ) + kdError() << TDEIO::NetAccess::lastErrorString() << endl; + return 0; + } + + return 0; +} diff --git a/tdeioslave/trash/tdefile-plugin/CMakeLists.txt b/tdeioslave/trash/tdefile-plugin/CMakeLists.txt new file mode 100644 index 000000000..cfd52e186 --- /dev/null +++ b/tdeioslave/trash/tdefile-plugin/CMakeLists.txt @@ -0,0 +1,38 @@ +################################################# +# +# (C) 2010-2011 Serghei Amelian +# serghei (DOT) amelian (AT) gmail.com +# +# Improvements and feedback are welcome +# +# This file is released under GPL >= 2 +# +################################################# + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR} + ${TDE_INCLUDE_DIR} + ${TQT_INCLUDE_DIRS} +) + +link_directories( + ${TQT_LIBRARY_DIRS} +) + + +##### other data ################################ + +install( FILES + tdefile_trash.desktop tdefile_trash_system.desktop + DESTINATION ${SERVICES_INSTALL_DIR} ) + + +##### tdefile_trash (module) ###################### + +set( target tdefile_trash ) + +tde_add_kpart( ${target} AUTOMOC + SOURCES tdefile_trash.cpp + LINK trashcommon-static tdeio-shared + DESTINATION ${PLUGIN_INSTALL_DIR} +) diff --git a/tdeioslave/trash/tdefile-plugin/Makefile.am b/tdeioslave/trash/tdefile-plugin/Makefile.am new file mode 100644 index 000000000..50d2373e9 --- /dev/null +++ b/tdeioslave/trash/tdefile-plugin/Makefile.am @@ -0,0 +1,14 @@ +## Makefile.am for trash file meta info plugin + +AM_CPPFLAGS = $(all_includes) + +kde_module_LTLIBRARIES = tdefile_trash.la + +tdefile_trash_la_SOURCES = tdefile_trash.cpp +tdefile_trash_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) +tdefile_trash_la_LIBADD = ../libtrashcommon.la $(LIB_KIO) + +METASOURCES = AUTO + +services_DATA = tdefile_trash.desktop tdefile_trash_system.desktop +servicesdir = $(kde_servicesdir) diff --git a/tdeioslave/trash/tdefile-plugin/RETURNED_ITEMS b/tdeioslave/trash/tdefile-plugin/RETURNED_ITEMS new file mode 100644 index 000000000..f4e10f262 --- /dev/null +++ b/tdeioslave/trash/tdefile-plugin/RETURNED_ITEMS @@ -0,0 +1,4 @@ +tdefile_trash +=========== +QString OriginalPath +DateTime DateOfDeletion diff --git a/tdeioslave/trash/tdefile-plugin/tdefile_trash.cpp b/tdeioslave/trash/tdefile-plugin/tdefile_trash.cpp new file mode 100644 index 000000000..cf4487d06 --- /dev/null +++ b/tdeioslave/trash/tdefile-plugin/tdefile_trash.cpp @@ -0,0 +1,93 @@ +/* This file is part of the KDE project + * Copyright (C) 2004 David Faure <[email protected]> + * Based on tdefile_txt.cpp by Nadeem Hasan <[email protected]> + * + * 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 "tdefile_trash.h" + +#include <kgenericfactory.h> +#include <kdebug.h> + +#include <tqfile.h> +#include <tqstringlist.h> +#include <tqdatetime.h> + +typedef KGenericFactory<KTrashPlugin> TrashFactory; + +K_EXPORT_COMPONENT_FACTORY(tdefile_trash, TrashFactory("tdefile_trash")) + +KTrashPlugin::KTrashPlugin(TQObject *parent, const char *name, + const TQStringList &args) : KFilePlugin(parent, name, args) +{ + TDEGlobal::locale()->insertCatalogue( "kio_trash" ); + + kdDebug(7034) << "Trash file meta info plugin\n"; + + makeMimeTypeInfo("trash"); + makeMimeTypeInfo("system"); + + (void)impl.init(); +} + +void KTrashPlugin::makeMimeTypeInfo(const TQString& mimeType) +{ + KFileMimeTypeInfo* info = addMimeTypeInfo( mimeType ); + + KFileMimeTypeInfo::GroupInfo* group = + addGroupInfo(info, "General", i18n("General")); + + KFileMimeTypeInfo::ItemInfo* item; + item = addItemInfo(group, "OriginalPath", i18n("Original Path"), TQVariant::String); + item = addItemInfo(group, "DateOfDeletion", i18n("Date of Deletion"), TQVariant::DateTime); +} + +bool KTrashPlugin::readInfo(KFileMetaInfo& info, uint) +{ + KURL url = info.url(); + + if ( url.protocol()=="system" + && url.path().startsWith("/trash") ) + { + TQString 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; + TQString fileId; + TQString 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 "tdefile_trash.moc" diff --git a/tdeioslave/trash/tdefile-plugin/tdefile_trash.desktop b/tdeioslave/trash/tdefile-plugin/tdefile_trash.desktop new file mode 100644 index 000000000..18416c2f0 --- /dev/null +++ b/tdeioslave/trash/tdefile-plugin/tdefile_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-TDE-Library=tdefile_trash +X-TDE-Protocol=trash +PreferredGroups=General +PreferredItems=OriginalPath,DateOfDeletion diff --git a/tdeioslave/trash/tdefile-plugin/tdefile_trash.h b/tdeioslave/trash/tdefile-plugin/tdefile_trash.h new file mode 100644 index 000000000..ad3d276d6 --- /dev/null +++ b/tdeioslave/trash/tdefile-plugin/tdefile_trash.h @@ -0,0 +1,42 @@ +/* This file is part of the KDE project + * Copyright (C) 2004 David Faure <[email protected]> + * Based on tdefile_txt.h by Nadeem Hasan <[email protected]> + * + * 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 <tdefilemetainfo.h> +#include "../trashimpl.h" + +class TQStringList; + +class KTrashPlugin: public KFilePlugin +{ + Q_OBJECT + +public: + KTrashPlugin(TQObject *parent, const char *name, const TQStringList& args); + virtual bool readInfo(KFileMetaInfo& info, uint what); + +private: + void makeMimeTypeInfo(const TQString& mimeType); + TrashImpl impl; +}; + +#endif diff --git a/tdeioslave/trash/tdefile-plugin/tdefile_trash_system.desktop b/tdeioslave/trash/tdefile-plugin/tdefile_trash_system.desktop new file mode 100644 index 000000000..3413db36c --- /dev/null +++ b/tdeioslave/trash/tdefile-plugin/tdefile_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-TDE-Library=tdefile_trash +X-TDE-Protocol=system +PreferredGroups=General +PreferredItems=OriginalPath,DateOfDeletion diff --git a/tdeioslave/trash/testtrash.cpp b/tdeioslave/trash/testtrash.cpp new file mode 100644 index 000000000..519de964c --- /dev/null +++ b/tdeioslave/trash/testtrash.cpp @@ -0,0 +1,1198 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <[email protected]> + + 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 <tdeio/netaccess.h> +#include <tdeio/job.h> +#include <kdebug.h> +#include <kcmdlineargs.h> + +#include <tqdir.h> +#include <tqfileinfo.h> +#include <tqvaluevector.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <assert.h> +#include <tdefileitem.h> +#include <kstandarddirs.h> + +static bool check(const TQString& txt, TQString a, TQString b) +{ + if (a.isEmpty()) + a = TQString::null; + if (b.isEmpty()) + b = TQString::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 TQFile::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( "TDE_UTF8_FILENAMES", "true", 1 ); +#else + unsetenv( "TDE_UTF8_FILENAMES" ); +#endif + + // Use another directory than the real one, just to keep things clean + setenv( "XDG_DATA_HOME", TQFile::encodeName( TQDir::homeDirPath() + "/.local-testtrash" ), true ); + setenv( "TDE_FORK_SLAVES", "yes", true ); + + TDEApplication::disableAutoDcopRegistration(); + TDECmdLineArgs::init(argc,argv,"testtrash", 0, 0, 0, 0); + TDEApplication 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. +} + +TQString TestTrash::homeTmpDir() const +{ + return TQDir::homeDirPath() + "/.trinity/testtrash/"; +} + +TQString TestTrash::readOnlyDirPath() const +{ + return homeTmpDir() + TQString( "readonly" ); +} + +TQString TestTrash::otherTmpDir() const +{ + // This one needs to be on another partition + return "/tmp/testtrash/"; +} + +TQString TestTrash::utf8FileName() const +{ + return TQString( "test" ) + TQChar( 0x2153 ); // "1/3" character, not part of latin1 +} + +TQString TestTrash::umlautFileName() const +{ + return TQString( "umlaut" ) + TQChar( 0xEB ); +} + +static void removeFile( const TQString& trashDir, const TQString& fileName ) +{ + TQDir dir; + dir.remove( trashDir + fileName ); + assert( !TQDir( trashDir + fileName ).exists() ); +} + +static void removeDir( const TQString& trashDir, const TQString& dirName ) +{ + TQDir dir; + dir.rmdir( trashDir + dirName ); + assert( !TQDir( trashDir + dirName ).exists() ); +} + +void TestTrash::setup() +{ + m_trashDir = TDEGlobal::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; + TQValueVector<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 TQString topdir = topDirs[it.key()]; + if ( TQFileInfo( 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 ( TQValueVector<int>::const_iterator it = writableTopDirs.begin(); it != writableTopDirs.end(); ++it ) { + const TQString topdir = topDirs[ *it ]; + const TQString 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 ( TQFileInfo( homeTmpDir() ).exists() ) { + bool ok = TDEIO::NetAccess::del( homeTmpDir(), 0 ); + if ( !ok ) + kdFatal() << "Couldn't delete " << homeTmpDir() << endl; + } + if ( TQFileInfo( otherTmpDir() ).exists() ) { + bool ok = TDEIO::NetAccess::del( otherTmpDir(), 0 ); + if ( !ok ) + kdFatal() << "Couldn't delete " << otherTmpDir() << endl; + } + TQDir 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" ); + TDEIO::NetAccess::del( m_trashDir + "/files/readonly", 0 ); + // for trashDirectoryOwnedByRoot + TDEIO::NetAccess::del( m_trashDir + "/files/cups", 0 ); + TDEIO::NetAccess::del( m_trashDir + "/files/boot", 0 ); + TDEIO::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", TQString::null ); + check( "makeURL for a file", url.url(), "trash:/1-fileId" ); + + int trashId; + TQString fileId; + TQString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + assert( ok ); + check( "parseURL: trashId", TQString::number( trashId ), "1" ); + check( "parseURL: fileId", fileId, "fileId" ); + check( "parseURL: relativePath", relativePath, TQString::null ); +} + +void TestTrash::urlTestDirectory() +{ + const KURL url = TrashImpl::makeURL( 1, "fileId", "subfile" ); + check( "makeURL", url.url(), "trash:/1-fileId/subfile" ); + + int trashId; + TQString fileId; + TQString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + assert( ok ); + check( "parseURL: trashId", TQString::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; + TQString fileId; + TQString relativePath; + bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath ); + assert( ok ); + check( "parseURL: trashId", TQString::number( trashId ), "1" ); + check( "parseURL: fileId", fileId, "fileId" ); + check( "parseURL: relativePath", relativePath, "subfile/foobar" ); +} + +static void checkInfoFile( const TQString& infoPath, const TQString& origFilePath ) +{ + kdDebug() << k_funcinfo << infoPath << endl; + TQFileInfo 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 TQString origPath = infoFile.readEntry( "Path" ); + assert( !origPath.isEmpty() ); + assert( origPath == KURL::encode_string( origFilePath, TDEGlobal::locale()->fileEncodingMib() ) ); + const TQString date = infoFile.readEntry( "DeletionDate" ); + assert( !date.isEmpty() ); + assert( date.contains( "T" ) ); +} + +static void createTestFile( const TQString& path ) +{ + TQFile f( path ); + if ( !f.open( IO_WriteOnly ) ) + kdFatal() << "Can't create " << path << endl; + f.writeBlock( "Hello world\n", 12 ); + f.close(); + assert( TQFile::exists( path ) ); +} + +void TestTrash::trashFile( const TQString& origFilePath, const TQString& fileId ) +{ + // setup + if ( !TQFile::exists( origFilePath ) ) + createTestFile( origFilePath ); + KURL u; + u.setPath( origFilePath ); + + // test + TDEIO::Job* job = TDEIO::move( u, "trash:/" ); + TQMap<TQString, TQString> metaData; + //bool ok = TDEIO::NetAccess::move( u, "trash:/" ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData ); + if ( !ok ) + kdError() << "moving " << u << " to trash failed with error " << TDEIO::NetAccess::lastError() << " " << TDEIO::NetAccess::lastErrorString() << endl; + assert( ok ); + if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) { + kdDebug() << " TESTS SKIPPED" << endl; + } else { + checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath ); + + TQFileInfo files( m_trashDir + "/files/" + fileId ); + assert( files.isFile() ); + assert( files.size() == 12 ); + } + + // coolo suggests testing that the original file is actually gone, too :) + assert( !TQFile::exists( origFilePath ) ); + + assert( !metaData.isEmpty() ); + bool found = false; + TQMap<TQString, TQString>::ConstIterator it = metaData.begin(); + for ( ; it != metaData.end() ; ++it ) { + if ( it.key().startsWith( "trashURL" ) ) { + const TQString 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() == "/" + TQString::number( trashId ) + "-" + fileId ); + found = true; + } + } + assert( found ); +} + +void TestTrash::trashFileFromHome() +{ + kdDebug() << k_funcinfo << endl; + const TQString 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 TQString fileName = "file%2f"; + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::trashUtf8FileFromHome() +{ + kdDebug() << k_funcinfo << endl; + const TQString fileName = utf8FileName(); + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::trashUmlautFileFromHome() +{ + kdDebug() << k_funcinfo << endl; + const TQString 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 TQString fileName = "fileFromOther"; + trashFile( otherTmpDir() + fileName, fileName ); +} + +void TestTrash::trashFileIntoOtherPartition() +{ + if ( m_otherPartitionTrashDir.isEmpty() ) { + kdDebug() << k_funcinfo << " - SKIPPED" << endl; + return; + } + kdDebug() << k_funcinfo << endl; + const TQString fileName = "testtrash-file"; + const TQString origFilePath = m_otherPartitionTopDir + fileName; + const TQString fileId = fileName; + // cleanup + TQFile::remove( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo" ); + TQFile::remove( m_otherPartitionTrashDir + "/files/" + fileId ); + + // setup + if ( !TQFile::exists( origFilePath ) ) + createTestFile( origFilePath ); + KURL u; + u.setPath( origFilePath ); + + // test + TDEIO::Job* job = TDEIO::move( u, "trash:/" ); + TQMap<TQString, TQString> metaData; + bool ok = TDEIO::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 ); + + TQFileInfo files( m_otherPartitionTrashDir + "/files/" + fileId ); + assert( files.isFile() ); + assert( files.size() == 12 ); + + // coolo suggests testing that the original file is actually gone, too :) + assert( !TQFile::exists( origFilePath ) ); + + assert( !metaData.isEmpty() ); + bool found = false; + TQMap<TQString, TQString>::ConstIterator it = metaData.begin(); + for ( ; it != metaData.end() ; ++it ) { + if ( it.key().startsWith( "trashURL" ) ) { + const TQString origPath = it.key().mid( 9 ); + KURL trashURL( it.data() ); + kdDebug() << trashURL << endl; + assert( !trashURL.isEmpty() ); + assert( trashURL.protocol() == "trash" ); + assert( trashURL.path() == TQString( "/%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 TQString fileId = "passwd"; + + TDEIO::CopyJob* job = TDEIO::move( u, "trash:/" ); + job->setInteractive( false ); // no skip dialog, thanks + TQMap<TQString, TQString> metaData; + //bool ok = TDEIO::NetAccess::move( u, "trash:/" ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData ); + assert( !ok ); + assert( TDEIO::NetAccess::lastError() == TDEIO::ERR_ACCESS_DENIED ); + const TQString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( !TQFile::exists( infoPath ) ); + + TQFileInfo files( m_trashDir + "/files/" + fileId ); + assert( !files.exists() ); + + assert( TQFile::exists( u.path() ) ); +} + +void TestTrash::trashSymlink( const TQString& origFilePath, const TQString& fileId, bool broken ) +{ + kdDebug() << k_funcinfo << endl; + // setup + const char* target = broken ? "/nonexistent" : "/tmp"; + bool ok = ::symlink( target, TQFile::encodeName( origFilePath ) ) == 0; + assert( ok ); + KURL u; + u.setPath( origFilePath ); + + // test + ok = TDEIO::NetAccess::move( u, "trash:/" ); + assert( ok ); + if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) { + kdDebug() << " TESTS SKIPPED" << endl; + return; + } + checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath ); + + TQFileInfo files( m_trashDir + "/files/" + fileId ); + assert( files.isSymLink() ); + assert( files.readLink() == TQFile::decodeName( target ) ); + assert( !TQFile::exists( origFilePath ) ); +} + +void TestTrash::trashSymlinkFromHome() +{ + kdDebug() << k_funcinfo << endl; + const TQString fileName = "symlinkFromHome"; + trashSymlink( homeTmpDir() + fileName, fileName, false ); +} + +void TestTrash::trashSymlinkFromOther() +{ + kdDebug() << k_funcinfo << endl; + const TQString fileName = "symlinkFromOther"; + trashSymlink( otherTmpDir() + fileName, fileName, false ); +} + +void TestTrash::trashBrokenSymlinkFromHome() +{ + kdDebug() << k_funcinfo << endl; + const TQString fileName = "brokenSymlinkFromHome"; + trashSymlink( homeTmpDir() + fileName, fileName, true ); +} + +void TestTrash::trashDirectory( const TQString& origPath, const TQString& fileId ) +{ + kdDebug() << k_funcinfo << fileId << endl; + // setup + if ( !TQFileInfo( origPath ).exists() ) { + TQDir dir; + bool ok = dir.mkdir( origPath ); + Q_ASSERT( ok ); + } + createTestFile( origPath + "/testfile" ); + KURL u; u.setPath( origPath ); + + // test + bool ok = TDEIO::NetAccess::move( u, "trash:/" ); + assert( ok ); + if ( origPath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) { + kdDebug() << " TESTS SKIPPED" << endl; + return; + } + checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origPath ); + + TQFileInfo filesDir( m_trashDir + "/files/" + fileId ); + assert( filesDir.isDir() ); + TQFileInfo files( m_trashDir + "/files/" + fileId + "/testfile" ); + assert( files.exists() ); + assert( files.isFile() ); + assert( files.size() == 12 ); + assert( !TQFile::exists( origPath ) ); +} + +void TestTrash::trashDirectoryFromHome() +{ + kdDebug() << k_funcinfo << endl; + TQString 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 TQString dirName = readOnlyDirPath(); + TQDir dir; + bool ok = dir.mkdir( dirName ); + Q_ASSERT( ok ); + // #130780 + const TQString subDirPath = dirName + "/readonly_subdir"; + ok = dir.mkdir( subDirPath ); + Q_ASSERT( ok ); + createTestFile( subDirPath + "/testfile_in_subdir" ); + ::chmod( TQFile::encodeName( subDirPath ), 0500 ); + + trashDirectory( dirName, "readonly" ); +} + +void TestTrash::trashDirectoryFromOther() +{ + kdDebug() << k_funcinfo << endl; + TQString dirName = "trashDirFromOther"; + trashDirectory( otherTmpDir() + dirName, dirName ); +} + +void TestTrash::tryRenameInsideTrash() +{ + kdDebug() << k_funcinfo << " with file_move" << endl; + bool worked = TDEIO::NetAccess::file_move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" ); + assert( !worked ); + assert( TDEIO::NetAccess::lastError() == TDEIO::ERR_CANNOT_RENAME ); + + kdDebug() << k_funcinfo << " with move" << endl; + worked = TDEIO::NetAccess::move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" ); + assert( !worked ); + assert( TDEIO::NetAccess::lastError() == TDEIO::ERR_CANNOT_RENAME ); +} + +void TestTrash::delRootFile() +{ + kdDebug() << k_funcinfo << endl; + + // test deleting a trashed file + bool ok = TDEIO::NetAccess::del( "trash:/0-fileFromHome", 0L ); + assert( ok ); + + TQFileInfo file( m_trashDir + "/files/fileFromHome" ); + assert( !file.exists() ); + TQFileInfo info( m_trashDir + "/info/fileFromHome.trashinfo" ); + assert( !info.exists() ); + + // trash it again, we might need it later + const TQString 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 = TDEIO::NetAccess::del( "trash:/0-trashDirFromHome/testfile", 0L ); + assert( !ok ); + assert( TDEIO::NetAccess::lastError() == TDEIO::ERR_ACCESS_DENIED ); + + TQFileInfo dir( m_trashDir + "/files/trashDirFromHome" ); + assert( dir.exists() ); + TQFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" ); + assert( file.exists() ); + TQFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" ); + assert( info.exists() ); +} + +void TestTrash::delDirectory() +{ + kdDebug() << k_funcinfo << endl; + + // test deleting a trashed directory + bool ok = TDEIO::NetAccess::del( "trash:/0-trashDirFromHome", 0L ); + assert( ok ); + + TQFileInfo dir( m_trashDir + "/files/trashDirFromHome" ); + assert( !dir.exists() ); + TQFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" ); + assert( !file.exists() ); + TQFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" ); + assert( !info.exists() ); + + // trash it again, we'll need it later + TQString dirName = "trashDirFromHome"; + trashDirectory( homeTmpDir() + dirName, dirName ); +} + +void TestTrash::statRoot() +{ + kdDebug() << k_funcinfo << endl; + KURL url( "trash:/" ); + TDEIO::UDSEntry entry; + bool ok = TDEIO::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" ); + TDEIO::UDSEntry entry; + bool ok = TDEIO::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" ); + TDEIO::UDSEntry entry; + bool ok = TDEIO::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" ); + TDEIO::UDSEntry entry; + bool ok = TDEIO::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" ); + TDEIO::UDSEntry entry; + bool ok = TDEIO::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 TQString& fileId, const TQString& destPath, const TQString& relativePath ) +{ + KURL src( "trash:/0-" + fileId ); + if ( !relativePath.isEmpty() ) + src.addPath( relativePath ); + KURL dest; + dest.setPath( destPath ); + + assert( TDEIO::NetAccess::exists( src, true, (TQWidget*)0 ) ); + + // A dnd would use copy(), but we use copyAs to ensure the final filename + //kdDebug() << k_funcinfo << "copyAs:" << src << " -> " << dest << endl; + TDEIO::Job* job = TDEIO::copyAs( src, dest ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + TQString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( TQFile::exists( infoFile ) ); + + TQFileInfo filesItem( m_trashDir + "/files/" + fileId ); + assert( filesItem.exists() ); + + assert( TQFile::exists( destPath ) ); +} + +void TestTrash::copyFileFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "fileFromHome_copied"; + copyFromTrash( "fileFromHome", destPath ); + assert( TQFileInfo( destPath ).isFile() ); + assert( TQFileInfo( destPath ).size() == 12 ); +} + +void TestTrash::copyFileInDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "testfile_copied"; + copyFromTrash( "trashDirFromHome", destPath, "testfile" ); + assert( TQFileInfo( destPath ).isFile() ); + assert( TQFileInfo( destPath ).size() == 12 ); +} + +void TestTrash::copyDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "trashDirFromHome_copied"; + copyFromTrash( "trashDirFromHome", destPath ); + assert( TQFileInfo( destPath ).isDir() ); +} + +void TestTrash::copySymlinkFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "symlinkFromHome_copied"; + copyFromTrash( "symlinkFromHome", destPath ); + assert( TQFileInfo( destPath ).isSymLink() ); +} + +void TestTrash::moveFromTrash( const TQString& fileId, const TQString& destPath, const TQString& relativePath ) +{ + KURL src( "trash:/0-" + fileId ); + if ( !relativePath.isEmpty() ) + src.addPath( relativePath ); + KURL dest; + dest.setPath( destPath ); + + assert( TDEIO::NetAccess::exists( src, true, (TQWidget*)0 ) ); + + // A dnd would use move(), but we use moveAs to ensure the final filename + TDEIO::Job* job = TDEIO::moveAs( src, dest ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + TQString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( !TQFile::exists( infoFile ) ); + + TQFileInfo filesItem( m_trashDir + "/files/" + fileId ); + assert( !filesItem.exists() ); + + assert( TQFile::exists( destPath ) ); +} + +void TestTrash::moveFileFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "fileFromHome_restored"; + moveFromTrash( "fileFromHome", destPath ); + assert( TQFileInfo( destPath ).isFile() ); + assert( TQFileInfo( destPath ).size() == 12 ); + + // trash it again for later + const TQString fileName = "fileFromHome"; + trashFile( homeTmpDir() + fileName, fileName ); +} + +void TestTrash::moveFileInDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "testfile_restored"; + copyFromTrash( "trashDirFromHome", destPath, "testfile" ); + assert( TQFileInfo( destPath ).isFile() ); + assert( TQFileInfo( destPath ).size() == 12 ); +} + +void TestTrash::moveDirectoryFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "trashDirFromHome_restored"; + moveFromTrash( "trashDirFromHome", destPath ); + assert( TQFileInfo( destPath ).isDir() ); + + // trash it again, we'll need it later + TQString dirName = "trashDirFromHome"; + trashDirectory( homeTmpDir() + dirName, dirName ); +} + +void TestTrash::trashDirectoryOwnedByRoot() +{ + KURL u; + if ( TQFile::exists( "/etc/cups" ) ) + u.setPath( "/etc/cups" ); + else if ( TQFile::exists( "/boot" ) ) + u.setPath( "/boot" ); + else + u.setPath( "/etc" ); + const TQString fileId = u.path(); + kdDebug() << k_funcinfo << "fileId=" << fileId << endl; + + TDEIO::CopyJob* job = TDEIO::move( u, "trash:/" ); + job->setInteractive( false ); // no skip dialog, thanks + TQMap<TQString, TQString> metaData; + bool ok = TDEIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData ); + assert( !ok ); + const int err = TDEIO::NetAccess::lastError(); + assert( err == TDEIO::ERR_ACCESS_DENIED + || err == TDEIO::ERR_CANNOT_OPEN_FOR_READING ); + + const TQString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" ); + assert( !TQFile::exists( infoPath ) ); + + TQFileInfo files( m_trashDir + "/files/" + fileId ); + assert( !files.exists() ); + + assert( TQFile::exists( u.path() ) ); +} + +void TestTrash::moveSymlinkFromTrash() +{ + kdDebug() << k_funcinfo << endl; + const TQString destPath = otherTmpDir() + "symlinkFromHome_restored"; + moveFromTrash( "symlinkFromHome", destPath ); + assert( TQFileInfo( destPath ).isSymLink() ); +} + +void TestTrash::getFile() +{ + kdDebug() << k_funcinfo << endl; + const TQString fileId = "fileFromHome_1"; + const KURL url = TrashImpl::makeURL( 0, fileId, TQString::null ); + TQString tmpFile; + bool ok = TDEIO::NetAccess::download( url, tmpFile, 0 ); + assert( ok ); + TQFile file( tmpFile ); + ok = file.open( IO_ReadOnly ); + assert( ok ); + TQByteArray str = file.readAll(); + TQCString cstr( str.data(), str.size() + 1 ); + if ( cstr != "Hello world\n" ) + kdFatal() << "get() returned the following data:" << cstr << endl; + file.close(); + TDEIO::NetAccess::removeTempFile( tmpFile ); +} + +void TestTrash::restoreFile() +{ + kdDebug() << k_funcinfo << endl; + const TQString fileId = "fileFromHome_1"; + const KURL url = TrashImpl::makeURL( 0, fileId, TQString::null ); + const TQString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + const TQString filesItem( m_trashDir + "/files/" + fileId ); + + assert( TQFile::exists( infoFile ) ); + assert( TQFile::exists( filesItem ) ); + + TQByteArray packedArgs; + TQDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << url; + TDEIO::Job* job = TDEIO::special( url, packedArgs ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + + assert( !TQFile::exists( infoFile ) ); + assert( !TQFile::exists( filesItem ) ); + + const TQString destPath = homeTmpDir() + "fileFromHome"; + assert( TQFile::exists( destPath ) ); +} + +void TestTrash::restoreFileFromSubDir() +{ + kdDebug() << k_funcinfo << endl; + const TQString fileId = "trashDirFromHome_1/testfile"; + assert( !TQFile::exists( homeTmpDir() + "trashDirFromHome_1" ) ); + + const KURL url = TrashImpl::makeURL( 0, fileId, TQString::null ); + const TQString infoFile( m_trashDir + "/info/trashDirFromHome_1.trashinfo" ); + const TQString filesItem( m_trashDir + "/files/trashDirFromHome_1/testfile" ); + + assert( TQFile::exists( infoFile ) ); + assert( TQFile::exists( filesItem ) ); + + TQByteArray packedArgs; + TQDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << url; + TDEIO::Job* job = TDEIO::special( url, packedArgs ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0 ); + assert( !ok ); + // dest dir doesn't exist -> error message + assert( TDEIO::NetAccess::lastError() == TDEIO::ERR_SLAVE_DEFINED ); + + // check that nothing happened + assert( TQFile::exists( infoFile ) ); + assert( TQFile::exists( filesItem ) ); + assert( !TQFile::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 = TDEIO::NetAccess::del( homeTmpDir(), 0 ); + assert( delOK ); + + const TQString fileId = "fileFromHome"; + const KURL url = TrashImpl::makeURL( 0, fileId, TQString::null ); + const TQString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" ); + const TQString filesItem( m_trashDir + "/files/" + fileId ); + + assert( TQFile::exists( infoFile ) ); + assert( TQFile::exists( filesItem ) ); + + TQByteArray packedArgs; + TQDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)3 << url; + TDEIO::Job* job = TDEIO::special( url, packedArgs ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0 ); + assert( !ok ); + // dest dir doesn't exist -> error message + assert( TDEIO::NetAccess::lastError() == TDEIO::ERR_SLAVE_DEFINED ); + + // check that nothing happened + assert( TQFile::exists( infoFile ) ); + assert( TQFile::exists( filesItem ) ); + + const TQString destPath = homeTmpDir() + "fileFromHome"; + assert( !TQFile::exists( destPath ) ); +} + +void TestTrash::listRootDir() +{ + kdDebug() << k_funcinfo << endl; + m_entryCount = 0; + m_listResult.clear(); + TDEIO::ListJob* job = TDEIO::listDir( "trash:/" ); + connect( job, TQT_SIGNAL( entries( TDEIO::Job*, const TDEIO::UDSEntryList& ) ), + TQT_SLOT( slotEntries( TDEIO::Job*, const TDEIO::UDSEntryList& ) ) ); + bool ok = TDEIO::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(); + TDEIO::ListJob* job = TDEIO::listRecursive( "trash:/" ); + connect( job, TQT_SIGNAL( entries( TDEIO::Job*, const TDEIO::UDSEntryList& ) ), + TQT_SLOT( slotEntries( TDEIO::Job*, const TDEIO::UDSEntryList& ) ) ); + bool ok = TDEIO::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(); + TDEIO::ListJob* job = TDEIO::listDir( "trash:/0-trashDirFromHome" ); + connect( job, TQT_SIGNAL( entries( TDEIO::Job*, const TDEIO::UDSEntryList& ) ), + TQT_SLOT( slotEntries( TDEIO::Job*, const TDEIO::UDSEntryList& ) ) ); + bool ok = TDEIO::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( TDEIO::Job*, const TDEIO::UDSEntryList& lst ) +{ + for( TDEIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) { + TDEIO::UDSEntry::ConstIterator it2 = (*it).begin(); + TQString displayName; + KURL url; + for( ; it2 != (*it).end(); it2++ ) { + switch ((*it2).m_uds) { + case TDEIO::UDS_NAME: + displayName = (*it2).m_str; + break; + case TDEIO::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; + TQByteArray packedArgs; + TQDataStream stream( packedArgs, IO_WriteOnly ); + stream << (int)1; + TDEIO::Job* job = TDEIO::special( "trash:/", packedArgs ); + bool ok = TDEIO::NetAccess::synchronousRun( job, 0 ); + assert( ok ); + + KSimpleConfig cfg( "trashrc", true ); + assert( cfg.hasGroup( "Status" ) ); + cfg.setGroup( "Status" ); + assert( cfg.readBoolEntry( "Empty", false ) == true ); + + assert( !TQFile::exists( m_trashDir + "/files/fileFromHome" ) ); + assert( !TQFile::exists( m_trashDir + "/files/readonly" ) ); + assert( !TQFile::exists( m_trashDir + "/info/readonly.trashinfo" ) ); + +#else + kdDebug() << k_funcinfo << " : SKIPPED" << endl; +#endif +} + +#include "testtrash.moc" diff --git a/tdeioslave/trash/testtrash.h b/tdeioslave/trash/testtrash.h new file mode 100644 index 000000000..ebc217aac --- /dev/null +++ b/tdeioslave/trash/testtrash.h @@ -0,0 +1,118 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <[email protected]> + + 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 <tqobject.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( TDEIO::Job*, const TDEIO::UDSEntryList& ); + +private: + void trashFile( const TQString& origFilePath, const TQString& fileId ); + void trashSymlink( const TQString& origFilePath, const TQString& fileName, bool broken ); + void trashDirectory( const TQString& origPath, const TQString& fileName ); + void copyFromTrash( const TQString& fileId, const TQString& destPath, const TQString& relativePath = TQString::null ); + void moveFromTrash( const TQString& fileId, const TQString& destPath, const TQString& relativePath = TQString::null ); + + TQString homeTmpDir() const; + TQString otherTmpDir() const; + TQString utf8FileName() const; + TQString umlautFileName() const; + TQString readOnlyDirPath() const; + + TQString m_trashDir; + + TQString m_otherPartitionTopDir; + TQString m_otherPartitionTrashDir; + bool m_tmpIsWritablePartition; + int m_tmpTrashId; + int m_otherPartitionId; + + int m_entryCount; + TQStringList m_listResult; +}; + +#endif diff --git a/tdeioslave/trash/trash.protocol b/tdeioslave/trash/trash.protocol new file mode 100644 index 000000000..8387fdcb7 --- /dev/null +++ b/tdeioslave/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/tdeioslave/trash/trashimpl.cpp b/tdeioslave/trash/trashimpl.cpp new file mode 100644 index 000000000..a31ee1122 --- /dev/null +++ b/tdeioslave/trash/trashimpl.cpp @@ -0,0 +1,962 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <[email protected]> + + 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 <tdeio/global.h> +#include <tdeio/renamedlg.h> +#include <tdeio/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 <tdefileitem.h> +#include <tdeio/chmodjob.h> + +#include <dcopref.h> + +#include <tqapplication.h> +#include <tqeventloop.h> +#include <tqfile.h> +#include <tqdir.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() : + TQObject(), + m_lastErrorCode( 0 ), + m_initStatus( InitToBeDone ), + m_lastId( 0 ), + m_homeDevice( 0 ), + m_trashDirectoriesScanned( false ), + m_mibEnum( TDEGlobal::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( TQFile::encodeName( TQDir::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 TQString &_name ) const +{ + DIR *dp = opendir( TQFile::encodeName(_name) ); + if ( dp == NULL ) + { + TQString name = _name; + if ( name.endsWith( "/" ) ) + name.truncate( name.length() - 1 ); + TQCString path = TQFile::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 TDE 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 TDEIO::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 TDEIO::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 TQString xdgDataDir = TDEGlobal::dirs()->localxdgdatadir(); + if ( !KStandardDirs::makeDir( xdgDataDir, 0700 ) ) { + kdWarning() << "failed to create " << xdgDataDir << endl; + return false; + } + + const TQString 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 TQString oldTrashDir = TDEGlobalSettings::trashPath(); + const TQStrList entries = listDir( oldTrashDir ); + bool allOK = true; + TQStrListIterator entryIt( entries ); + for (; entryIt.current(); ++entryIt) { + TQString srcPath = TQFile::decodeName( *entryIt ); + if ( srcPath == "." || srcPath == ".." || srcPath == ".directory" ) + continue; + srcPath.prepend( oldTrashDir ); // make absolute + int trashId; + TQString 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 TQString& origPath, int& trashId, TQString& fileId ) +{ + kdDebug() << k_funcinfo << origPath << endl; + // Check source + const TQCString origPath_c( TQFile::encodeName( origPath ) ); + KDE_struct_stat buff_src; + if ( KDE_lstat( origPath_c.data(), &buff_src ) == -1 ) { + if ( errno == EACCES ) + error( TDEIO::ERR_ACCESS_DENIED, origPath ); + else + error( TDEIO::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 TQString 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 tdeioslave processes + int fd = 0; + do { + kdDebug() << k_funcinfo << "trying to create " << url.path() << endl; + fd = ::open( TQFile::encodeName( url.path() ), O_WRONLY | O_CREAT | O_EXCL, 0600 ); + if ( fd < 0 ) { + if ( errno == EEXIST ) { + url.setFileName( TDEIO::RenameDlg::suggestName( baseDirectory, url.fileName() ) ); + // and try again on the next iteration + } else { + error( TDEIO::ERR_COULD_NOT_WRITE, url.path() ); + return false; + } + } + } while ( fd < 0 ); + const TQString 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( TDEIO::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... + TQCString 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 += TQDateTime::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 ); + TQFile::remove( infoPath ); + error( TDEIO::ERR_DISK_FULL, infoPath ); + return false; + } + + ::fclose( file ); + + kdDebug() << k_funcinfo << "info file created in trashId=" << trashId << " : " << fileId << endl; + return true; +} + +TQString TrashImpl::makeRelativePath( const TQString& topdir, const TQString& path ) +{ + const TQString realPath = KStandardDirs::realFilePath( path ); + // topdir ends with '/' + if ( realPath.startsWith( topdir ) ) { + const TQString 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; + } +} + +TQString TrashImpl::infoPath( int trashId, const TQString& fileId ) const +{ + TQString trashPath = trashDirectoryPath( trashId ); + trashPath += "/info/"; + trashPath += fileId; + trashPath += ".trashinfo"; + return trashPath; +} + +TQString TrashImpl::filesPath( int trashId, const TQString& fileId ) const +{ + TQString trashPath = trashDirectoryPath( trashId ); + trashPath += "/files/"; + trashPath += fileId; + return trashPath; +} + +bool TrashImpl::deleteInfo( int trashId, const TQString& fileId ) +{ + bool ok = TQFile::remove( infoPath( trashId, fileId ) ); + if ( ok ) + fileRemoved(); + return ok; +} + +bool TrashImpl::moveToTrash( const TQString& origPath, int trashId, const TQString& fileId ) +{ + kdDebug() << k_funcinfo << endl; + const TQString 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 ( TQFileInfo( dest ).isFile() ) + TQFile::remove( dest ); + else + synchronousDel( dest, false, true ); + return false; + } + fileAdded(); + return true; +} + +bool TrashImpl::moveFromTrash( const TQString& dest, int trashId, const TQString& fileId, const TQString& relativePath ) +{ + TQString src = filesPath( trashId, fileId ); + if ( !relativePath.isEmpty() ) { + src += '/'; + src += relativePath; + } + if ( !move( src, dest ) ) + return false; + return true; +} + +bool TrashImpl::move( const TQString& src, const TQString& dest ) +{ + if ( directRename( src, dest ) ) { + // This notification is done by TDEIO::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 != TDEIO::ERR_UNSUPPORTED_ACTION ) + return false; + + KURL urlSrc, urlDest; + urlSrc.setPath( src ); + urlDest.setPath( dest ); + kdDebug() << k_funcinfo << urlSrc << " -> " << urlDest << endl; + TDEIO::CopyJob* job = TDEIO::moveAs( urlSrc, urlDest, false ); +#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE + job->setInteractive( false ); +#endif + connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), + this, TQT_SLOT( jobFinished(TDEIO::Job *) ) ); + tqApp->eventLoop()->enterLoop(); + + return m_lastErrorCode == 0; +} + +void TrashImpl::jobFinished(TDEIO::Job* job) +{ + kdDebug() << k_funcinfo << " error=" << job->error() << endl; + error( job->error(), job->errorText() ); + tqApp->eventLoop()->exitLoop(); +} + +bool TrashImpl::copyToTrash( const TQString& origPath, int trashId, const TQString& fileId ) +{ + kdDebug() << k_funcinfo << endl; + const TQString dest = filesPath( trashId, fileId ); + if ( !copy( origPath, dest ) ) + return false; + fileAdded(); + return true; +} + +bool TrashImpl::copyFromTrash( const TQString& dest, int trashId, const TQString& fileId, const TQString& relativePath ) +{ + TQString src = filesPath( trashId, fileId ); + if ( !relativePath.isEmpty() ) { + src += '/'; + src += relativePath; + } + return copy( src, dest ); +} + +bool TrashImpl::copy( const TQString& src, const TQString& 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; + TDEIO::CopyJob* job = TDEIO::copyAs( urlSrc, urlDest, false ); +#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE + job->setInteractive( false ); +#endif + connect( job, TQT_SIGNAL( result( TDEIO::Job* ) ), + this, TQT_SLOT( jobFinished( TDEIO::Job* ) ) ); + tqApp->eventLoop()->enterLoop(); + + return m_lastErrorCode == 0; +} + +bool TrashImpl::directRename( const TQString& src, const TQString& dest ) +{ + kdDebug() << k_funcinfo << src << " -> " << dest << endl; + if ( ::rename( TQFile::encodeName( src ), TQFile::encodeName( dest ) ) != 0 ) { + if (errno == EXDEV) { + error( TDEIO::ERR_UNSUPPORTED_ACTION, TQString::fromLatin1("rename") ); + } else { + if (( errno == EACCES ) || (errno == EPERM)) { + error( TDEIO::ERR_ACCESS_DENIED, dest ); + } else if (errno == EROFS) { // The file is on a read-only filesystem + error( TDEIO::ERR_CANNOT_DELETE, src ); + } else { + error( TDEIO::ERR_CANNOT_RENAME, src ); + } + } + return false; + } + return true; +} + +#if 0 +bool TrashImpl::mkdir( int trashId, const TQString& fileId, int permissions ) +{ + const TQString path = filesPath( trashId, fileId ); + if ( ::mkdir( TQFile::encodeName( path ), permissions ) != 0 ) { + if ( errno == EACCES ) { + error( TDEIO::ERR_ACCESS_DENIED, path ); + return false; + } else if ( errno == ENOSPC ) { + error( TDEIO::ERR_DISK_FULL, path ); + return false; + } else { + error( TDEIO::ERR_COULD_NOT_MKDIR, path ); + return false; + } + } else { + if ( permissions != -1 ) + ::chmod( TQFile::encodeName( path ), permissions ); + } + return true; +} +#endif + +bool TrashImpl::del( int trashId, const TQString& fileId ) +{ + TQString info = infoPath(trashId, fileId); + TQString file = filesPath(trashId, fileId); + + TQCString info_c = TQFile::encodeName(info); + + KDE_struct_stat buff; + if ( KDE_lstat( info_c.data(), &buff ) == -1 ) { + if ( errno == EACCES ) + error( TDEIO::ERR_ACCESS_DENIED, file ); + else + error( TDEIO::ERR_DOES_NOT_EXIST, file ); + return false; + } + + if ( !synchronousDel( file, true, TQFileInfo(file).isDir() ) ) + return false; + + TQFile::remove( info ); + fileRemoved(); + return true; +} + +bool TrashImpl::synchronousDel( const TQString& path, bool setLastErrorCode, bool isDir ) +{ + const int oldErrorCode = m_lastErrorCode; + const TQString 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 ); + TDEIO::ChmodJob* chmodJob = TDEIO::chmod( fileItemList, 0200, 0200, TQString::null, TQString::null, true /*recursive*/, false /*showProgressInfo*/ ); + connect( chmodJob, TQT_SIGNAL( result(TDEIO::Job *) ), + this, TQT_SLOT( jobFinished(TDEIO::Job *) ) ); + tqApp->eventLoop()->enterLoop(); + } + + kdDebug() << k_funcinfo << "deleting " << url << endl; + TDEIO::DeleteJob *job = TDEIO::del( url, false, false ); + connect( job, TQT_SIGNAL( result(TDEIO::Job *) ), + this, TQT_SLOT( jobFinished(TDEIO::Job *) ) ); + tqApp->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 TQString filesPath = info.physicalPath; + if ( synchronousDel( filesPath, true, true ) ) { + TQFile::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(); + TQString infoPath = it.data(); + infoPath += "/info"; + // Code taken from kio_file + TQStrList entryNames = listDir( infoPath ); + //char path_buffer[PATH_MAX]; + //getcwd(path_buffer, PATH_MAX - 1); + //if ( chdir( infoPathEnc ) ) + // continue; + TQStrListIterator entryIt( entryNames ); + for (; entryIt.current(); ++entryIt) { + TQString fileName = TQFile::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 ".." +TQStrList TrashImpl::listDir( const TQString& physicalPath ) +{ + const TQCString physicalPathEnc = TQFile::encodeName( physicalPath ); + kdDebug() << k_funcinfo << "listing " << physicalPath << endl; + TQStrList 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 TQString& 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 TQString& infoPath, TrashedFileInfo& info, int trashId ) +{ + KSimpleConfig cfg( infoPath, true ); + if ( !cfg.hasGroup( "Trash Info" ) ) { + error( TDEIO::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 TQString topdir = topDirectoryPath( trashId ); // includes trailing slash + info.origPath.prepend( topdir ); + } + TQString line = cfg.readEntry( "DeletionDate" ); + if ( !line.isEmpty() ) { + info.deletionDate = TQT_TQDATETIME_OBJECT(TQDateTime::fromString( line, Qt::ISODate )); + } + return true; +} + +TQString TrashImpl::physicalPath( int trashId, const TQString& fileId, const TQString& relativePath ) +{ + TQString filePath = filesPath( trashId, fileId ); + if ( !relativePath.isEmpty() ) { + filePath += "/"; + filePath += relativePath; + } + return filePath; +} + +void TrashImpl::error( int e, const TQString& 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 ) { + TQString infoPath = it.data(); + infoPath += "/info"; + + DIR *dp = opendir( TQFile::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 TQString& 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( TQFile::encodeName( origPath ), &buff ) == 0 + && buff.st_dev == m_homeDevice ) + return 0; + + TQString mountPoint = TDEIO::findPathMountPoint( origPath ); + const TQString 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 TQCString 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 */ ) { + TQString topdir = (*it)->mountPoint(); + TQString 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; +} + +TQString TrashImpl::trashForMountPoint( const TQString& topdir, bool createIfNeeded ) const +{ + // (1) Administrator-created $topdir/.Trash directory + + const TQString rootTrashDir = topdir + "/.Trash"; + const TQCString rootTrashDir_c = TQFile::encodeName( rootTrashDir ); + // Can't use TQFileInfo 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 TQString trashDir = rootTrashDir + "/" + TQString::number( uid ); + const TQCString trashDir_c = TQFile::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 TQString trashDir = topdir + "/.Trash-" + TQString::number( uid ); + const TQCString trashDir_c = TQFile::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 TQString::null; + } + if ( createIfNeeded && initTrashDirectory( trashDir_c ) ) { + return trashDir; + } + return TQString::null; +} + +int TrashImpl::idForTrashDirectory( const TQString& 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 TQCString& 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 TQCString& trashDir_c ) const +{ + // testDir currently works with a TQString - ## optimize + TQString trashDir = TQFile::decodeName( trashDir_c ); + const TQString info = trashDir + "/info"; + if ( testDir( info ) != 0 ) + return false; + const TQString files = trashDir + "/files"; + if ( testDir( files ) != 0 ) + return false; + return true; +} + +TQString 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]; +} + +TQString 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 TQString& fileId, const TQString& relativePath ) +{ + KURL url; + url.setProtocol( "trash" ); + TQString path = "/"; + path += TQString::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, TQString& fileId, TQString& relativePath ) +{ + if ( url.protocol() != "trash" ) + return false; + const TQString 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 = TQString::null; + return true; + } + fileId = path.mid( start, slashPos - start ); + relativePath = path.mid( slashPos + 1 ); + return true; +} + +#include "trashimpl.moc" diff --git a/tdeioslave/trash/trashimpl.h b/tdeioslave/trash/trashimpl.h new file mode 100644 index 000000000..6aa941013 --- /dev/null +++ b/tdeioslave/trash/trashimpl.h @@ -0,0 +1,182 @@ +/* This file is part of the KDE project + Copyright (C) 2004 David Faure <[email protected]> + + 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 <tdeio/jobclasses.h> +#include <ksimpleconfig.h> + +#include <tqstring.h> +#include <tqdatetime.h> +#include <tqmap.h> +#include <tqvaluelist.h> +#include <tqstrlist.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 TQObject +{ + 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 TQString& origPath, int& trashId, TQString& 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 TQString& fileId ); + + /// Moving a file or directory into the trash. The ids come from createInfo. + bool moveToTrash( const TQString& origPath, int trashId, const TQString& fileId ); + + /// Moving a file or directory out of the trash. The ids come from createInfo. + bool moveFromTrash( const TQString& origPath, int trashId, const TQString& fileId, const TQString& relativePath ); + + /// Copying a file or directory into the trash. The ids come from createInfo. + bool copyToTrash( const TQString& origPath, int trashId, const TQString& fileId ); + + /// Copying a file or directory out of the trash. The ids come from createInfo. + bool copyFromTrash( const TQString& origPath, int trashId, const TQString& fileId, const TQString& relativePath ); + + /// Create a top-level trashed directory + //bool mkdir( int trashId, const TQString& fileId, int permissions ); + + /// Get rid of a trashed file + bool del( int trashId, const TQString& 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 + TQString fileId; // for the url + TQString physicalPath; // for stat'ing etc. + TQString origPath; // from info file + TQDateTime deletionDate; // from info file + }; + /// List trashed files + typedef TQValueList<TrashedFileInfo> TrashedFileInfoList; + TrashedFileInfoList list(); + + /// Return the info for a given trashed file + bool infoForFile( int trashId, const TQString& 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. + TQString physicalPath( int trashId, const TQString& fileId, const TQString& relativePath ); + + /// Move data from the old trash system to the new one + void migrateOldTrash(); + + /// KIO error code + int lastErrorCode() const { return m_lastErrorCode; } + TQString lastErrorMessage() const { return m_lastErrorMessage; } + + TQStrList listDir( const TQString& physicalPath ); + + static KURL makeURL( int trashId, const TQString& fileId, const TQString& relativePath ); + static bool parseURL( const KURL& url, int& trashId, TQString& fileId, TQString& relativePath ); + + typedef TQMap<int, TQString> 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 TQString& src, const TQString& dest ); + bool copy( const TQString& src, const TQString& dest ); + /// Helper method. Tries to call ::rename(src,dest) and does error handling. + bool directRename( const TQString& src, const TQString& dest ); + + void fileAdded(); + void fileRemoved(); + + // Warning, returns error code, not a bool + int testDir( const TQString& name ) const; + void error( int e, const TQString& s ); + + bool readInfoFile( const TQString& infoPath, TrashedFileInfo& info, int trashId ); + + TQString infoPath( int trashId, const TQString& fileId ) const; + TQString filesPath( int trashId, const TQString& fileId ) const; + + /// Find the trash dir to use for a given file to delete, based on original path + int findTrashDirectory( const TQString& origPath ); + + TQString trashDirectoryPath( int trashId ) const; + TQString topDirectoryPath( int trashId ) const; + + bool synchronousDel( const TQString& path, bool setLastErrorCode, bool isDir ); + + void scanTrashDirectories() const; + + int idForTrashDirectory( const TQString& trashDir ) const; + bool initTrashDirectory( const TQCString& trashDir_c ) const; + bool checkTrashSubdirs( const TQCString& trashDir_c ) const; + TQString trashForMountPoint( const TQString& topdir, bool createIfNeeded ) const; + static TQString makeRelativePath( const TQString& topdir, const TQString& path ); + +private slots: + void jobFinished(TDEIO::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; + TQString 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 tdeioslave 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 tdeioslave. +}; + +#endif |