kurlcompletion.cpp Source File (kio Library)

| # kio Library API Documentation
Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Source Files

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

This file is part of the documentation for kdelibs Version 3.1.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Wed Jan 28 13:14:33 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001