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 #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 
KDE Logo
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