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