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.4.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Sun Feb 27 22:15:33 2005 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001