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