/* This file is part of the KDE libraries Copyright (C) 2000 David Smith Copyright (C) 2004 Scott Wheeler This class was inspired by a previous KURLCompletion by Henner Zeller This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kurlcompletion.h" static bool expandTilde(TQString &); static bool expandEnv(TQString &); static TQString unescape(const TQString &text); // Permission mask for files that are executable by // user, group or other #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH) // Constants for types of completion enum ComplType {CTNone=0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo}; class CompletionThread; /** * A custom event type that is used to return a list of completion * matches from an asyncrynous lookup. */ class CompletionMatchEvent : public TQCustomEvent { public: CompletionMatchEvent( CompletionThread *thread ) : TQCustomEvent( uniqueType() ), m_completionThread( thread ) {} CompletionThread *completionThread() const { return m_completionThread; } static int uniqueType() { return User + 61080; } private: CompletionThread *m_completionThread; }; class CompletionThread : public TQThread { protected: CompletionThread( KURLCompletion *receiver ) : TQThread(), m_receiver( receiver ), m_terminationRequested( false ) {} public: void requestTermination() { m_terminationRequested = true; } TQDeepCopy matches() const { return m_matches; } protected: void addMatch( const TQString &match ) { m_matches.append( match ); } bool terminationRequested() const { return m_terminationRequested; } void done() { if ( !m_terminationRequested ) kapp->postEvent( m_receiver, new CompletionMatchEvent( this ) ); else delete this; } private: KURLCompletion *m_receiver; TQStringList m_matches; bool m_terminationRequested; }; /** * A simple thread that fetches a list of tilde-completions and returns this * to the caller via a CompletionMatchEvent. */ class UserListThread : public CompletionThread { public: UserListThread( KURLCompletion *receiver ) : CompletionThread( receiver ) {} protected: virtual void run() { static const TQChar tilde = '~'; struct passwd *pw; while ( ( pw = ::getpwent() ) && !terminationRequested() ) addMatch( tilde + TQString::fromLocal8Bit( pw->pw_name ) ); ::endpwent(); addMatch( tilde ); done(); } }; class DirectoryListThread : public CompletionThread { public: DirectoryListThread( KURLCompletion *receiver, const TQStringList &dirList, const TQString &filter, bool onlyExe, bool onlyDir, bool noHidden, bool appendSlashToDir ) : CompletionThread( receiver ), m_dirList( TQDeepCopy( dirList ) ), m_filter( TQDeepCopy( filter ) ), m_onlyExe( onlyExe ), m_onlyDir( onlyDir ), m_noHidden( noHidden ), m_appendSlashToDir( appendSlashToDir ) {} virtual void run(); private: TQStringList m_dirList; TQString m_filter; bool m_onlyExe; bool m_onlyDir; bool m_noHidden; bool m_appendSlashToDir; }; void DirectoryListThread::run() { // Thread safety notes: // // There very possibly may be thread safety issues here, but I've done a check // of all of the things that would seem to be problematic. Here are a few // things that I have checked to be safe here (some used indirectly): // // TQDir::currentDirPath(), TQDir::setCurrent(), TQFile::decodeName(), TQFile::encodeName() // TQString::fromLocal8Bit(), TQString::local8Bit(), TQTextCodec::codecForLocale() // // Also see (for POSIX functions): // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html DIR *dir = 0; for ( TQStringList::ConstIterator it = m_dirList.begin(); it != m_dirList.end() && !terminationRequested(); ++it ) { // Open the next directory if ( !dir ) { dir = ::opendir( TQFile::encodeName( *it ) ); if ( ! dir ) { kdDebug() << "Failed to open dir: " << *it << endl; done(); return; } } // A trick from TDEIO that helps performance by a little bit: // chdir to the directroy so we won't have to deal with full paths // with stat() TQString path = TQDir::currentDirPath(); TQDir::setCurrent( *it ); // Loop through all directory entries // Solaris and IRIX dirent structures do not allocate space for d_name. On // systems that do (HP-UX, Linux, Tru64 UNIX), we overallocate space but // that's ok. #ifndef HAVE_READDIR_R struct dirent *dirEntry = 0; while ( !terminationRequested() && (dirEntry = ::readdir( dir))) #else #if !defined(MAXPATHLEN) && defined(__GNU__) #define MAXPATHLEN UCHAR_MAX #endif struct dirent *dirPosition = (struct dirent *) malloc( sizeof( struct dirent ) + MAXPATHLEN + 1 ); struct dirent *dirEntry = 0; while ( !terminationRequested() && ::readdir_r( dir, dirPosition, &dirEntry ) == 0 && dirEntry ) #endif { // Skip hidden files if m_noHidden is true if ( dirEntry->d_name[0] == '.' && m_noHidden ) continue; // Skip "." if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '\0' ) continue; // Skip ".." if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0' ) continue; TQString file = TQFile::decodeName( dirEntry->d_name ); if ( m_filter.isEmpty() || file.startsWith( m_filter ) ) { if ( m_onlyExe || m_onlyDir || m_appendSlashToDir ) { KDE_struct_stat sbuff; if ( KDE_stat( dirEntry->d_name, &sbuff ) == 0 ) { // Verify executable if ( m_onlyExe && ( sbuff.st_mode & MODE_EXE ) == 0 ) continue; // Verify directory if ( m_onlyDir && !S_ISDIR( sbuff.st_mode ) ) continue; // Add '/' to directories if ( m_appendSlashToDir && S_ISDIR( sbuff.st_mode ) ) file.append( '/' ); } else { kdDebug() << "Could not stat file " << file << endl; continue; } } addMatch( file ); } } // chdir to the original directory TQDir::setCurrent( path ); ::closedir( dir ); dir = 0; #ifdef HAVE_READDIR_R free( dirPosition ); #endif } done(); } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // MyURL - wrapper for KURL with some different functionality // class KURLCompletion::MyURL { public: MyURL(const TQString &url, const TQString &cwd); MyURL(const MyURL &url); ~MyURL(); KURL *kurl() const { return m_kurl; } TQString protocol() const { return m_kurl->protocol(); } // The directory with a trailing '/' TQString dir() const { return m_kurl->directory(false, false); } TQString file() const { return m_kurl->fileName(false); } // The initial, unparsed, url, as a string. TQString url() const { return m_url; } // Is the initial string a URL, or just a path (whether absolute or relative) bool isURL() const { return m_isURL; } void filter( bool replace_user_dir, bool replace_env ); private: void init(const TQString &url, const TQString &cwd); KURL *m_kurl; TQString m_url; bool m_isURL; }; KURLCompletion::MyURL::MyURL(const TQString &url, const TQString &cwd) { init(url, cwd); } KURLCompletion::MyURL::MyURL(const MyURL &url) { m_kurl = new KURL( *(url.m_kurl) ); m_url = url.m_url; m_isURL = url.m_isURL; } void KURLCompletion::MyURL::init(const TQString &url, const TQString &cwd) { // Save the original text m_url = url; // Non-const copy TQString url_copy = url; // Special shortcuts for "man:" and "info:" if ( url_copy[0] == '#' ) { if ( url_copy[1] == '#' ) url_copy.replace( 0, 2, TQString("info:") ); else url_copy.replace( 0, 1, TQString("man:") ); } // Look for a protocol in 'url' TQRegExp protocol_regex = TQRegExp( "^[^/\\s\\\\]*:" ); // Assume "file:" or whatever is given by 'cwd' if there is // no protocol. (KURL does this only for absoute paths) if ( protocol_regex.search( url_copy ) == 0 ) { m_kurl = new KURL( url_copy ); m_isURL = true; } else // relative path or ~ or $something { m_isURL = false; if ( cwd.isEmpty() ) { m_kurl = new KURL(); if ( !TQDir::isRelativePath(url_copy) || url_copy[0] == '$' || url_copy[0] == '~' ) m_kurl->setPath( url_copy ); else *m_kurl = url_copy; } else { KURL base = KURL::fromPathOrURL( cwd ); base.adjustPath(+1); if ( !TQDir::isRelativePath(url_copy) || url_copy[0] == '~' || url_copy[0] == '$' ) { m_kurl = new KURL(); m_kurl->setPath( url_copy ); } else // relative path { //m_kurl = new KURL( base, url_copy ); m_kurl = new KURL( base ); m_kurl->addPath( url_copy ); } } } } KURLCompletion::MyURL::~MyURL() { delete m_kurl; } void KURLCompletion::MyURL::filter( bool replace_user_dir, bool replace_env ) { TQString d = dir() + file(); if ( replace_user_dir ) expandTilde( d ); if ( replace_env ) expandEnv( d ); m_kurl->setPath( d ); } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KURLCompletionPrivate // class KURLCompletionPrivate { public: KURLCompletionPrivate() : url_auto_completion(true), userListThread(0), dirListThread(0) {} ~KURLCompletionPrivate(); TQValueList list_urls; bool onlyLocalProto; // urlCompletion() in Auto/Popup mode? bool url_auto_completion; // Append '/' to directories in Popup mode? // Doing that stat's all files and is slower bool popup_append_slash; // Keep track of currently listed files to avoid reading them again TQString last_path_listed; TQString last_file_listed; TQString last_prepend; int last_compl_type; int last_no_hidden; TQString cwd; // "current directory" = base dir for completion KURLCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion bool replace_env; bool replace_home; bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path TDEIO::ListJob *list_job; // tdeio job to list directories TQString prepend; // text to prepend to listed items TQString compl_text; // text to pass on to TDECompletion // Filters for files read with tdeio bool list_urls_only_exe; // true = only list executables bool list_urls_no_hidden; TQString list_urls_filter; // filter for listed files CompletionThread *userListThread; CompletionThread *dirListThread; }; KURLCompletionPrivate::~KURLCompletionPrivate() { if ( userListThread ) userListThread->requestTermination(); if ( dirListThread ) dirListThread->requestTermination(); } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KURLCompletion // KURLCompletion::KURLCompletion() : TDECompletion() { init(); } KURLCompletion::KURLCompletion( Mode mode ) : TDECompletion() { init(); setMode ( mode ); } KURLCompletion::~KURLCompletion() { stop(); delete d; } void KURLCompletion::init() { d = new KURLCompletionPrivate; d->cwd = TQDir::homeDirPath(); d->replace_home = true; d->replace_env = true; d->last_no_hidden = false; d->last_compl_type = 0; d->list_job = 0L; d->mode = KURLCompletion::FileCompletion; // Read settings TDEConfig *c = TDEGlobal::config(); TDEConfigGroupSaver cgs( c, "URLCompletion" ); d->url_auto_completion = c->readBoolEntry("alwaysAutoComplete", true); d->popup_append_slash = c->readBoolEntry("popupAppendSlash", true); d->onlyLocalProto = c->readBoolEntry("LocalProtocolsOnly", false); } void KURLCompletion::setDir(const TQString &dir) { d->cwd = dir; } TQString KURLCompletion::dir() const { return d->cwd; } KURLCompletion::Mode KURLCompletion::mode() const { return d->mode; } void KURLCompletion::setMode( Mode mode ) { d->mode = mode; } bool KURLCompletion::replaceEnv() const { return d->replace_env; } void KURLCompletion::setReplaceEnv( bool replace ) { d->replace_env = replace; } bool KURLCompletion::replaceHome() const { return d->replace_home; } void KURLCompletion::setReplaceHome( bool replace ) { d->replace_home = replace; } /* * makeCompletion() * * Entry point for file name completion */ TQString KURLCompletion::makeCompletion(const TQString &text) { //kdDebug() << "KURLCompletion::makeCompletion: " << text << " d->cwd=" << d->cwd << endl; MyURL url(text, d->cwd); d->compl_text = text; // Set d->prepend to the original URL, with the filename [and ref/query] stripped. // This is what gets prepended to the directory-listing matches. int toRemove = url.file().length() - url.kurl()->query().length(); if ( url.kurl()->hasRef() ) toRemove += url.kurl()->ref().length() + 1; d->prepend = text.left( text.length() - toRemove ); d->complete_url = url.isURL(); TQString match; // Environment variables // if ( d->replace_env && envCompletion( url, &match ) ) return match; // User directories // if ( d->replace_home && userCompletion( url, &match ) ) return match; // Replace user directories and variables url.filter( d->replace_home, d->replace_env ); //kdDebug() << "Filtered: proto=" << url.protocol() // << ", dir=" << url.dir() // << ", file=" << url.file() // << ", kurl url=" << *url.kurl() << endl; if ( d->mode == ExeCompletion ) { // Executables // if ( exeCompletion( url, &match ) ) return match; // KRun can run "man:" and "info:" etc. so why not treat them // as executables... if ( urlCompletion( url, &match ) ) return match; } else if ( d->mode == SystemExeCompletion ) { // Executables // if ( systemexeCompletion( url, &match ) ) return match; // KRun can run "man:" and "info:" etc. so why not treat them // as executables... if ( urlCompletion( url, &match ) ) return match; } else { // Local files, directories // if ( fileCompletion( url, &match ) ) return match; // All other... // if ( urlCompletion( url, &match ) ) return match; } setListedURL( CTNone ); stop(); return TQString::null; } /* * finished * * Go on and call TDECompletion. * Called when all matches have been added */ TQString KURLCompletion::finished() { if ( d->last_compl_type == CTInfo ) return TDECompletion::makeCompletion( d->compl_text.lower() ); else return TDECompletion::makeCompletion( d->compl_text ); } /* * isRunning * * Return true if either a TDEIO job or the DirLister * is running */ bool KURLCompletion::isRunning() const { return d->list_job || (d->dirListThread && !d->dirListThread->finished()); } /* * stop * * Stop and delete a running TDEIO job or the DirLister */ void KURLCompletion::stop() { if ( d->list_job ) { d->list_job->kill(); d->list_job = 0L; } if ( !d->list_urls.isEmpty() ) { TQValueList::Iterator it = d->list_urls.begin(); for ( ; it != d->list_urls.end(); it++ ) delete (*it); d->list_urls.clear(); } if ( d->dirListThread ) { d->dirListThread->requestTermination(); d->dirListThread = 0; } } /* * Keep track of the last listed directory */ void KURLCompletion::setListedURL( int complType, const TQString& dir, const TQString& filter, bool no_hidden ) { d->last_compl_type = complType; d->last_path_listed = dir; d->last_file_listed = filter; d->last_no_hidden = (int)no_hidden; d->last_prepend = d->prepend; } bool KURLCompletion::isListedURL( int complType, const TQString& dir, const TQString& filter, bool no_hidden ) { return d->last_compl_type == complType && ( d->last_path_listed == dir || (dir.isEmpty() && d->last_path_listed.isEmpty()) ) && ( filter.startsWith(d->last_file_listed) || (filter.isEmpty() && d->last_file_listed.isEmpty()) ) && d->last_no_hidden == (int)no_hidden && d->last_prepend == d->prepend; // e.g. relative path vs absolute } /* * isAutoCompletion * * Returns true if completion mode is Auto or Popup */ bool KURLCompletion::isAutoCompletion() { return completionMode() == TDEGlobalSettings::CompletionAuto || completionMode() == TDEGlobalSettings::CompletionPopup || completionMode() == TDEGlobalSettings::CompletionMan || completionMode() == TDEGlobalSettings::CompletionPopupAuto; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // User directories // bool KURLCompletion::userCompletion(const MyURL &url, TQString *match) { if ( url.protocol() != "file" || !url.dir().isEmpty() || url.file().at(0) != '~' ) return false; if ( !isListedURL( CTUser ) ) { stop(); clear(); if ( !d->userListThread ) { d->userListThread = new UserListThread( this ); d->userListThread->start(); // If the thread finishes quickly make sure that the results // are added to the first matching case. d->userListThread->wait( 200 ); TQStringList l = d->userListThread->matches(); addMatches( l ); } } *match = finished(); return true; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // Environment variables // #if !defined(__OpenBSD__) && !defined(__FreeBSD__) extern char **environ; // Array of environment variables #endif bool KURLCompletion::envCompletion(const MyURL &url, TQString *match) { #if defined(__OpenBSD__) || defined(__FreeBSD__) return false; #else if ( url.file().at(0) != '$' ) return false; if ( !isListedURL( CTEnv ) ) { stop(); clear(); char **env = environ; TQString dollar = TQString("$"); TQStringList l; while ( *env ) { TQString s = TQString::fromLocal8Bit( *env ); int pos = s.find('='); if ( pos == -1 ) pos = s.length(); if ( pos > 0 ) l.append( dollar + s.left(pos) ); env++; } addMatches( l ); } setListedURL( CTEnv ); *match = finished(); return true; #endif } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Executables // bool KURLCompletion::exeCompletion(const MyURL &url, TQString *match) { if ( url.protocol() != "file" ) return false; TQString dir = url.dir(); dir = unescape( dir ); // remove escapes // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. $PATH // 4. no directory at all TQStringList dirList; if ( !TQDir::isRelativePath(dir) ) { // complete path in url dirList.append( dir ); } else if ( !dir.isEmpty() && !d->cwd.isEmpty() ) { // current directory dirList.append( d->cwd + '/' + dir ); } else if ( !url.file().isEmpty() ) { // $PATH dirList = TQStringList::split(KPATH_SEPARATOR, TQString::fromLocal8Bit(::getenv("PATH"))); TQStringList::Iterator it = dirList.begin(); for ( ; it != dirList.end(); it++ ) (*it).append('/'); } // No hidden files unless the user types "." bool no_hidden_files = url.file().at(0) != '.'; // List files if needed // if ( !isListedURL( CTExe, dir, url.file(), no_hidden_files ) ) { stop(); clear(); setListedURL( CTExe, dir, url.file(), no_hidden_files ); *match = listDirectories( dirList, url.file(), true, false, no_hidden_files ); } else if ( !isRunning() ) { *match = finished(); } else { if ( d->dirListThread ) setListedURL( CTExe, dir, url.file(), no_hidden_files ); *match = TQString::null; } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // System Executables // bool KURLCompletion::systemexeCompletion(const MyURL &url, TQString *match) { if ( url.protocol() != "file" ) return false; TQString dir = url.dir(); dir = unescape( dir ); // remove escapes // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. $PATH // 4. no directory at all TQStringList dirList; if ( !url.file().isEmpty() ) { // $PATH dirList = TQStringList::split(KPATH_SEPARATOR, TQString::fromLocal8Bit(::getenv("PATH"))); TQStringList::Iterator it = dirList.begin(); for ( ; it != dirList.end(); it++ ) (*it).append('/'); } // No hidden files unless the user types "." bool no_hidden_files = url.file().at(0) != '.'; // List files if needed // if ( !isListedURL( CTExe, dir, url.file(), no_hidden_files ) ) { stop(); clear(); setListedURL( CTExe, dir, url.file(), no_hidden_files ); *match = listDirectories( dirList, url.file(), true, false, no_hidden_files ); } else if ( !isRunning() ) { *match = finished(); } else { if ( d->dirListThread ) setListedURL( CTExe, dir, url.file(), no_hidden_files ); *match = TQString::null; } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Local files // bool KURLCompletion::fileCompletion(const MyURL &url, TQString *match) { if ( url.protocol() != "file" ) return false; TQString dir = url.dir(); if (url.url()[0] == '.') { if (url.url().length() == 1) { *match = ( completionMode() == TDEGlobalSettings::CompletionMan )? "." : ".."; return true; } if (url.url().length() == 2 && url.url()[1]=='.') { *match=".."; return true; } } //kdDebug() << "fileCompletion " << url.url() << " dir=" << dir << endl; dir = unescape( dir ); // remove escapes // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. no directory at all TQStringList dirList; if ( !TQDir::isRelativePath(dir) ) { // complete path in url dirList.append( dir ); } else if ( !d->cwd.isEmpty() ) { // current directory dirList.append( d->cwd + '/' + dir ); } // No hidden files unless the user types "." bool no_hidden_files = ( url.file().at(0) != '.' ); // List files if needed // if ( !isListedURL( CTFile, dir, "", no_hidden_files ) ) { stop(); clear(); setListedURL( CTFile, dir, "", no_hidden_files ); // Append '/' to directories in Popup mode? bool append_slash = ( d->popup_append_slash && (completionMode() == TDEGlobalSettings::CompletionPopup || completionMode() == TDEGlobalSettings::CompletionPopupAuto ) ); bool only_dir = ( d->mode == DirCompletion ); *match = listDirectories( dirList, "", false, only_dir, no_hidden_files, append_slash ); } else if ( !isRunning() ) { *match = finished(); } else { *match = TQString::null; } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // URLs not handled elsewhere... // bool KURLCompletion::urlCompletion(const MyURL &url, TQString *match) { //kdDebug() << "urlCompletion: url = " << *url.kurl() << endl; if (d->onlyLocalProto && KProtocolInfo::protocolClass(url.protocol()) != ":local") return false; // Use d->cwd as base url in case url is not absolute KURL url_cwd = KURL::fromPathOrURL( d->cwd ); // Create an URL with the directory to be listed KURL url_dir( url_cwd, url.kurl()->url() ); // Don't try url completion if // 1. malformed url // 2. protocol that doesn't have listDir() // 3. there is no directory (e.g. "ftp://ftp.kd" shouldn't do anything) // 4. auto or popup completion mode depending on settings bool man_or_info = ( url_dir.protocol() == TQString("man") || url_dir.protocol() == TQString("info") ); if ( !url_dir.isValid() || !KProtocolInfo::supportsListing( url_dir ) || ( !man_or_info && ( url_dir.directory(false,false).isEmpty() || ( isAutoCompletion() && !d->url_auto_completion ) ) ) ) { return false; } url_dir.setFileName(""); // not really nesseccary, but clear the filename anyway... // Remove escapes TQString dir = url_dir.directory( false, false ); dir = unescape( dir ); url_dir.setPath( dir ); // List files if needed // if ( !isListedURL( CTUrl, url_dir.prettyURL(), url.file() ) ) { stop(); clear(); setListedURL( CTUrl, url_dir.prettyURL(), "" ); TQValueList url_list; url_list.append( new KURL( url_dir ) ); listURLs( url_list, "", false ); *match = TQString::null; } else if ( !isRunning() ) { *match = finished(); } else { *match = TQString::null; } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Directory and URL listing // /* * addMatches * * Called to add matches to TDECompletion */ void KURLCompletion::addMatches( const TQStringList &matches ) { TQStringList::ConstIterator it = matches.begin(); TQStringList::ConstIterator end = matches.end(); if ( d->complete_url ) for ( ; it != end; it++ ) addItem( d->prepend + KURL::encode_string(*it)); else for ( ; it != end; it++ ) addItem( d->prepend + (*it)); } /* * listDirectories * * List files starting with 'filter' in the given directories, * either using DirLister or listURLs() * * In either case, addMatches() is called with the listed * files, and eventually finished() when the listing is done * * Returns the match if available, or TQString::null if * DirLister timed out or using tdeio */ TQString KURLCompletion::listDirectories( const TQStringList &dirList, const TQString &filter, bool only_exe, bool only_dir, bool no_hidden, bool append_slash_to_dir) { assert( !isRunning() ); if ( !::getenv("KURLCOMPLETION_LOCAL_TDEIO") ) { //kdDebug() << "Listing (listDirectories): " << dirList << " filter=" << filter << " without TDEIO" << endl; // Don't use TDEIO if ( d->dirListThread ) d->dirListThread->requestTermination(); TQStringList dirs; for ( TQStringList::ConstIterator it = dirList.begin(); it != dirList.end(); ++it ) { KURL url; url.setPath(*it); if ( kapp->authorizeURLAction( "list", KURL(), url ) ) dirs.append( *it ); } d->dirListThread = new DirectoryListThread( this, dirs, filter, only_exe, only_dir, no_hidden, append_slash_to_dir ); d->dirListThread->start(); d->dirListThread->wait( 200 ); addMatches( d->dirListThread->matches() ); return finished(); } else { // Use TDEIO //kdDebug() << "Listing (listDirectories): " << dirList << " with TDEIO" << endl; TQValueList url_list; TQStringList::ConstIterator it = dirList.begin(); for ( ; it != dirList.end(); it++ ) url_list.append( new KURL(*it) ); listURLs( url_list, filter, only_exe, no_hidden ); // Will call addMatches() and finished() return TQString::null; } } /* * listURLs * * Use TDEIO to list the given urls * * addMatches() is called with the listed files * finished() is called when the listing is done */ void KURLCompletion::listURLs( const TQValueList &urls, const TQString &filter, bool only_exe, bool no_hidden ) { assert( d->list_urls.isEmpty() ); assert( d->list_job == 0L ); d->list_urls = urls; d->list_urls_filter = filter; d->list_urls_only_exe = only_exe; d->list_urls_no_hidden = no_hidden; // kdDebug() << "Listing URLs: " << urls[0]->prettyURL() << ",..." << endl; // Start it off by calling slotIOFinished // // This will start a new list job as long as there // are urls in d->list_urls // slotIOFinished(0L); } /* * slotEntries * * Receive files listed by TDEIO and call addMatches() */ void KURLCompletion::slotEntries(TDEIO::Job*, const TDEIO::UDSEntryList& entries) { TQStringList matches; TDEIO::UDSEntryListConstIterator it = entries.begin(); TDEIO::UDSEntryListConstIterator end = entries.end(); TQString filter = d->list_urls_filter; int filter_len = filter.length(); // Iterate over all files // for (; it != end; ++it) { TQString name; TQString url; bool is_exe = false; bool is_dir = false; TDEIO::UDSEntry e = *it; TDEIO::UDSEntry::ConstIterator it_2 = e.begin(); for( ; it_2 != e.end(); it_2++ ) { switch ( (*it_2).m_uds ) { case TDEIO::UDS_NAME: name = (*it_2).m_str; break; case TDEIO::UDS_ACCESS: is_exe = ((*it_2).m_long & MODE_EXE) != 0; break; case TDEIO::UDS_FILE_TYPE: is_dir = ((*it_2).m_long & S_IFDIR) != 0; break; case TDEIO::UDS_URL: url = (*it_2).m_str; break; } } if (!url.isEmpty()) { // kdDebug() << "KURLCompletion::slotEntries url: " << url << endl; name = KURL(url).fileName(); } // kdDebug() << "KURLCompletion::slotEntries name: " << name << endl; if ( name[0] == '.' && ( d->list_urls_no_hidden || name.length() == 1 || ( name.length() == 2 && name[1] == '.' ) ) ) continue; if ( d->mode == DirCompletion && !is_dir ) continue; if ( filter_len == 0 || name.left(filter_len) == filter ) { if ( is_dir ) name.append( '/' ); if ( is_exe || !d->list_urls_only_exe ) matches.append( name ); } } addMatches( matches ); } /* * slotIOFinished * * Called when a TDEIO job is finished. * * Start a new list job if there are still urls in * d->list_urls, otherwise call finished() */ void KURLCompletion::slotIOFinished( TDEIO::Job * job ) { // kdDebug() << "slotIOFinished() " << endl; assert( job == d->list_job ); if ( d->list_urls.isEmpty() ) { d->list_job = 0L; finished(); // will call TDECompletion::makeCompletion() } else { KURL *kurl = d->list_urls.first(); d->list_urls.remove( kurl ); // kdDebug() << "Start TDEIO: " << kurl->prettyURL() << endl; d->list_job = TDEIO::listDir( *kurl, false ); d->list_job->addMetaData("no-auth-prompt", "true"); assert( d->list_job ); connect( d->list_job, TQ_SIGNAL(result(TDEIO::Job*)), TQ_SLOT(slotIOFinished(TDEIO::Job*)) ); connect( d->list_job, TQ_SIGNAL( entries( TDEIO::Job*, const TDEIO::UDSEntryList&)), TQ_SLOT( slotEntries( TDEIO::Job*, const TDEIO::UDSEntryList&)) ); delete kurl; } } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /* * postProcessMatch, postProcessMatches * * Called by TDECompletion before emitting match() and matches() * * Append '/' to directories for file completion. This is * done here to avoid stat()'ing a lot of files */ void KURLCompletion::postProcessMatch( TQString *match ) const { // kdDebug() << "KURLCompletion::postProcess: " << *match << endl; if ( !match->isEmpty() ) { // Add '/' to directories in file completion mode // unless it has already been done if ( d->last_compl_type == CTFile ) adjustMatch( *match ); } } void KURLCompletion::adjustMatch( TQString& match ) const { if ( match.at( match.length()-1 ) != '/' ) { TQString copy; if ( match.startsWith( TQString("file:") ) ) copy = KURL(match).path(); else copy = match; expandTilde( copy ); expandEnv( copy ); if ( TQDir::isRelativePath(copy) ) copy.prepend( d->cwd + '/' ); // kdDebug() << "postProcess: stating " << copy << endl; KDE_struct_stat sbuff; TQCString file = TQFile::encodeName( copy ); if ( KDE_stat( (const char*)file, &sbuff ) == 0 ) { if ( S_ISDIR ( sbuff.st_mode ) ) match.append( '/' ); } else { kdDebug() << "Could not stat file " << copy << endl; } } } void KURLCompletion::postProcessMatches( TQStringList * matches ) const { if ( !matches->isEmpty() && d->last_compl_type == CTFile ) { TQStringList::Iterator it = matches->begin(); for (; it != matches->end(); ++it ) { adjustMatch( (*it) ); } } } void KURLCompletion::postProcessMatches( TDECompletionMatches * matches ) const { if ( !matches->isEmpty() && d->last_compl_type == CTFile ) { TDECompletionMatches::Iterator it = matches->begin(); for (; it != matches->end(); ++it ) { adjustMatch( (*it).value() ); } } } void KURLCompletion::customEvent(TQCustomEvent *e) { if ( e->type() == CompletionMatchEvent::uniqueType() ) { CompletionMatchEvent *event = static_cast( e ); event->completionThread()->wait(); if ( !isListedURL( CTUser ) ) { stop(); clear(); addMatches( event->completionThread()->matches() ); } setListedURL( CTUser ); if ( d->userListThread == event->completionThread() ) d->userListThread = 0; if ( d->dirListThread == event->completionThread() ) d->dirListThread = 0; delete event->completionThread(); } } // static TQString KURLCompletion::replacedPath( const TQString& text, bool replaceHome, bool replaceEnv ) { if ( text.isEmpty() ) return text; MyURL url( text, TQString::null ); // no need to replace something of our current cwd if ( !url.kurl()->isLocalFile() ) return text; url.filter( replaceHome, replaceEnv ); return url.dir() + url.file(); } TQString KURLCompletion::replacedPath( const TQString& text ) { return replacedPath( text, d->replace_home, d->replace_env ); } ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// // Static functions /* * expandEnv * * Expand environment variables in text. Escaped '$' are ignored. * Return true if expansion was made. */ static bool expandEnv( TQString &text ) { // Find all environment variables beginning with '$' // int pos = 0; bool expanded = false; while ( (pos = text.find('$', pos)) != -1 ) { // Skip escaped '$' // if ( text[pos-1] == '\\' ) { pos++; } // Variable found => expand // else { // Find the end of the variable = next '/' or ' ' // int pos2 = text.find( ' ', pos+1 ); int pos_tmp = text.find( '/', pos+1 ); if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) ) pos2 = pos_tmp; if ( pos2 == -1 ) pos2 = text.length(); // Replace if the variable is terminated by '/' or ' ' // and defined // if ( pos2 >= 0 ) { int len = pos2 - pos; TQString key = text.mid( pos+1, len-1); TQString value = TQString::fromLocal8Bit( ::getenv(key.local8Bit()) ); if ( !value.isEmpty() ) { expanded = true; text.replace( pos, len, value ); pos = pos + value.length(); } else { pos = pos2; } } } } return expanded; } /* * expandTilde * * Replace "~user" with the users home directory * Return true if expansion was made. */ static bool expandTilde(TQString &text) { if ( text[0] != '~' ) return false; bool expanded = false; // Find the end of the user name = next '/' or ' ' // int pos2 = text.find( ' ', 1 ); int pos_tmp = text.find( '/', 1 ); if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) ) pos2 = pos_tmp; if ( pos2 == -1 ) pos2 = text.length(); // Replace ~user if the user name is terminated by '/' or ' ' // if ( pos2 >= 0 ) { TQString user = text.mid( 1, pos2-1 ); TQString dir; // A single ~ is replaced with $HOME // if ( user.isEmpty() ) { dir = TQDir::homeDirPath(); } // ~user is replaced with the dir from passwd // else { struct passwd *pw = ::getpwnam( user.local8Bit() ); if ( pw ) dir = TQFile::decodeName( pw->pw_dir ); ::endpwent(); } if ( !dir.isEmpty() ) { expanded = true; text.replace(0, pos2, dir); } } return expanded; } /* * unescape * * Remove escapes and return the result in a new string * */ static TQString unescape(const TQString &text) { TQString result; for (uint pos = 0; pos < text.length(); pos++) if ( text[pos] != '\\' ) result.insert( result.length(), text[pos] ); return result; } void KURLCompletion::virtual_hook( int id, void* data ) { TDECompletion::virtual_hook( id, data ); } #include "kurlcompletion.moc"