• Skip to content
  • Skip to link menu
KDE 3.5 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kio

kurlcompletion.cpp

Go to the documentation of this file.
00001 /* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*-
00002 
00003    This file is part of the KDE libraries
00004    Copyright (C) 2000 David Smith <dsmith@algonet.se>
00005    Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
00006 
00007    This class was inspired by a previous KURLCompletion by
00008    Henner Zeller <zeller@think.de>
00009 
00010    This library is free software; you can redistribute it and/or
00011    modify it under the terms of the GNU Library General Public
00012    License as published by the Free Software Foundation; either
00013    version 2 of the License, or (at your option) any later version.
00014 
00015    This library is distributed in the hope that it will be useful,
00016    but WITHOUT ANY WARRANTY; without even the implied warranty of
00017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00018    Library General Public License for more details.
00019 
00020    You should have received a copy of the GNU Library General Public License
00021    along with this library; see the file COPYING.LIB.   If not, write to
00022    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00023    Boston, MA 02110-1301, USA.
00024 */
00025 
00026 #include <config.h>
00027 #include <stdlib.h>
00028 #include <assert.h>
00029 #include <limits.h>
00030 
00031 #include <qstring.h>
00032 #include <qstringlist.h>
00033 #include <qvaluelist.h>
00034 #include <qregexp.h>
00035 #include <qtimer.h>
00036 #include <qdir.h>
00037 #include <qfile.h>
00038 #include <qtextstream.h>
00039 #include <qdeepcopy.h>
00040 #include <qthread.h>
00041 
00042 #include <kapplication.h>
00043 #include <kdebug.h>
00044 #include <kcompletion.h>
00045 #include <kurl.h>
00046 #include <kio/jobclasses.h>
00047 #include <kio/job.h>
00048 #include <kprotocolinfo.h>
00049 #include <kconfig.h>
00050 #include <kglobal.h>
00051 #include <klocale.h>
00052 #include <kde_file.h>
00053 
00054 #include <sys/types.h>
00055 #include <dirent.h>
00056 #include <unistd.h>
00057 #include <sys/stat.h>
00058 #include <pwd.h>
00059 #include <time.h>
00060 #include <sys/param.h>
00061 
00062 #include "kurlcompletion.h"
00063 
00064 static bool expandTilde(QString &);
00065 static bool expandEnv(QString &);
00066 
00067 static QString unescape(const QString &text);
00068 
00069 // Permission mask for files that are executable by
00070 // user, group or other
00071 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
00072 
00073 // Constants for types of completion
00074 enum ComplType {CTNone=0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
00075 
00076 class CompletionThread;
00077 
00083 class CompletionMatchEvent : public QCustomEvent
00084 {
00085 public:
00086     CompletionMatchEvent( CompletionThread *thread ) :
00087         QCustomEvent( uniqueType() ),
00088         m_completionThread( thread )
00089     {}
00090 
00091     CompletionThread *completionThread() const { return m_completionThread; }
00092     static int uniqueType() { return User + 61080; }
00093 
00094 private:
00095     CompletionThread *m_completionThread;
00096 };
00097 
00098 class CompletionThread : public QThread
00099 {
00100 protected:
00101     CompletionThread( KURLCompletion *receiver ) :
00102         QThread(),
00103         m_receiver( receiver ),
00104         m_terminationRequested( false )
00105     {}
00106 
00107 public:
00108     void requestTermination() { m_terminationRequested = true; }
00109     QDeepCopy<QStringList> matches() const { return m_matches; }
00110 
00111 protected:
00112     void addMatch( const QString &match ) { m_matches.append( match ); }
00113     bool terminationRequested() const { return m_terminationRequested; }
00114     void done()
00115     {
00116         if ( !m_terminationRequested )
00117             kapp->postEvent( m_receiver, new CompletionMatchEvent( this ) );
00118         else
00119             delete this;
00120     }
00121 
00122 private:
00123     KURLCompletion *m_receiver;
00124     QStringList m_matches;
00125     bool m_terminationRequested;
00126 };
00127 
00133 class UserListThread : public CompletionThread
00134 {
00135 public:
00136     UserListThread( KURLCompletion *receiver ) :
00137         CompletionThread( receiver )
00138     {}
00139 
00140 protected:
00141     virtual void run()
00142     {
00143         static const QChar tilde = '~';
00144 
00145         struct passwd *pw;
00146         while ( ( pw = ::getpwent() ) && !terminationRequested() )
00147             addMatch( tilde + QString::fromLocal8Bit( pw->pw_name ) );
00148 
00149         ::endpwent();
00150 
00151         addMatch( tilde );
00152 
00153         done();
00154     }
00155 };
00156 
00157 class DirectoryListThread : public CompletionThread
00158 {
00159 public:
00160     DirectoryListThread( KURLCompletion *receiver,
00161                          const QStringList &dirList,
00162                          const QString &filter,
00163                          bool onlyExe,
00164                          bool onlyDir,
00165                          bool noHidden,
00166                          bool appendSlashToDir ) :
00167         CompletionThread( receiver ),
00168         m_dirList( QDeepCopy<QStringList>( dirList ) ),
00169         m_filter( QDeepCopy<QString>( filter ) ),
00170         m_onlyExe( onlyExe ),
00171         m_onlyDir( onlyDir ),
00172         m_noHidden( noHidden ),
00173         m_appendSlashToDir( appendSlashToDir )
00174     {}
00175 
00176     virtual void run();
00177 
00178 private:
00179     QStringList m_dirList;
00180     QString m_filter;
00181     bool m_onlyExe;
00182     bool m_onlyDir;
00183     bool m_noHidden;
00184     bool m_appendSlashToDir;
00185 };
00186 
00187 void DirectoryListThread::run()
00188 {
00189     // Thread safety notes:
00190     //
00191     // There very possibly may be thread safety issues here, but I've done a check
00192     // of all of the things that would seem to be problematic.  Here are a few
00193     // things that I have checked to be safe here (some used indirectly):
00194     //
00195     // QDir::currentDirPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName()
00196     // QString::fromLocal8Bit(), QString::local8Bit(), QTextCodec::codecForLocale()
00197     //
00198     // Also see (for POSIX functions):
00199     // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
00200 
00201     DIR *dir = 0;
00202 
00203     for ( QStringList::ConstIterator it = m_dirList.begin();
00204           it != m_dirList.end() && !terminationRequested();
00205           ++it )
00206     {
00207         // Open the next directory
00208 
00209         if ( !dir ) {
00210             dir = ::opendir( QFile::encodeName( *it ) );
00211             if ( ! dir ) {
00212                 kdDebug() << "Failed to open dir: " << *it << endl;
00213                 done();
00214                 return;
00215             }
00216         }
00217 
00218         // A trick from KIO that helps performance by a little bit:
00219         // chdir to the directroy so we won't have to deal with full paths
00220         // with stat()
00221 
00222         QString path = QDir::currentDirPath();
00223         QDir::setCurrent( *it );
00224 
00225         // Loop through all directory entries
00226         // Solaris and IRIX dirent structures do not allocate space for d_name. On
00227         // systems that do (HP-UX, Linux, Tru64 UNIX), we overallocate space but
00228         // that's ok.
00229 #ifndef HAVE_READDIR_R
00230         struct dirent *dirEntry = 0;
00231         while ( !terminationRequested() &&
00232                 (dirEntry = ::readdir( dir)))
00233 #else
00234         struct dirent *dirPosition = (struct dirent *) malloc( sizeof( struct dirent ) + MAXPATHLEN + 1 );
00235         struct dirent *dirEntry = 0;
00236         while ( !terminationRequested() &&
00237                 ::readdir_r( dir, dirPosition, &dirEntry ) == 0 && dirEntry )
00238 #endif 
00239 
00240         {
00241             // Skip hidden files if m_noHidden is true
00242 
00243             if ( dirEntry->d_name[0] == '.' && m_noHidden )
00244                 continue;
00245 
00246             // Skip "."
00247 
00248             if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '\0' )
00249                 continue;
00250 
00251             // Skip ".."
00252 
00253             if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0' )
00254                 continue;
00255 
00256             QString file = QFile::decodeName( dirEntry->d_name );
00257 
00258             if ( m_filter.isEmpty() || file.startsWith( m_filter ) ) {
00259 
00260                 if ( m_onlyExe || m_onlyDir || m_appendSlashToDir ) {
00261                     KDE_struct_stat sbuff;
00262 
00263                     if ( KDE_stat( dirEntry->d_name, &sbuff ) == 0 ) {
00264 
00265                         // Verify executable
00266 
00267                         if ( m_onlyExe && ( sbuff.st_mode & MODE_EXE ) == 0 )
00268                             continue;
00269 
00270                         // Verify directory
00271 
00272                         if ( m_onlyDir && !S_ISDIR( sbuff.st_mode ) )
00273                             continue;
00274 
00275                         // Add '/' to directories
00276 
00277                         if ( m_appendSlashToDir && S_ISDIR( sbuff.st_mode ) )
00278                             file.append( '/' );
00279 
00280                     }
00281                     else {
00282                         kdDebug() << "Could not stat file " << file << endl;
00283                         continue;
00284                     }
00285                 }
00286 
00287                 addMatch( file );
00288             }
00289         }
00290 
00291         // chdir to the original directory
00292 
00293         QDir::setCurrent( path );
00294 
00295         ::closedir( dir );
00296         dir = 0;
00297 #ifdef HAVE_READDIR_R
00298         free( dirPosition );
00299 #endif
00300     }
00301 
00302     done();
00303 }
00304 
00307 // MyURL - wrapper for KURL with some different functionality
00308 //
00309 
00310 class KURLCompletion::MyURL
00311 {
00312 public:
00313     MyURL(const QString &url, const QString &cwd);
00314     MyURL(const MyURL &url);
00315     ~MyURL();
00316 
00317     KURL *kurl() const { return m_kurl; }
00318 
00319     QString protocol() const { return m_kurl->protocol(); }
00320     // The directory with a trailing '/'
00321     QString dir() const { return m_kurl->directory(false, false); }
00322     QString file() const { return m_kurl->fileName(false); }
00323 
00324     // The initial, unparsed, url, as a string.
00325     QString url() const { return m_url; }
00326 
00327     // Is the initial string a URL, or just a path (whether absolute or relative)
00328     bool isURL() const { return m_isURL; }
00329 
00330     void filter( bool replace_user_dir, bool replace_env );
00331 
00332 private:
00333     void init(const QString &url, const QString &cwd);
00334 
00335     KURL *m_kurl;
00336     QString m_url;
00337     bool m_isURL;
00338 };
00339 
00340 KURLCompletion::MyURL::MyURL(const QString &url, const QString &cwd)
00341 {
00342     init(url, cwd);
00343 }
00344 
00345 KURLCompletion::MyURL::MyURL(const MyURL &url)
00346 {
00347     m_kurl = new KURL( *(url.m_kurl) );
00348     m_url = url.m_url;
00349     m_isURL = url.m_isURL;
00350 }
00351 
00352 void KURLCompletion::MyURL::init(const QString &url, const QString &cwd)
00353 {
00354     // Save the original text
00355     m_url = url;
00356 
00357     // Non-const copy
00358     QString url_copy = url;
00359 
00360     // Special shortcuts for "man:" and "info:"
00361     if ( url_copy[0] == '#' ) {
00362         if ( url_copy[1] == '#' )
00363             url_copy.replace( 0, 2, QString("info:") );
00364         else
00365             url_copy.replace( 0, 1, QString("man:") );
00366     }
00367 
00368     // Look for a protocol in 'url'
00369     QRegExp protocol_regex = QRegExp( "^[^/\\s\\\\]*:" );
00370 
00371     // Assume "file:" or whatever is given by 'cwd' if there is
00372     // no protocol.  (KURL does this only for absoute paths)
00373     if ( protocol_regex.search( url_copy ) == 0 )
00374     {
00375         m_kurl = new KURL( url_copy );
00376         m_isURL = true;
00377     }
00378     else // relative path or ~ or $something
00379     {
00380         m_isURL = false;
00381         if ( cwd.isEmpty() )
00382         {
00383             m_kurl = new KURL();
00384             if ( !QDir::isRelativePath(url_copy) || url_copy[0] == '$' || url_copy[0] == '~' )
00385                 m_kurl->setPath( url_copy );
00386             else
00387                 *m_kurl = url_copy;
00388         }
00389         else
00390         {
00391             KURL base = KURL::fromPathOrURL( cwd );
00392             base.adjustPath(+1);
00393 
00394             if ( !QDir::isRelativePath(url_copy) || url_copy[0] == '~' || url_copy[0] == '$' )
00395             {
00396                 m_kurl = new KURL();
00397                 m_kurl->setPath( url_copy );
00398             }
00399             else // relative path
00400             {
00401                 //m_kurl = new KURL( base, url_copy );
00402                 m_kurl = new KURL( base );
00403                 m_kurl->addPath( url_copy );
00404             }
00405         }
00406     }
00407 }
00408 
00409 KURLCompletion::MyURL::~MyURL()
00410 {
00411     delete m_kurl;
00412 }
00413 
00414 void KURLCompletion::MyURL::filter( bool replace_user_dir, bool replace_env )
00415 {
00416     QString d = dir() + file();
00417     if ( replace_user_dir ) expandTilde( d );
00418     if ( replace_env ) expandEnv( d );
00419     m_kurl->setPath( d );
00420 }
00421 
00424 // KURLCompletionPrivate
00425 //
00426 class KURLCompletionPrivate
00427 {
00428 public:
00429     KURLCompletionPrivate() : url_auto_completion(true),
00430                               userListThread(0),
00431                               dirListThread(0) {}
00432     ~KURLCompletionPrivate();
00433 
00434     QValueList<KURL*> list_urls;
00435 
00436     bool onlyLocalProto;
00437 
00438     // urlCompletion() in Auto/Popup mode?
00439     bool url_auto_completion;
00440 
00441     // Append '/' to directories in Popup mode?
00442     // Doing that stat's all files and is slower
00443     bool popup_append_slash;
00444 
00445     // Keep track of currently listed files to avoid reading them again
00446     QString last_path_listed;
00447     QString last_file_listed;
00448     QString last_prepend;
00449     int last_compl_type;
00450     int last_no_hidden;
00451 
00452     QString cwd; // "current directory" = base dir for completion
00453 
00454     KURLCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion
00455     bool replace_env;
00456     bool replace_home;
00457     bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
00458 
00459     KIO::ListJob *list_job; // kio job to list directories
00460 
00461     QString prepend; // text to prepend to listed items
00462     QString compl_text; // text to pass on to KCompletion
00463 
00464     // Filters for files read with  kio
00465     bool list_urls_only_exe; // true = only list executables
00466     bool list_urls_no_hidden;
00467     QString list_urls_filter; // filter for listed files
00468 
00469     CompletionThread *userListThread;
00470     CompletionThread *dirListThread;
00471 };
00472 
00473 KURLCompletionPrivate::~KURLCompletionPrivate()
00474 {
00475     if ( userListThread )
00476         userListThread->requestTermination();
00477     if ( dirListThread )
00478         dirListThread->requestTermination();
00479 }
00480 
00483 // KURLCompletion
00484 //
00485 
00486 KURLCompletion::KURLCompletion() : KCompletion()
00487 {
00488     init();
00489 }
00490 
00491 
00492 KURLCompletion::KURLCompletion( Mode mode ) : KCompletion()
00493 {
00494     init();
00495     setMode ( mode );
00496 }
00497 
00498 KURLCompletion::~KURLCompletion()
00499 {
00500     stop();
00501     delete d;
00502 }
00503 
00504 
00505 void KURLCompletion::init()
00506 {
00507     d = new KURLCompletionPrivate;
00508 
00509     d->cwd = QDir::homeDirPath();
00510 
00511     d->replace_home = true;
00512     d->replace_env = true;
00513     d->last_no_hidden = false;
00514     d->last_compl_type = 0;
00515     d->list_job = 0L;
00516     d->mode = KURLCompletion::FileCompletion;
00517 
00518     // Read settings
00519     KConfig *c = KGlobal::config();
00520     KConfigGroupSaver cgs( c, "URLCompletion" );
00521 
00522     d->url_auto_completion = c->readBoolEntry("alwaysAutoComplete", true);
00523     d->popup_append_slash = c->readBoolEntry("popupAppendSlash", true);
00524     d->onlyLocalProto = c->readBoolEntry("LocalProtocolsOnly", false);
00525 }
00526 
00527 void KURLCompletion::setDir(const QString &dir)
00528 {
00529     d->cwd = dir;
00530 }
00531 
00532 QString KURLCompletion::dir() const
00533 {
00534     return d->cwd;
00535 }
00536 
00537 KURLCompletion::Mode KURLCompletion::mode() const
00538 {
00539     return d->mode;
00540 }
00541 
00542 void KURLCompletion::setMode( Mode mode )
00543 {
00544     d->mode = mode;
00545 }
00546 
00547 bool KURLCompletion::replaceEnv() const
00548 {
00549     return d->replace_env;
00550 }
00551 
00552 void KURLCompletion::setReplaceEnv( bool replace )
00553 {
00554     d->replace_env = replace;
00555 }
00556 
00557 bool KURLCompletion::replaceHome() const
00558 {
00559     return d->replace_home;
00560 }
00561 
00562 void KURLCompletion::setReplaceHome( bool replace )
00563 {
00564     d->replace_home = replace;
00565 }
00566 
00567 /*
00568  * makeCompletion()
00569  *
00570  * Entry point for file name completion
00571  */
00572 QString KURLCompletion::makeCompletion(const QString &text)
00573 {
00574     //kdDebug() << "KURLCompletion::makeCompletion: " << text << " d->cwd=" << d->cwd << endl;
00575 
00576     MyURL url(text, d->cwd);
00577 
00578     d->compl_text = text;
00579 
00580     // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
00581     // This is what gets prepended to the directory-listing matches.
00582     int toRemove = url.file().length() - url.kurl()->query().length();
00583     if ( url.kurl()->hasRef() )
00584         toRemove += url.kurl()->ref().length() + 1;
00585     d->prepend = text.left( text.length() - toRemove );
00586     d->complete_url = url.isURL();
00587 
00588     QString match;
00589 
00590     // Environment variables
00591     //
00592     if ( d->replace_env && envCompletion( url, &match ) )
00593         return match;
00594 
00595     // User directories
00596     //
00597     if ( d->replace_home && userCompletion( url, &match ) )
00598         return match;
00599 
00600     // Replace user directories and variables
00601     url.filter( d->replace_home, d->replace_env );
00602 
00603     //kdDebug() << "Filtered: proto=" << url.protocol()
00604     //          << ", dir=" << url.dir()
00605     //          << ", file=" << url.file()
00606     //          << ", kurl url=" << *url.kurl() << endl;
00607 
00608     if ( d->mode == ExeCompletion ) {
00609         // Executables
00610         //
00611         if ( exeCompletion( url, &match ) )
00612             return match;
00613 
00614         // KRun can run "man:" and "info:" etc. so why not treat them
00615         // as executables...
00616 
00617         if ( urlCompletion( url, &match ) )
00618             return match;
00619     }
00620     else {
00621         // Local files, directories
00622         //
00623         if ( fileCompletion( url, &match ) )
00624             return match;
00625 
00626         // All other...
00627         //
00628         if ( urlCompletion( url, &match ) )
00629             return match;
00630     }
00631 
00632     setListedURL( CTNone );
00633     stop();
00634 
00635     return QString::null;
00636 }
00637 
00638 /*
00639  * finished
00640  *
00641  * Go on and call KCompletion.
00642  * Called when all matches have been added
00643  */
00644 QString KURLCompletion::finished()
00645 {
00646     if ( d->last_compl_type == CTInfo )
00647         return KCompletion::makeCompletion( d->compl_text.lower() );
00648     else
00649         return KCompletion::makeCompletion( d->compl_text );
00650 }
00651 
00652 /*
00653  * isRunning
00654  *
00655  * Return true if either a KIO job or the DirLister
00656  * is running
00657  */
00658 bool KURLCompletion::isRunning() const
00659 {
00660     return d->list_job || (d->dirListThread && !d->dirListThread->finished());
00661 }
00662 
00663 /*
00664  * stop
00665  *
00666  * Stop and delete a running KIO job or the DirLister
00667  */
00668 void KURLCompletion::stop()
00669 {
00670     if ( d->list_job ) {
00671         d->list_job->kill();
00672         d->list_job = 0L;
00673     }
00674 
00675     if ( !d->list_urls.isEmpty() ) {
00676         QValueList<KURL*>::Iterator it = d->list_urls.begin();
00677         for ( ; it != d->list_urls.end(); it++ )
00678             delete (*it);
00679         d->list_urls.clear();
00680     }
00681 
00682     if ( d->dirListThread ) {
00683         d->dirListThread->requestTermination();
00684         d->dirListThread = 0;
00685     }
00686 }
00687 
00688 /*
00689  * Keep track of the last listed directory
00690  */
00691 void KURLCompletion::setListedURL( int complType,
00692                                    const QString& dir,
00693                                    const QString& filter,
00694                                    bool no_hidden )
00695 {
00696     d->last_compl_type = complType;
00697     d->last_path_listed = dir;
00698     d->last_file_listed = filter;
00699     d->last_no_hidden = (int)no_hidden;
00700     d->last_prepend = d->prepend;
00701 }
00702 
00703 bool KURLCompletion::isListedURL( int complType,
00704                                   const QString& dir,
00705                                   const QString& filter,
00706                                   bool no_hidden )
00707 {
00708     return  d->last_compl_type == complType
00709             && ( d->last_path_listed == dir
00710                     || (dir.isEmpty() && d->last_path_listed.isEmpty()) )
00711             && ( filter.startsWith(d->last_file_listed)
00712                     || (filter.isEmpty() && d->last_file_listed.isEmpty()) )
00713             && d->last_no_hidden == (int)no_hidden
00714             && d->last_prepend == d->prepend; // e.g. relative path vs absolute
00715 }
00716 
00717 /*
00718  * isAutoCompletion
00719  *
00720  * Returns true if completion mode is Auto or Popup
00721  */
00722 bool KURLCompletion::isAutoCompletion()
00723 {
00724     return completionMode() == KGlobalSettings::CompletionAuto
00725            || completionMode() == KGlobalSettings::CompletionPopup
00726            || completionMode() == KGlobalSettings::CompletionMan
00727            || completionMode() == KGlobalSettings::CompletionPopupAuto;
00728 }
00731 // User directories
00732 //
00733 
00734 bool KURLCompletion::userCompletion(const MyURL &url, QString *match)
00735 {
00736     if ( url.protocol() != "file"
00737           || !url.dir().isEmpty()
00738           || url.file().at(0) != '~' )
00739         return false;
00740 
00741     if ( !isListedURL( CTUser ) ) {
00742         stop();
00743         clear();
00744 
00745         if ( !d->userListThread ) {
00746             d->userListThread = new UserListThread( this );
00747             d->userListThread->start();
00748 
00749             // If the thread finishes quickly make sure that the results
00750             // are added to the first matching case.
00751 
00752             d->userListThread->wait( 200 );
00753             QStringList l = d->userListThread->matches();
00754             addMatches( l );
00755         }
00756     }
00757     *match = finished();
00758     return true;
00759 }
00760 
00763 // Environment variables
00764 //
00765 
00766 extern char **environ; // Array of environment variables
00767 
00768 bool KURLCompletion::envCompletion(const MyURL &url, QString *match)
00769 {
00770     if ( url.file().at(0) != '$' )
00771         return false;
00772 
00773     if ( !isListedURL( CTEnv ) ) {
00774         stop();
00775         clear();
00776 
00777         char **env = environ;
00778 
00779         QString dollar = QString("$");
00780 
00781         QStringList l;
00782 
00783         while ( *env ) {
00784             QString s = QString::fromLocal8Bit( *env );
00785 
00786             int pos = s.find('=');
00787 
00788             if ( pos == -1 )
00789                 pos = s.length();
00790 
00791             if ( pos > 0 )
00792                 l.append( dollar + s.left(pos) );
00793 
00794             env++;
00795         }
00796 
00797         addMatches( l );
00798     }
00799 
00800     setListedURL( CTEnv );
00801 
00802     *match = finished();
00803     return true;
00804 }
00805 
00808 // Executables
00809 //
00810 
00811 bool KURLCompletion::exeCompletion(const MyURL &url, QString *match)
00812 {
00813     if ( url.protocol() != "file" )
00814         return false;
00815 
00816     QString dir = url.dir();
00817 
00818     dir = unescape( dir ); // remove escapes
00819 
00820     // Find directories to search for completions, either
00821     //
00822     // 1. complete path given in url
00823     // 2. current directory (d->cwd)
00824     // 3. $PATH
00825     // 4. no directory at all
00826 
00827     QStringList dirList;
00828 
00829     if ( !QDir::isRelativePath(dir) ) {
00830         // complete path in url
00831         dirList.append( dir );
00832     }
00833     else if ( !dir.isEmpty() && !d->cwd.isEmpty() ) {
00834         // current directory
00835         dirList.append( d->cwd + '/' + dir );
00836     }
00837     else if ( !url.file().isEmpty() ) {
00838         // $PATH
00839         dirList = QStringList::split(KPATH_SEPARATOR,
00840                     QString::fromLocal8Bit(::getenv("PATH")));
00841 
00842         QStringList::Iterator it = dirList.begin();
00843 
00844         for ( ; it != dirList.end(); it++ )
00845             (*it).append('/');
00846     }
00847 
00848     // No hidden files unless the user types "."
00849     bool no_hidden_files = url.file().at(0) != '.';
00850 
00851     // List files if needed
00852     //
00853     if ( !isListedURL( CTExe, dir, url.file(), no_hidden_files ) )
00854     {
00855         stop();
00856         clear();
00857 
00858         setListedURL( CTExe, dir, url.file(), no_hidden_files );
00859 
00860         *match = listDirectories( dirList, url.file(), true, false, no_hidden_files );
00861     }
00862     else if ( !isRunning() ) {
00863         *match = finished();
00864     }
00865     else {
00866         if ( d->dirListThread )
00867             setListedURL( CTExe, dir, url.file(), no_hidden_files );
00868         *match = QString::null;
00869     }
00870 
00871     return true;
00872 }
00873 
00876 // Local files
00877 //
00878 
00879 bool KURLCompletion::fileCompletion(const MyURL &url, QString *match)
00880 {
00881     if ( url.protocol() != "file" )
00882         return false;
00883 
00884     QString dir = url.dir();
00885 
00886     if (url.url()[0] == '.')
00887     {
00888         if (url.url().length() == 1)
00889         {
00890             *match =
00891                 ( completionMode() == KGlobalSettings::CompletionMan )? "." : "..";
00892             return true;
00893         }
00894         if (url.url().length() == 2 && url.url()[1]=='.')
00895         {
00896             *match="..";
00897             return true;
00898         }
00899     }
00900 
00901     //kdDebug() << "fileCompletion " << url.url() << " dir=" << dir << endl;
00902 
00903     dir = unescape( dir ); // remove escapes
00904 
00905     // Find directories to search for completions, either
00906     //
00907     // 1. complete path given in url
00908     // 2. current directory (d->cwd)
00909     // 3. no directory at all
00910 
00911     QStringList dirList;
00912 
00913     if ( !QDir::isRelativePath(dir) ) {
00914         // complete path in url
00915         dirList.append( dir );
00916     }
00917     else if ( !d->cwd.isEmpty() ) {
00918         // current directory
00919         dirList.append( d->cwd + '/' + dir );
00920     }
00921 
00922     // No hidden files unless the user types "."
00923     bool no_hidden_files = ( url.file().at(0) != '.' );
00924 
00925     // List files if needed
00926     //
00927     if ( !isListedURL( CTFile, dir, "", no_hidden_files ) )
00928     {
00929         stop();
00930         clear();
00931 
00932         setListedURL( CTFile, dir, "", no_hidden_files );
00933 
00934         // Append '/' to directories in Popup mode?
00935         bool append_slash = ( d->popup_append_slash
00936             && (completionMode() == KGlobalSettings::CompletionPopup ||
00937             completionMode() == KGlobalSettings::CompletionPopupAuto ) );
00938 
00939         bool only_dir = ( d->mode == DirCompletion );
00940 
00941         *match = listDirectories( dirList, "", false, only_dir, no_hidden_files,
00942                                   append_slash );
00943     }
00944     else if ( !isRunning() ) {
00945         *match = finished();
00946     }
00947     else {
00948         *match = QString::null;
00949     }
00950 
00951     return true;
00952 }
00953 
00956 // URLs not handled elsewhere...
00957 //
00958 
00959 bool KURLCompletion::urlCompletion(const MyURL &url, QString *match)
00960 {
00961     //kdDebug() << "urlCompletion: url = " << *url.kurl() << endl;
00962     if (d->onlyLocalProto && KProtocolInfo::protocolClass(url.protocol()) != ":local")
00963         return false;
00964 
00965     // Use d->cwd as base url in case url is not absolute
00966     KURL url_cwd = KURL::fromPathOrURL( d->cwd );
00967 
00968     // Create an URL with the directory to be listed
00969     KURL url_dir( url_cwd, url.kurl()->url() );
00970 
00971     // Don't try url completion if
00972     // 1. malformed url
00973     // 2. protocol that doesn't have listDir()
00974     // 3. there is no directory (e.g. "ftp://ftp.kd" shouldn't do anything)
00975     // 4. auto or popup completion mode depending on settings
00976 
00977     bool man_or_info = ( url_dir.protocol() == QString("man")
00978                          || url_dir.protocol() == QString("info") );
00979 
00980     if ( !url_dir.isValid()
00981          || !KProtocolInfo::supportsListing( url_dir )
00982          || ( !man_or_info
00983               && ( url_dir.directory(false,false).isEmpty()
00984                    || ( isAutoCompletion()
00985                         && !d->url_auto_completion ) ) ) ) {
00986         return false;
00987         }
00988 
00989     url_dir.setFileName(""); // not really nesseccary, but clear the filename anyway...
00990 
00991     // Remove escapes
00992     QString dir = url_dir.directory( false, false );
00993 
00994     dir = unescape( dir );
00995 
00996     url_dir.setPath( dir );
00997 
00998     // List files if needed
00999     //
01000     if ( !isListedURL( CTUrl, url_dir.prettyURL(), url.file() ) )
01001     {
01002         stop();
01003         clear();
01004 
01005         setListedURL( CTUrl, url_dir.prettyURL(), "" );
01006 
01007         QValueList<KURL*> url_list;
01008         url_list.append( new KURL( url_dir ) );
01009 
01010         listURLs( url_list, "", false );
01011 
01012         *match = QString::null;
01013     }
01014     else if ( !isRunning() ) {
01015         *match = finished();
01016     }
01017     else {
01018         *match = QString::null;
01019     }
01020 
01021     return true;
01022 }
01023 
01026 // Directory and URL listing
01027 //
01028 
01029 /*
01030  * addMatches
01031  *
01032  * Called to add matches to KCompletion
01033  */
01034 void KURLCompletion::addMatches( const QStringList &matches )
01035 {
01036     QStringList::ConstIterator it = matches.begin();
01037     QStringList::ConstIterator end = matches.end();
01038 
01039     if ( d->complete_url )
01040         for ( ; it != end; it++ )
01041             addItem( d->prepend + KURL::encode_string(*it));
01042     else
01043         for ( ; it != end; it++ )
01044             addItem( d->prepend + (*it));
01045 }
01046 
01047 /*
01048  * listDirectories
01049  *
01050  * List files starting with 'filter' in the given directories,
01051  * either using DirLister or listURLs()
01052  *
01053  * In either case, addMatches() is called with the listed
01054  * files, and eventually finished() when the listing is done
01055  *
01056  * Returns the match if available, or QString::null if
01057  * DirLister timed out or using kio
01058  */
01059 QString KURLCompletion::listDirectories(
01060         const QStringList &dirList,
01061         const QString &filter,
01062         bool only_exe,
01063         bool only_dir,
01064         bool no_hidden,
01065         bool append_slash_to_dir)
01066 {
01067     assert( !isRunning() );
01068 
01069     if ( !::getenv("KURLCOMPLETION_LOCAL_KIO") ) {
01070 
01071         //kdDebug() << "Listing (listDirectories): " << dirList << " filter=" << filter << " without KIO" << endl;
01072 
01073         // Don't use KIO
01074 
01075         if ( d->dirListThread )
01076             d->dirListThread->requestTermination();
01077 
01078         QStringList dirs;
01079 
01080         for ( QStringList::ConstIterator it = dirList.begin();
01081               it != dirList.end();
01082               ++it )
01083         {
01084             KURL url;
01085             url.setPath(*it);
01086             if ( kapp->authorizeURLAction( "list", KURL(), url ) )
01087                 dirs.append( *it );
01088         }
01089 
01090         d->dirListThread = new DirectoryListThread( this, dirs, filter, only_exe, only_dir,
01091                                                     no_hidden, append_slash_to_dir );
01092         d->dirListThread->start();
01093         d->dirListThread->wait( 200 );
01094         addMatches( d->dirListThread->matches() );
01095 
01096         return finished();
01097     }
01098     else {
01099 
01100         // Use KIO
01101         //kdDebug() << "Listing (listDirectories): " << dirList << " with KIO" << endl;
01102 
01103         QValueList<KURL*> url_list;
01104 
01105         QStringList::ConstIterator it = dirList.begin();
01106 
01107         for ( ; it != dirList.end(); it++ )
01108             url_list.append( new KURL(*it) );
01109 
01110         listURLs( url_list, filter, only_exe, no_hidden );
01111         // Will call addMatches() and finished()
01112 
01113         return QString::null;
01114     }
01115 }
01116 
01117 /*
01118  * listURLs
01119  *
01120  * Use KIO to list the given urls
01121  *
01122  * addMatches() is called with the listed files
01123  * finished() is called when the listing is done
01124  */
01125 void KURLCompletion::listURLs(
01126         const QValueList<KURL *> &urls,
01127         const QString &filter,
01128         bool only_exe,
01129         bool no_hidden )
01130 {
01131     assert( d->list_urls.isEmpty() );
01132     assert( d->list_job == 0L );
01133 
01134     d->list_urls = urls;
01135     d->list_urls_filter = filter;
01136     d->list_urls_only_exe = only_exe;
01137     d->list_urls_no_hidden = no_hidden;
01138 
01139 //  kdDebug() << "Listing URLs: " << urls[0]->prettyURL() << ",..." << endl;
01140 
01141     // Start it off by calling slotIOFinished
01142     //
01143     // This will start a new list job as long as there
01144     // are urls in d->list_urls
01145     //
01146     slotIOFinished(0L);
01147 }
01148 
01149 /*
01150  * slotEntries
01151  *
01152  * Receive files listed by KIO and call addMatches()
01153  */
01154 void KURLCompletion::slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
01155 {
01156     QStringList matches;
01157 
01158     KIO::UDSEntryListConstIterator it = entries.begin();
01159     KIO::UDSEntryListConstIterator end = entries.end();
01160 
01161     QString filter = d->list_urls_filter;
01162 
01163     int filter_len = filter.length();
01164 
01165     // Iterate over all files
01166     //
01167     for (; it != end; ++it) {
01168         QString name;
01169         QString url;
01170         bool is_exe = false;
01171         bool is_dir = false;
01172 
01173         KIO::UDSEntry e = *it;
01174         KIO::UDSEntry::ConstIterator it_2 = e.begin();
01175 
01176         for( ; it_2 != e.end(); it_2++ ) {
01177             switch ( (*it_2).m_uds ) {
01178                 case KIO::UDS_NAME:
01179                     name = (*it_2).m_str;
01180                     break;
01181                 case KIO::UDS_ACCESS:
01182                     is_exe = ((*it_2).m_long & MODE_EXE) != 0;
01183                     break;
01184                 case KIO::UDS_FILE_TYPE:
01185                     is_dir = ((*it_2).m_long & S_IFDIR) != 0;
01186                     break;
01187                 case KIO::UDS_URL:
01188                     url = (*it_2).m_str;
01189                     break;
01190             }
01191         }
01192 
01193         if (!url.isEmpty()) {
01194             // kdDebug() << "KURLCompletion::slotEntries url: " << url << endl;
01195             name = KURL(url).fileName();
01196         }
01197 
01198         // kdDebug() << "KURLCompletion::slotEntries name: " << name << endl;
01199 
01200         if ( name[0] == '.' &&
01201              ( d->list_urls_no_hidden ||
01202                 name.length() == 1 ||
01203                   ( name.length() == 2 && name[1] == '.' ) ) )
01204             continue;
01205 
01206         if ( d->mode == DirCompletion && !is_dir )
01207             continue;
01208 
01209         if ( filter_len == 0 || name.left(filter_len) == filter ) {
01210             if ( is_dir )
01211                 name.append( '/' );
01212 
01213             if ( is_exe || !d->list_urls_only_exe )
01214                 matches.append( name );
01215         }
01216     }
01217 
01218     addMatches( matches );
01219 }
01220 
01221 /*
01222  * slotIOFinished
01223  *
01224  * Called when a KIO job is finished.
01225  *
01226  * Start a new list job if there are still urls in
01227  * d->list_urls, otherwise call finished()
01228  */
01229 void KURLCompletion::slotIOFinished( KIO::Job * job )
01230 {
01231 //  kdDebug() << "slotIOFinished() " << endl;
01232 
01233     assert( job == d->list_job );
01234 
01235     if ( d->list_urls.isEmpty() ) {
01236 
01237         d->list_job = 0L;
01238 
01239         finished(); // will call KCompletion::makeCompletion()
01240 
01241     }
01242     else {
01243 
01244         KURL *kurl = d->list_urls.first();
01245 
01246         d->list_urls.remove( kurl );
01247 
01248 //      kdDebug() << "Start KIO: " << kurl->prettyURL() << endl;
01249 
01250         d->list_job = KIO::listDir( *kurl, false );
01251         d->list_job->addMetaData("no-auth-prompt", "true");
01252 
01253         assert( d->list_job );
01254 
01255         connect( d->list_job,
01256                 SIGNAL(result(KIO::Job*)),
01257                 SLOT(slotIOFinished(KIO::Job*)) );
01258 
01259         connect( d->list_job,
01260                 SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList&)),
01261                 SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList&)) );
01262 
01263         delete kurl;
01264     }
01265 }
01266 
01269 
01270 /*
01271  * postProcessMatch, postProcessMatches
01272  *
01273  * Called by KCompletion before emitting match() and matches()
01274  *
01275  * Append '/' to directories for file completion. This is
01276  * done here to avoid stat()'ing a lot of files
01277  */
01278 void KURLCompletion::postProcessMatch( QString *match ) const
01279 {
01280 //  kdDebug() << "KURLCompletion::postProcess: " << *match << endl;
01281 
01282     if ( !match->isEmpty() ) {
01283 
01284         // Add '/' to directories in file completion mode
01285         // unless it has already been done
01286         if ( d->last_compl_type == CTFile )
01287             adjustMatch( *match );
01288     }
01289 }
01290 
01291 void KURLCompletion::adjustMatch( QString& match ) const
01292 {
01293     if ( match.at( match.length()-1 ) != '/' )
01294     {
01295         QString copy;
01296 
01297         if ( match.startsWith( QString("file:") ) )
01298             copy = KURL(match).path();
01299         else
01300             copy = match;
01301 
01302         expandTilde( copy );
01303         expandEnv( copy );
01304         if ( QDir::isRelativePath(copy) )
01305             copy.prepend( d->cwd + '/' );
01306 
01307 //      kdDebug() << "postProcess: stating " << copy << endl;
01308 
01309         KDE_struct_stat sbuff;
01310 
01311         QCString file = QFile::encodeName( copy );
01312 
01313         if ( KDE_stat( (const char*)file, &sbuff ) == 0 ) {
01314             if ( S_ISDIR ( sbuff.st_mode ) )
01315                 match.append( '/' );
01316         }
01317         else {
01318             kdDebug() << "Could not stat file " << copy << endl;
01319         }
01320     }
01321 }
01322 
01323 void KURLCompletion::postProcessMatches( QStringList * matches ) const
01324 {
01325     if ( !matches->isEmpty() && d->last_compl_type == CTFile ) {
01326         QStringList::Iterator it = matches->begin();
01327         for (; it != matches->end(); ++it ) {
01328             adjustMatch( (*it) );
01329         }
01330     }
01331 }
01332 
01333 void KURLCompletion::postProcessMatches( KCompletionMatches * matches ) const
01334 {
01335     if ( !matches->isEmpty() && d->last_compl_type == CTFile ) {
01336         KCompletionMatches::Iterator it = matches->begin();
01337         for (; it != matches->end(); ++it ) {
01338             adjustMatch( (*it).value() );
01339         }
01340     }
01341 }
01342 
01343 void KURLCompletion::customEvent(QCustomEvent *e)
01344 {
01345     if ( e->type() == CompletionMatchEvent::uniqueType() ) {
01346 
01347         CompletionMatchEvent *event = static_cast<CompletionMatchEvent *>( e );
01348 
01349         event->completionThread()->wait();
01350 
01351         if ( !isListedURL( CTUser ) ) {
01352             stop();
01353             clear();
01354             addMatches( event->completionThread()->matches() );
01355         }
01356 
01357         setListedURL( CTUser );
01358 
01359         if ( d->userListThread == event->completionThread() )
01360             d->userListThread = 0;
01361 
01362         if ( d->dirListThread == event->completionThread() )
01363             d->dirListThread = 0;
01364 
01365         delete event->completionThread();
01366     }
01367 }
01368 
01369 // static
01370 QString KURLCompletion::replacedPath( const QString& text, bool replaceHome, bool replaceEnv )
01371 {
01372     if ( text.isEmpty() )
01373         return text;
01374 
01375     MyURL url( text, QString::null ); // no need to replace something of our current cwd
01376     if ( !url.kurl()->isLocalFile() )
01377         return text;
01378 
01379     url.filter( replaceHome, replaceEnv );
01380     return url.dir() + url.file();
01381 }
01382 
01383 
01384 QString KURLCompletion::replacedPath( const QString& text )
01385 {
01386     return replacedPath( text, d->replace_home, d->replace_env );
01387 }
01388 
01391 // Static functions
01392 
01393 /*
01394  * expandEnv
01395  *
01396  * Expand environment variables in text. Escaped '$' are ignored.
01397  * Return true if expansion was made.
01398  */
01399 static bool expandEnv( QString &text )
01400 {
01401     // Find all environment variables beginning with '$'
01402     //
01403     int pos = 0;
01404 
01405     bool expanded = false;
01406 
01407     while ( (pos = text.find('$', pos)) != -1 ) {
01408 
01409         // Skip escaped '$'
01410         //
01411         if ( text[pos-1] == '\\' ) {
01412             pos++;
01413         }
01414         // Variable found => expand
01415         //
01416         else {
01417             // Find the end of the variable = next '/' or ' '
01418             //
01419             int pos2 = text.find( ' ', pos+1 );
01420             int pos_tmp = text.find( '/', pos+1 );
01421 
01422             if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
01423                 pos2 = pos_tmp;
01424 
01425             if ( pos2 == -1 )
01426                 pos2 = text.length();
01427 
01428             // Replace if the variable is terminated by '/' or ' '
01429             // and defined
01430             //
01431             if ( pos2 >= 0 ) {
01432                 int len = pos2 - pos;
01433                 QString key = text.mid( pos+1, len-1);
01434                 QString value =
01435                     QString::fromLocal8Bit( ::getenv(key.local8Bit()) );
01436 
01437                 if ( !value.isEmpty() ) {
01438                     expanded = true;
01439                     text.replace( pos, len, value );
01440                     pos = pos + value.length();
01441                 }
01442                 else {
01443                     pos = pos2;
01444                 }
01445             }
01446         }
01447     }
01448 
01449     return expanded;
01450 }
01451 
01452 /*
01453  * expandTilde
01454  *
01455  * Replace "~user" with the users home directory
01456  * Return true if expansion was made.
01457  */
01458 static bool expandTilde(QString &text)
01459 {
01460     if ( text[0] != '~' )
01461         return false;
01462 
01463     bool expanded = false;
01464 
01465     // Find the end of the user name = next '/' or ' '
01466     //
01467     int pos2 = text.find( ' ', 1 );
01468     int pos_tmp = text.find( '/', 1 );
01469 
01470     if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
01471         pos2 = pos_tmp;
01472 
01473     if ( pos2 == -1 )
01474         pos2 = text.length();
01475 
01476     // Replace ~user if the user name is terminated by '/' or ' '
01477     //
01478     if ( pos2 >= 0 ) {
01479 
01480         QString user = text.mid( 1, pos2-1 );
01481         QString dir;
01482 
01483         // A single ~ is replaced with $HOME
01484         //
01485         if ( user.isEmpty() ) {
01486             dir = QDir::homeDirPath();
01487         }
01488         // ~user is replaced with the dir from passwd
01489         //
01490         else {
01491             struct passwd *pw = ::getpwnam( user.local8Bit() );
01492 
01493             if ( pw )
01494                 dir = QFile::decodeName( pw->pw_dir );
01495 
01496             ::endpwent();
01497         }
01498 
01499         if ( !dir.isEmpty() ) {
01500             expanded = true;
01501             text.replace(0, pos2, dir);
01502         }
01503     }
01504 
01505     return expanded;
01506 }
01507 
01508 /*
01509  * unescape
01510  *
01511  * Remove escapes and return the result in a new string
01512  *
01513  */
01514 static QString unescape(const QString &text)
01515 {
01516     QString result;
01517 
01518     for (uint pos = 0; pos < text.length(); pos++)
01519         if ( text[pos] != '\\' )
01520             result.insert( result.length(), text[pos] );
01521 
01522     return result;
01523 }
01524 
01525 void KURLCompletion::virtual_hook( int id, void* data )
01526 { KCompletion::virtual_hook( id, data ); }
01527 
01528 #include "kurlcompletion.moc"
01529 

kio

Skip menu "kio"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • dcop
  • DNSSD
  • interfaces
  • Kate
  • kconf_update
  • KDECore
  • KDED
  • kdefx
  • KDEsu
  • kdeui
  • KDocTools
  • KHTML
  • KImgIO
  • KInit
  • kio
  • kioslave
  • KJS
  • KNewStuff
  • KParts
  • KUtils
Generated for API Reference by doxygen 1.5.9
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal