• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • kio/kio
 

kio/kio

  • kio
  • kio
kurlcompletion.cpp
1 /* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*-
2 
3  This file is part of the KDE libraries
4  Copyright (C) 2000 David Smith <dsmith@algonet.se>
5  Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
6 
7  This class was inspired by a previous KURLCompletion by
8  Henner Zeller <zeller@think.de>
9 
10  This library is free software; you can redistribute it and/or
11  modify it under the terms of the GNU Library General Public
12  License as published by the Free Software Foundation; either
13  version 2 of the License, or (at your option) any later version.
14 
15  This library is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  Library General Public License for more details.
19 
20  You should have received a copy of the GNU Library General Public License
21  along with this library; see the file COPYING.LIB. If not, write to
22  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  Boston, MA 02110-1301, USA.
24 */
25 
26 #include <config.h>
27 #include <stdlib.h>
28 #include <assert.h>
29 #include <limits.h>
30 
31 #include <tqstring.h>
32 #include <tqstringlist.h>
33 #include <tqvaluelist.h>
34 #include <tqregexp.h>
35 #include <tqtimer.h>
36 #include <tqdir.h>
37 #include <tqfile.h>
38 #include <tqtextstream.h>
39 #include <tqdeepcopy.h>
40 #include <tqthread.h>
41 
42 #include <kapplication.h>
43 #include <kdebug.h>
44 #include <kcompletion.h>
45 #include <kurl.h>
46 #include <kio/jobclasses.h>
47 #include <kio/job.h>
48 #include <kprotocolinfo.h>
49 #include <kconfig.h>
50 #include <kglobal.h>
51 #include <klocale.h>
52 #include <kde_file.h>
53 
54 #include <sys/types.h>
55 #include <dirent.h>
56 #include <unistd.h>
57 #include <sys/stat.h>
58 #include <pwd.h>
59 #include <time.h>
60 #include <sys/param.h>
61 
62 #include "kurlcompletion.h"
63 
64 static bool expandTilde(TQString &);
65 static bool expandEnv(TQString &);
66 
67 static TQString unescape(const TQString &text);
68 
69 // Permission mask for files that are executable by
70 // user, group or other
71 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
72 
73 // Constants for types of completion
74 enum ComplType {CTNone=0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
75 
76 class CompletionThread;
77 
83 class CompletionMatchEvent : public TQCustomEvent
84 {
85 public:
86  CompletionMatchEvent( CompletionThread *thread ) :
87  TQCustomEvent( uniqueType() ),
88  m_completionThread( thread )
89  {}
90 
91  CompletionThread *completionThread() const { return m_completionThread; }
92  static int uniqueType() { return User + 61080; }
93 
94 private:
95  CompletionThread *m_completionThread;
96 };
97 
98 class CompletionThread : public TQThread
99 {
100 protected:
101  CompletionThread( KURLCompletion *receiver ) :
102  TQThread(),
103  m_receiver( receiver ),
104  m_terminationRequested( false )
105  {}
106 
107 public:
108  void requestTermination() { m_terminationRequested = true; }
109  TQDeepCopy<TQStringList> matches() const { return m_matches; }
110 
111 protected:
112  void addMatch( const TQString &match ) { m_matches.append( match ); }
113  bool terminationRequested() const { return m_terminationRequested; }
114  void done()
115  {
116  if ( !m_terminationRequested )
117  kapp->postEvent( m_receiver, new CompletionMatchEvent( this ) );
118  else
119  delete this;
120  }
121 
122 private:
123  KURLCompletion *m_receiver;
124  TQStringList m_matches;
125  bool m_terminationRequested;
126 };
127 
133 class UserListThread : public CompletionThread
134 {
135 public:
136  UserListThread( KURLCompletion *receiver ) :
137  CompletionThread( receiver )
138  {}
139 
140 protected:
141  virtual void run()
142  {
143  static const TQChar tilde = '~';
144 
145  struct passwd *pw;
146  while ( ( pw = ::getpwent() ) && !terminationRequested() )
147  addMatch( tilde + TQString::fromLocal8Bit( pw->pw_name ) );
148 
149  ::endpwent();
150 
151  addMatch( tilde );
152 
153  done();
154  }
155 };
156 
157 class DirectoryListThread : public CompletionThread
158 {
159 public:
160  DirectoryListThread( KURLCompletion *receiver,
161  const TQStringList &dirList,
162  const TQString &filter,
163  bool onlyExe,
164  bool onlyDir,
165  bool noHidden,
166  bool appendSlashToDir ) :
167  CompletionThread( receiver ),
168  m_dirList( TQDeepCopy<TQStringList>( dirList ) ),
169  m_filter( TQDeepCopy<TQString>( filter ) ),
170  m_onlyExe( onlyExe ),
171  m_onlyDir( onlyDir ),
172  m_noHidden( noHidden ),
173  m_appendSlashToDir( appendSlashToDir )
174  {}
175 
176  virtual void run();
177 
178 private:
179  TQStringList m_dirList;
180  TQString m_filter;
181  bool m_onlyExe;
182  bool m_onlyDir;
183  bool m_noHidden;
184  bool m_appendSlashToDir;
185 };
186 
187 void DirectoryListThread::run()
188 {
189  // Thread safety notes:
190  //
191  // There very possibly may be thread safety issues here, but I've done a check
192  // of all of the things that would seem to be problematic. Here are a few
193  // things that I have checked to be safe here (some used indirectly):
194  //
195  // TQDir::currentDirPath(), TQDir::setCurrent(), TQFile::decodeName(), TQFile::encodeName()
196  // TQString::fromLocal8Bit(), TQString::local8Bit(), TQTextCodec::codecForLocale()
197  //
198  // Also see (for POSIX functions):
199  // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
200 
201  DIR *dir = 0;
202 
203  for ( TQStringList::ConstIterator it = m_dirList.begin();
204  it != m_dirList.end() && !terminationRequested();
205  ++it )
206  {
207  // Open the next directory
208 
209  if ( !dir ) {
210  dir = ::opendir( TQFile::encodeName( *it ) );
211  if ( ! dir ) {
212  kdDebug() << "Failed to open dir: " << *it << endl;
213  done();
214  return;
215  }
216  }
217 
218  // A trick from KIO that helps performance by a little bit:
219  // chdir to the directroy so we won't have to deal with full paths
220  // with stat()
221 
222  TQString path = TQDir::currentDirPath();
223  TQDir::setCurrent( *it );
224 
225  // Loop through all directory entries
226  // Solaris and IRIX dirent structures do not allocate space for d_name. On
227  // systems that do (HP-UX, Linux, Tru64 UNIX), we overallocate space but
228  // that's ok.
229 #ifndef HAVE_READDIR_R
230  struct dirent *dirEntry = 0;
231  while ( !terminationRequested() &&
232  (dirEntry = ::readdir( dir)))
233 #else
234 #if !defined(MAXPATHLEN) && defined(__GNU__)
235 #define MAXPATHLEN UCHAR_MAX
236 #endif
237  struct dirent *dirPosition = (struct dirent *) malloc( sizeof( struct dirent ) + MAXPATHLEN + 1 );
238  struct dirent *dirEntry = 0;
239  while ( !terminationRequested() &&
240  ::readdir_r( dir, dirPosition, &dirEntry ) == 0 && dirEntry )
241 #endif
242 
243  {
244  // Skip hidden files if m_noHidden is true
245 
246  if ( dirEntry->d_name[0] == '.' && m_noHidden )
247  continue;
248 
249  // Skip "."
250 
251  if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '\0' )
252  continue;
253 
254  // Skip ".."
255 
256  if ( dirEntry->d_name[0] == '.' && dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0' )
257  continue;
258 
259  TQString file = TQFile::decodeName( dirEntry->d_name );
260 
261  if ( m_filter.isEmpty() || file.startsWith( m_filter ) ) {
262 
263  if ( m_onlyExe || m_onlyDir || m_appendSlashToDir ) {
264  KDE_struct_stat sbuff;
265 
266  if ( KDE_stat( dirEntry->d_name, &sbuff ) == 0 ) {
267 
268  // Verify executable
269 
270  if ( m_onlyExe && ( sbuff.st_mode & MODE_EXE ) == 0 )
271  continue;
272 
273  // Verify directory
274 
275  if ( m_onlyDir && !S_ISDIR( sbuff.st_mode ) )
276  continue;
277 
278  // Add '/' to directories
279 
280  if ( m_appendSlashToDir && S_ISDIR( sbuff.st_mode ) )
281  file.append( '/' );
282 
283  }
284  else {
285  kdDebug() << "Could not stat file " << file << endl;
286  continue;
287  }
288  }
289 
290  addMatch( file );
291  }
292  }
293 
294  // chdir to the original directory
295 
296  TQDir::setCurrent( path );
297 
298  ::closedir( dir );
299  dir = 0;
300 #ifdef HAVE_READDIR_R
301  free( dirPosition );
302 #endif
303  }
304 
305  done();
306 }
307 
310 // MyURL - wrapper for KURL with some different functionality
311 //
312 
313 class KURLCompletion::MyURL
314 {
315 public:
316  MyURL(const TQString &url, const TQString &cwd);
317  MyURL(const MyURL &url);
318  ~MyURL();
319 
320  KURL *kurl() const { return m_kurl; }
321 
322  TQString protocol() const { return m_kurl->protocol(); }
323  // The directory with a trailing '/'
324  TQString dir() const { return m_kurl->directory(false, false); }
325  TQString file() const { return m_kurl->fileName(false); }
326 
327  // The initial, unparsed, url, as a string.
328  TQString url() const { return m_url; }
329 
330  // Is the initial string a URL, or just a path (whether absolute or relative)
331  bool isURL() const { return m_isURL; }
332 
333  void filter( bool replace_user_dir, bool replace_env );
334 
335 private:
336  void init(const TQString &url, const TQString &cwd);
337 
338  KURL *m_kurl;
339  TQString m_url;
340  bool m_isURL;
341 };
342 
343 KURLCompletion::MyURL::MyURL(const TQString &url, const TQString &cwd)
344 {
345  init(url, cwd);
346 }
347 
348 KURLCompletion::MyURL::MyURL(const MyURL &url)
349 {
350  m_kurl = new KURL( *(url.m_kurl) );
351  m_url = url.m_url;
352  m_isURL = url.m_isURL;
353 }
354 
355 void KURLCompletion::MyURL::init(const TQString &url, const TQString &cwd)
356 {
357  // Save the original text
358  m_url = url;
359 
360  // Non-const copy
361  TQString url_copy = url;
362 
363  // Special shortcuts for "man:" and "info:"
364  if ( url_copy[0] == '#' ) {
365  if ( url_copy[1] == '#' )
366  url_copy.replace( 0, 2, TQString("info:") );
367  else
368  url_copy.replace( 0, 1, TQString("man:") );
369  }
370 
371  // Look for a protocol in 'url'
372  TQRegExp protocol_regex = TQRegExp( "^[^/\\s\\\\]*:" );
373 
374  // Assume "file:" or whatever is given by 'cwd' if there is
375  // no protocol. (KURL does this only for absoute paths)
376  if ( protocol_regex.search( url_copy ) == 0 )
377  {
378  m_kurl = new KURL( url_copy );
379  m_isURL = true;
380  }
381  else // relative path or ~ or $something
382  {
383  m_isURL = false;
384  if ( cwd.isEmpty() )
385  {
386  m_kurl = new KURL();
387  if ( !TQDir::isRelativePath(url_copy) || url_copy[0] == '$' || url_copy[0] == '~' )
388  m_kurl->setPath( url_copy );
389  else
390  *m_kurl = url_copy;
391  }
392  else
393  {
394  KURL base = KURL::fromPathOrURL( cwd );
395  base.adjustPath(+1);
396 
397  if ( !TQDir::isRelativePath(url_copy) || url_copy[0] == '~' || url_copy[0] == '$' )
398  {
399  m_kurl = new KURL();
400  m_kurl->setPath( url_copy );
401  }
402  else // relative path
403  {
404  //m_kurl = new KURL( base, url_copy );
405  m_kurl = new KURL( base );
406  m_kurl->addPath( url_copy );
407  }
408  }
409  }
410 }
411 
412 KURLCompletion::MyURL::~MyURL()
413 {
414  delete m_kurl;
415 }
416 
417 void KURLCompletion::MyURL::filter( bool replace_user_dir, bool replace_env )
418 {
419  TQString d = dir() + file();
420  if ( replace_user_dir ) expandTilde( d );
421  if ( replace_env ) expandEnv( d );
422  m_kurl->setPath( d );
423 }
424 
427 // KURLCompletionPrivate
428 //
429 class KURLCompletionPrivate
430 {
431 public:
432  KURLCompletionPrivate() : url_auto_completion(true),
433  userListThread(0),
434  dirListThread(0) {}
435  ~KURLCompletionPrivate();
436 
437  TQValueList<KURL*> list_urls;
438 
439  bool onlyLocalProto;
440 
441  // urlCompletion() in Auto/Popup mode?
442  bool url_auto_completion;
443 
444  // Append '/' to directories in Popup mode?
445  // Doing that stat's all files and is slower
446  bool popup_append_slash;
447 
448  // Keep track of currently listed files to avoid reading them again
449  TQString last_path_listed;
450  TQString last_file_listed;
451  TQString last_prepend;
452  int last_compl_type;
453  int last_no_hidden;
454 
455  TQString cwd; // "current directory" = base dir for completion
456 
457  KURLCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion
458  bool replace_env;
459  bool replace_home;
460  bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
461 
462  KIO::ListJob *list_job; // kio job to list directories
463 
464  TQString prepend; // text to prepend to listed items
465  TQString compl_text; // text to pass on to KCompletion
466 
467  // Filters for files read with kio
468  bool list_urls_only_exe; // true = only list executables
469  bool list_urls_no_hidden;
470  TQString list_urls_filter; // filter for listed files
471 
472  CompletionThread *userListThread;
473  CompletionThread *dirListThread;
474 };
475 
476 KURLCompletionPrivate::~KURLCompletionPrivate()
477 {
478  if ( userListThread )
479  userListThread->requestTermination();
480  if ( dirListThread )
481  dirListThread->requestTermination();
482 }
483 
486 // KURLCompletion
487 //
488 
489 KURLCompletion::KURLCompletion() : KCompletion()
490 {
491  init();
492 }
493 
494 
495 KURLCompletion::KURLCompletion( Mode mode ) : KCompletion()
496 {
497  init();
498  setMode ( mode );
499 }
500 
501 KURLCompletion::~KURLCompletion()
502 {
503  stop();
504  delete d;
505 }
506 
507 
508 void KURLCompletion::init()
509 {
510  d = new KURLCompletionPrivate;
511 
512  d->cwd = TQDir::homeDirPath();
513 
514  d->replace_home = true;
515  d->replace_env = true;
516  d->last_no_hidden = false;
517  d->last_compl_type = 0;
518  d->list_job = 0L;
519  d->mode = KURLCompletion::FileCompletion;
520 
521  // Read settings
522  KConfig *c = KGlobal::config();
523  KConfigGroupSaver cgs( c, "URLCompletion" );
524 
525  d->url_auto_completion = c->readBoolEntry("alwaysAutoComplete", true);
526  d->popup_append_slash = c->readBoolEntry("popupAppendSlash", true);
527  d->onlyLocalProto = c->readBoolEntry("LocalProtocolsOnly", false);
528 }
529 
530 void KURLCompletion::setDir(const TQString &dir)
531 {
532  d->cwd = dir;
533 }
534 
535 TQString KURLCompletion::dir() const
536 {
537  return d->cwd;
538 }
539 
540 KURLCompletion::Mode KURLCompletion::mode() const
541 {
542  return d->mode;
543 }
544 
545 void KURLCompletion::setMode( Mode mode )
546 {
547  d->mode = mode;
548 }
549 
550 bool KURLCompletion::replaceEnv() const
551 {
552  return d->replace_env;
553 }
554 
555 void KURLCompletion::setReplaceEnv( bool replace )
556 {
557  d->replace_env = replace;
558 }
559 
560 bool KURLCompletion::replaceHome() const
561 {
562  return d->replace_home;
563 }
564 
565 void KURLCompletion::setReplaceHome( bool replace )
566 {
567  d->replace_home = replace;
568 }
569 
570 /*
571  * makeCompletion()
572  *
573  * Entry point for file name completion
574  */
575 TQString KURLCompletion::makeCompletion(const TQString &text)
576 {
577  //kdDebug() << "KURLCompletion::makeCompletion: " << text << " d->cwd=" << d->cwd << endl;
578 
579  MyURL url(text, d->cwd);
580 
581  d->compl_text = text;
582 
583  // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
584  // This is what gets prepended to the directory-listing matches.
585  int toRemove = url.file().length() - url.kurl()->query().length();
586  if ( url.kurl()->hasRef() )
587  toRemove += url.kurl()->ref().length() + 1;
588  d->prepend = text.left( text.length() - toRemove );
589  d->complete_url = url.isURL();
590 
591  TQString match;
592 
593  // Environment variables
594  //
595  if ( d->replace_env && envCompletion( url, &match ) )
596  return match;
597 
598  // User directories
599  //
600  if ( d->replace_home && userCompletion( url, &match ) )
601  return match;
602 
603  // Replace user directories and variables
604  url.filter( d->replace_home, d->replace_env );
605 
606  //kdDebug() << "Filtered: proto=" << url.protocol()
607  // << ", dir=" << url.dir()
608  // << ", file=" << url.file()
609  // << ", kurl url=" << *url.kurl() << endl;
610 
611  if ( d->mode == ExeCompletion ) {
612  // Executables
613  //
614  if ( exeCompletion( url, &match ) )
615  return match;
616 
617  // KRun can run "man:" and "info:" etc. so why not treat them
618  // as executables...
619 
620  if ( urlCompletion( url, &match ) )
621  return match;
622  }
623  else if ( d->mode == SystemExeCompletion ) {
624  // Executables
625  //
626  if ( systemexeCompletion( url, &match ) )
627  return match;
628 
629  // KRun can run "man:" and "info:" etc. so why not treat them
630  // as executables...
631 
632  if ( urlCompletion( url, &match ) )
633  return match;
634  }
635  else {
636  // Local files, directories
637  //
638  if ( fileCompletion( url, &match ) )
639  return match;
640 
641  // All other...
642  //
643  if ( urlCompletion( url, &match ) )
644  return match;
645  }
646 
647  setListedURL( CTNone );
648  stop();
649 
650  return TQString::null;
651 }
652 
653 /*
654  * finished
655  *
656  * Go on and call KCompletion.
657  * Called when all matches have been added
658  */
659 TQString KURLCompletion::finished()
660 {
661  if ( d->last_compl_type == CTInfo )
662  return KCompletion::makeCompletion( d->compl_text.lower() );
663  else
664  return KCompletion::makeCompletion( d->compl_text );
665 }
666 
667 /*
668  * isRunning
669  *
670  * Return true if either a KIO job or the DirLister
671  * is running
672  */
673 bool KURLCompletion::isRunning() const
674 {
675  return d->list_job || (d->dirListThread && !d->dirListThread->finished());
676 }
677 
678 /*
679  * stop
680  *
681  * Stop and delete a running KIO job or the DirLister
682  */
683 void KURLCompletion::stop()
684 {
685  if ( d->list_job ) {
686  d->list_job->kill();
687  d->list_job = 0L;
688  }
689 
690  if ( !d->list_urls.isEmpty() ) {
691  TQValueList<KURL*>::Iterator it = d->list_urls.begin();
692  for ( ; it != d->list_urls.end(); it++ )
693  delete (*it);
694  d->list_urls.clear();
695  }
696 
697  if ( d->dirListThread ) {
698  d->dirListThread->requestTermination();
699  d->dirListThread = 0;
700  }
701 }
702 
703 /*
704  * Keep track of the last listed directory
705  */
706 void KURLCompletion::setListedURL( int complType,
707  const TQString& dir,
708  const TQString& filter,
709  bool no_hidden )
710 {
711  d->last_compl_type = complType;
712  d->last_path_listed = dir;
713  d->last_file_listed = filter;
714  d->last_no_hidden = (int)no_hidden;
715  d->last_prepend = d->prepend;
716 }
717 
718 bool KURLCompletion::isListedURL( int complType,
719  const TQString& dir,
720  const TQString& filter,
721  bool no_hidden )
722 {
723  return d->last_compl_type == complType
724  && ( d->last_path_listed == dir
725  || (dir.isEmpty() && d->last_path_listed.isEmpty()) )
726  && ( filter.startsWith(d->last_file_listed)
727  || (filter.isEmpty() && d->last_file_listed.isEmpty()) )
728  && d->last_no_hidden == (int)no_hidden
729  && d->last_prepend == d->prepend; // e.g. relative path vs absolute
730 }
731 
732 /*
733  * isAutoCompletion
734  *
735  * Returns true if completion mode is Auto or Popup
736  */
737 bool KURLCompletion::isAutoCompletion()
738 {
739  return completionMode() == KGlobalSettings::CompletionAuto
740  || completionMode() == KGlobalSettings::CompletionPopup
741  || completionMode() == KGlobalSettings::CompletionMan
742  || completionMode() == KGlobalSettings::CompletionPopupAuto;
743 }
746 // User directories
747 //
748 
749 bool KURLCompletion::userCompletion(const MyURL &url, TQString *match)
750 {
751  if ( url.protocol() != "file"
752  || !url.dir().isEmpty()
753  || url.file().at(0) != '~' )
754  return false;
755 
756  if ( !isListedURL( CTUser ) ) {
757  stop();
758  clear();
759 
760  if ( !d->userListThread ) {
761  d->userListThread = new UserListThread( this );
762  d->userListThread->start();
763 
764  // If the thread finishes quickly make sure that the results
765  // are added to the first matching case.
766 
767  d->userListThread->wait( 200 );
768  TQStringList l = d->userListThread->matches();
769  addMatches( l );
770  }
771  }
772  *match = finished();
773  return true;
774 }
775 
778 // Environment variables
779 //
780 
781 extern char **environ; // Array of environment variables
782 
783 bool KURLCompletion::envCompletion(const MyURL &url, TQString *match)
784 {
785  if ( url.file().at(0) != '$' )
786  return false;
787 
788  if ( !isListedURL( CTEnv ) ) {
789  stop();
790  clear();
791 
792  char **env = environ;
793 
794  TQString dollar = TQString("$");
795 
796  TQStringList l;
797 
798  while ( *env ) {
799  TQString s = TQString::fromLocal8Bit( *env );
800 
801  int pos = s.find('=');
802 
803  if ( pos == -1 )
804  pos = s.length();
805 
806  if ( pos > 0 )
807  l.append( dollar + s.left(pos) );
808 
809  env++;
810  }
811 
812  addMatches( l );
813  }
814 
815  setListedURL( CTEnv );
816 
817  *match = finished();
818  return true;
819 }
820 
823 // Executables
824 //
825 
826 bool KURLCompletion::exeCompletion(const MyURL &url, TQString *match)
827 {
828  if ( url.protocol() != "file" )
829  return false;
830 
831  TQString dir = url.dir();
832 
833  dir = unescape( dir ); // remove escapes
834 
835  // Find directories to search for completions, either
836  //
837  // 1. complete path given in url
838  // 2. current directory (d->cwd)
839  // 3. $PATH
840  // 4. no directory at all
841 
842  TQStringList dirList;
843 
844  if ( !TQDir::isRelativePath(dir) ) {
845  // complete path in url
846  dirList.append( dir );
847  }
848  else if ( !dir.isEmpty() && !d->cwd.isEmpty() ) {
849  // current directory
850  dirList.append( d->cwd + '/' + dir );
851  }
852  else if ( !url.file().isEmpty() ) {
853  // $PATH
854  dirList = TQStringList::split(KPATH_SEPARATOR,
855  TQString::fromLocal8Bit(::getenv("PATH")));
856 
857  TQStringList::Iterator it = dirList.begin();
858 
859  for ( ; it != dirList.end(); it++ )
860  (*it).append('/');
861  }
862 
863  // No hidden files unless the user types "."
864  bool no_hidden_files = url.file().at(0) != '.';
865 
866  // List files if needed
867  //
868  if ( !isListedURL( CTExe, dir, url.file(), no_hidden_files ) )
869  {
870  stop();
871  clear();
872 
873  setListedURL( CTExe, dir, url.file(), no_hidden_files );
874 
875  *match = listDirectories( dirList, url.file(), true, false, no_hidden_files );
876  }
877  else if ( !isRunning() ) {
878  *match = finished();
879  }
880  else {
881  if ( d->dirListThread )
882  setListedURL( CTExe, dir, url.file(), no_hidden_files );
883  *match = TQString::null;
884  }
885 
886  return true;
887 }
888 
891 // System Executables
892 //
893 
894 bool KURLCompletion::systemexeCompletion(const MyURL &url, TQString *match)
895 {
896  if ( url.protocol() != "file" )
897  return false;
898 
899  TQString dir = url.dir();
900 
901  dir = unescape( dir ); // remove escapes
902 
903  // Find directories to search for completions, either
904  //
905  // 1. complete path given in url
906  // 2. current directory (d->cwd)
907  // 3. $PATH
908  // 4. no directory at all
909 
910  TQStringList dirList;
911 
912  if ( !url.file().isEmpty() ) {
913  // $PATH
914  dirList = TQStringList::split(KPATH_SEPARATOR,
915  TQString::fromLocal8Bit(::getenv("PATH")));
916 
917  TQStringList::Iterator it = dirList.begin();
918 
919  for ( ; it != dirList.end(); it++ )
920  (*it).append('/');
921  }
922 
923  // No hidden files unless the user types "."
924  bool no_hidden_files = url.file().at(0) != '.';
925 
926  // List files if needed
927  //
928  if ( !isListedURL( CTExe, dir, url.file(), no_hidden_files ) )
929  {
930  stop();
931  clear();
932 
933  setListedURL( CTExe, dir, url.file(), no_hidden_files );
934 
935  *match = listDirectories( dirList, url.file(), true, false, no_hidden_files );
936  }
937  else if ( !isRunning() ) {
938  *match = finished();
939  }
940  else {
941  if ( d->dirListThread )
942  setListedURL( CTExe, dir, url.file(), no_hidden_files );
943  *match = TQString::null;
944  }
945 
946  return true;
947 }
948 
951 // Local files
952 //
953 
954 bool KURLCompletion::fileCompletion(const MyURL &url, TQString *match)
955 {
956  if ( url.protocol() != "file" )
957  return false;
958 
959  TQString dir = url.dir();
960 
961  if (url.url()[0] == '.')
962  {
963  if (url.url().length() == 1)
964  {
965  *match =
966  ( completionMode() == KGlobalSettings::CompletionMan )? "." : "..";
967  return true;
968  }
969  if (url.url().length() == 2 && url.url()[1]=='.')
970  {
971  *match="..";
972  return true;
973  }
974  }
975 
976  //kdDebug() << "fileCompletion " << url.url() << " dir=" << dir << endl;
977 
978  dir = unescape( dir ); // remove escapes
979 
980  // Find directories to search for completions, either
981  //
982  // 1. complete path given in url
983  // 2. current directory (d->cwd)
984  // 3. no directory at all
985 
986  TQStringList dirList;
987 
988  if ( !TQDir::isRelativePath(dir) ) {
989  // complete path in url
990  dirList.append( dir );
991  }
992  else if ( !d->cwd.isEmpty() ) {
993  // current directory
994  dirList.append( d->cwd + '/' + dir );
995  }
996 
997  // No hidden files unless the user types "."
998  bool no_hidden_files = ( url.file().at(0) != '.' );
999 
1000  // List files if needed
1001  //
1002  if ( !isListedURL( CTFile, dir, "", no_hidden_files ) )
1003  {
1004  stop();
1005  clear();
1006 
1007  setListedURL( CTFile, dir, "", no_hidden_files );
1008 
1009  // Append '/' to directories in Popup mode?
1010  bool append_slash = ( d->popup_append_slash
1011  && (completionMode() == KGlobalSettings::CompletionPopup ||
1012  completionMode() == KGlobalSettings::CompletionPopupAuto ) );
1013 
1014  bool only_dir = ( d->mode == DirCompletion );
1015 
1016  *match = listDirectories( dirList, "", false, only_dir, no_hidden_files,
1017  append_slash );
1018  }
1019  else if ( !isRunning() ) {
1020  *match = finished();
1021  }
1022  else {
1023  *match = TQString::null;
1024  }
1025 
1026  return true;
1027 }
1028 
1031 // URLs not handled elsewhere...
1032 //
1033 
1034 bool KURLCompletion::urlCompletion(const MyURL &url, TQString *match)
1035 {
1036  //kdDebug() << "urlCompletion: url = " << *url.kurl() << endl;
1037  if (d->onlyLocalProto && KProtocolInfo::protocolClass(url.protocol()) != ":local")
1038  return false;
1039 
1040  // Use d->cwd as base url in case url is not absolute
1041  KURL url_cwd = KURL::fromPathOrURL( d->cwd );
1042 
1043  // Create an URL with the directory to be listed
1044  KURL url_dir( url_cwd, url.kurl()->url() );
1045 
1046  // Don't try url completion if
1047  // 1. malformed url
1048  // 2. protocol that doesn't have listDir()
1049  // 3. there is no directory (e.g. "ftp://ftp.kd" shouldn't do anything)
1050  // 4. auto or popup completion mode depending on settings
1051 
1052  bool man_or_info = ( url_dir.protocol() == TQString("man")
1053  || url_dir.protocol() == TQString("info") );
1054 
1055  if ( !url_dir.isValid()
1056  || !KProtocolInfo::supportsListing( url_dir )
1057  || ( !man_or_info
1058  && ( url_dir.directory(false,false).isEmpty()
1059  || ( isAutoCompletion()
1060  && !d->url_auto_completion ) ) ) ) {
1061  return false;
1062  }
1063 
1064  url_dir.setFileName(""); // not really nesseccary, but clear the filename anyway...
1065 
1066  // Remove escapes
1067  TQString dir = url_dir.directory( false, false );
1068 
1069  dir = unescape( dir );
1070 
1071  url_dir.setPath( dir );
1072 
1073  // List files if needed
1074  //
1075  if ( !isListedURL( CTUrl, url_dir.prettyURL(), url.file() ) )
1076  {
1077  stop();
1078  clear();
1079 
1080  setListedURL( CTUrl, url_dir.prettyURL(), "" );
1081 
1082  TQValueList<KURL*> url_list;
1083  url_list.append( new KURL( url_dir ) );
1084 
1085  listURLs( url_list, "", false );
1086 
1087  *match = TQString::null;
1088  }
1089  else if ( !isRunning() ) {
1090  *match = finished();
1091  }
1092  else {
1093  *match = TQString::null;
1094  }
1095 
1096  return true;
1097 }
1098 
1101 // Directory and URL listing
1102 //
1103 
1104 /*
1105  * addMatches
1106  *
1107  * Called to add matches to KCompletion
1108  */
1109 void KURLCompletion::addMatches( const TQStringList &matches )
1110 {
1111  TQStringList::ConstIterator it = matches.begin();
1112  TQStringList::ConstIterator end = matches.end();
1113 
1114  if ( d->complete_url )
1115  for ( ; it != end; it++ )
1116  addItem( d->prepend + KURL::encode_string(*it));
1117  else
1118  for ( ; it != end; it++ )
1119  addItem( d->prepend + (*it));
1120 }
1121 
1122 /*
1123  * listDirectories
1124  *
1125  * List files starting with 'filter' in the given directories,
1126  * either using DirLister or listURLs()
1127  *
1128  * In either case, addMatches() is called with the listed
1129  * files, and eventually finished() when the listing is done
1130  *
1131  * Returns the match if available, or TQString::null if
1132  * DirLister timed out or using kio
1133  */
1134 TQString KURLCompletion::listDirectories(
1135  const TQStringList &dirList,
1136  const TQString &filter,
1137  bool only_exe,
1138  bool only_dir,
1139  bool no_hidden,
1140  bool append_slash_to_dir)
1141 {
1142  assert( !isRunning() );
1143 
1144  if ( !::getenv("KURLCOMPLETION_LOCAL_KIO") ) {
1145 
1146  //kdDebug() << "Listing (listDirectories): " << dirList << " filter=" << filter << " without KIO" << endl;
1147 
1148  // Don't use KIO
1149 
1150  if ( d->dirListThread )
1151  d->dirListThread->requestTermination();
1152 
1153  TQStringList dirs;
1154 
1155  for ( TQStringList::ConstIterator it = dirList.begin();
1156  it != dirList.end();
1157  ++it )
1158  {
1159  KURL url;
1160  url.setPath(*it);
1161  if ( kapp->authorizeURLAction( "list", KURL(), url ) )
1162  dirs.append( *it );
1163  }
1164 
1165  d->dirListThread = new DirectoryListThread( this, dirs, filter, only_exe, only_dir,
1166  no_hidden, append_slash_to_dir );
1167  d->dirListThread->start();
1168  d->dirListThread->wait( 200 );
1169  addMatches( d->dirListThread->matches() );
1170 
1171  return finished();
1172  }
1173  else {
1174 
1175  // Use KIO
1176  //kdDebug() << "Listing (listDirectories): " << dirList << " with KIO" << endl;
1177 
1178  TQValueList<KURL*> url_list;
1179 
1180  TQStringList::ConstIterator it = dirList.begin();
1181 
1182  for ( ; it != dirList.end(); it++ )
1183  url_list.append( new KURL(*it) );
1184 
1185  listURLs( url_list, filter, only_exe, no_hidden );
1186  // Will call addMatches() and finished()
1187 
1188  return TQString::null;
1189  }
1190 }
1191 
1192 /*
1193  * listURLs
1194  *
1195  * Use KIO to list the given urls
1196  *
1197  * addMatches() is called with the listed files
1198  * finished() is called when the listing is done
1199  */
1200 void KURLCompletion::listURLs(
1201  const TQValueList<KURL *> &urls,
1202  const TQString &filter,
1203  bool only_exe,
1204  bool no_hidden )
1205 {
1206  assert( d->list_urls.isEmpty() );
1207  assert( d->list_job == 0L );
1208 
1209  d->list_urls = urls;
1210  d->list_urls_filter = filter;
1211  d->list_urls_only_exe = only_exe;
1212  d->list_urls_no_hidden = no_hidden;
1213 
1214 // kdDebug() << "Listing URLs: " << urls[0]->prettyURL() << ",..." << endl;
1215 
1216  // Start it off by calling slotIOFinished
1217  //
1218  // This will start a new list job as long as there
1219  // are urls in d->list_urls
1220  //
1221  slotIOFinished(0L);
1222 }
1223 
1224 /*
1225  * slotEntries
1226  *
1227  * Receive files listed by KIO and call addMatches()
1228  */
1229 void KURLCompletion::slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
1230 {
1231  TQStringList matches;
1232 
1233  KIO::UDSEntryListConstIterator it = entries.begin();
1234  KIO::UDSEntryListConstIterator end = entries.end();
1235 
1236  TQString filter = d->list_urls_filter;
1237 
1238  int filter_len = filter.length();
1239 
1240  // Iterate over all files
1241  //
1242  for (; it != end; ++it) {
1243  TQString name;
1244  TQString url;
1245  bool is_exe = false;
1246  bool is_dir = false;
1247 
1248  KIO::UDSEntry e = *it;
1249  KIO::UDSEntry::ConstIterator it_2 = e.begin();
1250 
1251  for( ; it_2 != e.end(); it_2++ ) {
1252  switch ( (*it_2).m_uds ) {
1253  case KIO::UDS_NAME:
1254  name = (*it_2).m_str;
1255  break;
1256  case KIO::UDS_ACCESS:
1257  is_exe = ((*it_2).m_long & MODE_EXE) != 0;
1258  break;
1259  case KIO::UDS_FILE_TYPE:
1260  is_dir = ((*it_2).m_long & S_IFDIR) != 0;
1261  break;
1262  case KIO::UDS_URL:
1263  url = (*it_2).m_str;
1264  break;
1265  }
1266  }
1267 
1268  if (!url.isEmpty()) {
1269  // kdDebug() << "KURLCompletion::slotEntries url: " << url << endl;
1270  name = KURL(url).fileName();
1271  }
1272 
1273  // kdDebug() << "KURLCompletion::slotEntries name: " << name << endl;
1274 
1275  if ( name[0] == '.' &&
1276  ( d->list_urls_no_hidden ||
1277  name.length() == 1 ||
1278  ( name.length() == 2 && name[1] == '.' ) ) )
1279  continue;
1280 
1281  if ( d->mode == DirCompletion && !is_dir )
1282  continue;
1283 
1284  if ( filter_len == 0 || name.left(filter_len) == filter ) {
1285  if ( is_dir )
1286  name.append( '/' );
1287 
1288  if ( is_exe || !d->list_urls_only_exe )
1289  matches.append( name );
1290  }
1291  }
1292 
1293  addMatches( matches );
1294 }
1295 
1296 /*
1297  * slotIOFinished
1298  *
1299  * Called when a KIO job is finished.
1300  *
1301  * Start a new list job if there are still urls in
1302  * d->list_urls, otherwise call finished()
1303  */
1304 void KURLCompletion::slotIOFinished( KIO::Job * job )
1305 {
1306 // kdDebug() << "slotIOFinished() " << endl;
1307 
1308  assert( job == d->list_job );
1309 
1310  if ( d->list_urls.isEmpty() ) {
1311 
1312  d->list_job = 0L;
1313 
1314  finished(); // will call KCompletion::makeCompletion()
1315 
1316  }
1317  else {
1318 
1319  KURL *kurl = d->list_urls.first();
1320 
1321  d->list_urls.remove( kurl );
1322 
1323 // kdDebug() << "Start KIO: " << kurl->prettyURL() << endl;
1324 
1325  d->list_job = KIO::listDir( *kurl, false );
1326  d->list_job->addMetaData("no-auth-prompt", "true");
1327 
1328  assert( d->list_job );
1329 
1330  connect( d->list_job,
1331  TQT_SIGNAL(result(KIO::Job*)),
1332  TQT_SLOT(slotIOFinished(KIO::Job*)) );
1333 
1334  connect( d->list_job,
1335  TQT_SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList&)),
1336  TQT_SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList&)) );
1337 
1338  delete kurl;
1339  }
1340 }
1341 
1344 
1345 /*
1346  * postProcessMatch, postProcessMatches
1347  *
1348  * Called by KCompletion before emitting match() and matches()
1349  *
1350  * Append '/' to directories for file completion. This is
1351  * done here to avoid stat()'ing a lot of files
1352  */
1353 void KURLCompletion::postProcessMatch( TQString *match ) const
1354 {
1355 // kdDebug() << "KURLCompletion::postProcess: " << *match << endl;
1356 
1357  if ( !match->isEmpty() ) {
1358 
1359  // Add '/' to directories in file completion mode
1360  // unless it has already been done
1361  if ( d->last_compl_type == CTFile )
1362  adjustMatch( *match );
1363  }
1364 }
1365 
1366 void KURLCompletion::adjustMatch( TQString& match ) const
1367 {
1368  if ( match.at( match.length()-1 ) != '/' )
1369  {
1370  TQString copy;
1371 
1372  if ( match.startsWith( TQString("file:") ) )
1373  copy = KURL(match).path();
1374  else
1375  copy = match;
1376 
1377  expandTilde( copy );
1378  expandEnv( copy );
1379  if ( TQDir::isRelativePath(copy) )
1380  copy.prepend( d->cwd + '/' );
1381 
1382 // kdDebug() << "postProcess: stating " << copy << endl;
1383 
1384  KDE_struct_stat sbuff;
1385 
1386  TQCString file = TQFile::encodeName( copy );
1387 
1388  if ( KDE_stat( (const char*)file, &sbuff ) == 0 ) {
1389  if ( S_ISDIR ( sbuff.st_mode ) )
1390  match.append( '/' );
1391  }
1392  else {
1393  kdDebug() << "Could not stat file " << copy << endl;
1394  }
1395  }
1396 }
1397 
1398 void KURLCompletion::postProcessMatches( TQStringList * matches ) const
1399 {
1400  if ( !matches->isEmpty() && d->last_compl_type == CTFile ) {
1401  TQStringList::Iterator it = matches->begin();
1402  for (; it != matches->end(); ++it ) {
1403  adjustMatch( (*it) );
1404  }
1405  }
1406 }
1407 
1408 void KURLCompletion::postProcessMatches( KCompletionMatches * matches ) const
1409 {
1410  if ( !matches->isEmpty() && d->last_compl_type == CTFile ) {
1411  KCompletionMatches::Iterator it = matches->begin();
1412  for (; it != matches->end(); ++it ) {
1413  adjustMatch( (*it).value() );
1414  }
1415  }
1416 }
1417 
1418 void KURLCompletion::customEvent(TQCustomEvent *e)
1419 {
1420  if ( e->type() == CompletionMatchEvent::uniqueType() ) {
1421 
1422  CompletionMatchEvent *event = static_cast<CompletionMatchEvent *>( e );
1423 
1424  event->completionThread()->wait();
1425 
1426  if ( !isListedURL( CTUser ) ) {
1427  stop();
1428  clear();
1429  addMatches( event->completionThread()->matches() );
1430  }
1431 
1432  setListedURL( CTUser );
1433 
1434  if ( d->userListThread == event->completionThread() )
1435  d->userListThread = 0;
1436 
1437  if ( d->dirListThread == event->completionThread() )
1438  d->dirListThread = 0;
1439 
1440  delete event->completionThread();
1441  }
1442 }
1443 
1444 // static
1445 TQString KURLCompletion::replacedPath( const TQString& text, bool replaceHome, bool replaceEnv )
1446 {
1447  if ( text.isEmpty() )
1448  return text;
1449 
1450  MyURL url( text, TQString::null ); // no need to replace something of our current cwd
1451  if ( !url.kurl()->isLocalFile() )
1452  return text;
1453 
1454  url.filter( replaceHome, replaceEnv );
1455  return url.dir() + url.file();
1456 }
1457 
1458 
1459 TQString KURLCompletion::replacedPath( const TQString& text )
1460 {
1461  return replacedPath( text, d->replace_home, d->replace_env );
1462 }
1463 
1466 // Static functions
1467 
1468 /*
1469  * expandEnv
1470  *
1471  * Expand environment variables in text. Escaped '$' are ignored.
1472  * Return true if expansion was made.
1473  */
1474 static bool expandEnv( TQString &text )
1475 {
1476  // Find all environment variables beginning with '$'
1477  //
1478  int pos = 0;
1479 
1480  bool expanded = false;
1481 
1482  while ( (pos = text.find('$', pos)) != -1 ) {
1483 
1484  // Skip escaped '$'
1485  //
1486  if ( text[pos-1] == '\\' ) {
1487  pos++;
1488  }
1489  // Variable found => expand
1490  //
1491  else {
1492  // Find the end of the variable = next '/' or ' '
1493  //
1494  int pos2 = text.find( ' ', pos+1 );
1495  int pos_tmp = text.find( '/', pos+1 );
1496 
1497  if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
1498  pos2 = pos_tmp;
1499 
1500  if ( pos2 == -1 )
1501  pos2 = text.length();
1502 
1503  // Replace if the variable is terminated by '/' or ' '
1504  // and defined
1505  //
1506  if ( pos2 >= 0 ) {
1507  int len = pos2 - pos;
1508  TQString key = text.mid( pos+1, len-1);
1509  TQString value =
1510  TQString::fromLocal8Bit( ::getenv(key.local8Bit()) );
1511 
1512  if ( !value.isEmpty() ) {
1513  expanded = true;
1514  text.replace( pos, len, value );
1515  pos = pos + value.length();
1516  }
1517  else {
1518  pos = pos2;
1519  }
1520  }
1521  }
1522  }
1523 
1524  return expanded;
1525 }
1526 
1527 /*
1528  * expandTilde
1529  *
1530  * Replace "~user" with the users home directory
1531  * Return true if expansion was made.
1532  */
1533 static bool expandTilde(TQString &text)
1534 {
1535  if ( text[0] != '~' )
1536  return false;
1537 
1538  bool expanded = false;
1539 
1540  // Find the end of the user name = next '/' or ' '
1541  //
1542  int pos2 = text.find( ' ', 1 );
1543  int pos_tmp = text.find( '/', 1 );
1544 
1545  if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
1546  pos2 = pos_tmp;
1547 
1548  if ( pos2 == -1 )
1549  pos2 = text.length();
1550 
1551  // Replace ~user if the user name is terminated by '/' or ' '
1552  //
1553  if ( pos2 >= 0 ) {
1554 
1555  TQString user = text.mid( 1, pos2-1 );
1556  TQString dir;
1557 
1558  // A single ~ is replaced with $HOME
1559  //
1560  if ( user.isEmpty() ) {
1561  dir = TQDir::homeDirPath();
1562  }
1563  // ~user is replaced with the dir from passwd
1564  //
1565  else {
1566  struct passwd *pw = ::getpwnam( user.local8Bit() );
1567 
1568  if ( pw )
1569  dir = TQFile::decodeName( pw->pw_dir );
1570 
1571  ::endpwent();
1572  }
1573 
1574  if ( !dir.isEmpty() ) {
1575  expanded = true;
1576  text.replace(0, pos2, dir);
1577  }
1578  }
1579 
1580  return expanded;
1581 }
1582 
1583 /*
1584  * unescape
1585  *
1586  * Remove escapes and return the result in a new string
1587  *
1588  */
1589 static TQString unescape(const TQString &text)
1590 {
1591  TQString result;
1592 
1593  for (uint pos = 0; pos < text.length(); pos++)
1594  if ( text[pos] != '\\' )
1595  result.insert( result.length(), text[pos] );
1596 
1597  return result;
1598 }
1599 
1600 void KURLCompletion::virtual_hook( int id, void* data )
1601 { KCompletion::virtual_hook( id, data ); }
1602 
1603 #include "kurlcompletion.moc"
1604 

kio/kio

Skip menu "kio/kio"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kio/kio

Skip menu "kio/kio"
  • arts
  • dcop
  • dnssd
  • interfaces
  •     interface
  •     library
  •   kspeech
  •   ktexteditor
  • kabc
  • kate
  • kcmshell
  • kdecore
  • kded
  • kdefx
  • kdeprint
  • kdesu
  • kdeui
  • kdoctools
  • khtml
  • kimgio
  • kinit
  • kio
  •   bookmarks
  •   httpfilter
  •   kfile
  •   kio
  •   kioexec
  •   kpasswdserver
  •   kssl
  • kioslave
  •   http
  • kjs
  • kmdi
  •   kmdi
  • knewstuff
  • kparts
  • krandr
  • kresources
  • kspell2
  • kunittest
  • kutils
  • kwallet
  • libkmid
  • libkscreensaver
Generated for kio/kio by doxygen 1.8.3.1
This website is maintained by Timothy Pearson.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. |