kurlcompletion.cpp

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