kio Library API Documentation

kurlcompletion.cpp

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