kio Library API Documentation

kurlcompletion.cpp

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