• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.8.5 API Reference
  • KDE Home
  • Contact Us
 

KIO

  • kio
  • kio
kurlcompletion.cpp
Go to the documentation of this file.
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 "kurlcompletion.h"
27 
28 #include <config.h>
29 
30 #include <stdlib.h>
31 #include <assert.h>
32 #include <limits.h>
33 
34 #include <QtCore/QCoreApplication>
35 #include <QtCore/QMutableStringListIterator>
36 #include <QtCore/QRegExp>
37 #include <QtCore/QTimer>
38 #include <QtCore/QDir>
39 #include <QtCore/QDirIterator>
40 #include <QtCore/QFile>
41 #include <QtCore/QTextIStream>
42 #include <QtCore/QThread>
43 #include <QtGui/QActionEvent>
44 
45 #include <kauthorized.h>
46 #include <kdebug.h>
47 #include <kurl.h>
48 #include <kio/job.h>
49 #include <kprotocolmanager.h>
50 #include <kconfig.h>
51 #include <kglobal.h>
52 #include <kglobalsettings.h>
53 #include <kde_file.h>
54 
55 #include <sys/types.h>
56 #include <dirent.h>
57 #include <unistd.h>
58 #include <sys/stat.h>
59 #include <pwd.h>
60 #include <time.h>
61 #include <sys/param.h>
62 #include <kconfiggroup.h>
63 
64 #ifdef Q_WS_WIN
65 #include <kkernel_win.h>
66 #endif
67 
68 static bool expandTilde(QString&);
69 static bool expandEnv(QString&);
70 
71 static QString unescape(const QString& text);
72 
73 // Permission mask for files that are executable by
74 // user, group or other
75 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
76 
77 // Constants for types of completion
78 enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
79 
80 class CompletionThread;
81 
84 // KUrlCompletionPrivate
85 //
86 class KUrlCompletionPrivate
87 {
88 public:
89  KUrlCompletionPrivate(KUrlCompletion* parent)
90  : q(parent),
91  url_auto_completion(true),
92  userListThread(0),
93  dirListThread(0) {
94  }
95 
96  ~KUrlCompletionPrivate();
97 
98  void _k_slotEntries(KIO::Job*, const KIO::UDSEntryList&);
99  void _k_slotIOFinished(KJob*);
100 
101  class MyURL;
102  bool userCompletion(const MyURL& url, QString* match);
103  bool envCompletion(const MyURL& url, QString* match);
104  bool exeCompletion(const MyURL& url, QString* match);
105  bool fileCompletion(const MyURL& url, QString* match);
106  bool urlCompletion(const MyURL& url, QString* match);
107 
108  bool isAutoCompletion();
109 
110  // List the next dir in m_dirs
111  QString listDirectories(const QStringList&,
112  const QString&,
113  bool only_exe = false,
114  bool only_dir = false,
115  bool no_hidden = false,
116  bool stat_files = true);
117 
118  void listUrls(const QList<KUrl> &urls,
119  const QString& filter = QString(),
120  bool only_exe = false,
121  bool no_hidden = false);
122 
123  void addMatches(const QStringList&);
124  QString finished();
125 
126  void init();
127 
128  void setListedUrl(int compl_type /* enum ComplType */,
129  const QString& dir = QString(),
130  const QString& filter = QString(),
131  bool no_hidden = false);
132 
133  bool isListedUrl(int compl_type /* enum ComplType */,
134  const QString& dir = QString(),
135  const QString& filter = QString(),
136  bool no_hidden = false);
137 
138  KUrlCompletion* q;
139  QList<KUrl> list_urls;
140 
141  bool onlyLocalProto;
142 
143  // urlCompletion() in Auto/Popup mode?
144  bool url_auto_completion;
145 
146  // Append '/' to directories in Popup mode?
147  // Doing that stat's all files and is slower
148  bool popup_append_slash;
149 
150  // Keep track of currently listed files to avoid reading them again
151  QString last_path_listed;
152  QString last_file_listed;
153  QString last_prepend;
154  int last_compl_type;
155  int last_no_hidden;
156 
157  QString cwd; // "current directory" = base dir for completion
158 
159  KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion
160  bool replace_env;
161  bool replace_home;
162  bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
163 
164  KIO::ListJob* list_job; // kio job to list directories
165 
166  QString prepend; // text to prepend to listed items
167  QString compl_text; // text to pass on to KCompletion
168 
169  // Filters for files read with kio
170  bool list_urls_only_exe; // true = only list executables
171  bool list_urls_no_hidden;
172  QString list_urls_filter; // filter for listed files
173 
174  CompletionThread* userListThread;
175  CompletionThread* dirListThread;
176 };
177 
183 class CompletionMatchEvent : public QEvent
184 {
185 public:
186  CompletionMatchEvent(CompletionThread* thread) :
187  QEvent(uniqueType()),
188  m_completionThread(thread)
189  {}
190 
191  CompletionThread* completionThread() const {
192  return m_completionThread;
193  }
194  static Type uniqueType() {
195  return Type(User + 61080);
196  }
197 
198 private:
199  CompletionThread* m_completionThread;
200 };
201 
202 class CompletionThread : public QThread
203 {
204 protected:
205  CompletionThread(KUrlCompletionPrivate* receiver) :
206  QThread(),
207  m_prepend(receiver->prepend),
208  m_complete_url(receiver->complete_url),
209  m_receiver(receiver),
210  m_terminationRequested(false)
211  {}
212 
213 public:
214  void requestTermination() {
215  m_terminationRequested = true;
216  }
217  QStringList matches() const {
218  return m_matches;
219  }
220 
221 protected:
222  void addMatch(const QString& match) {
223  m_matches.append(match);
224  }
225  bool terminationRequested() const {
226  return m_terminationRequested;
227  }
228  void done() {
229  if (!m_terminationRequested)
230  qApp->postEvent(m_receiver->q, new CompletionMatchEvent(this));
231  else
232  deleteLater();
233  }
234 
235  const QString m_prepend;
236  const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
237 
238 private:
239  KUrlCompletionPrivate* m_receiver;
240  QStringList m_matches;
241  bool m_terminationRequested;
242 };
243 
249 class UserListThread : public CompletionThread
250 {
251 public:
252  UserListThread(KUrlCompletionPrivate* receiver) :
253  CompletionThread(receiver)
254  {}
255 
256 protected:
257  virtual void run() {
258  static const QChar tilde = '~';
259 
260  // we don't need to handle prepend here, right? ~user is always at pos 0
261  assert(m_prepend.isEmpty());
262  struct passwd* pw;
263  while ((pw = ::getpwent()) && !terminationRequested())
264  addMatch(tilde + QString::fromLocal8Bit(pw->pw_name));
265 
266  ::endpwent();
267 
268  addMatch(QString(tilde));
269 
270  done();
271  }
272 };
273 
274 class DirectoryListThread : public CompletionThread
275 {
276 public:
277  DirectoryListThread(KUrlCompletionPrivate* receiver,
278  const QStringList& dirList,
279  const QString& filter,
280  bool onlyExe,
281  bool onlyDir,
282  bool noHidden,
283  bool appendSlashToDir) :
284  CompletionThread(receiver),
285  m_dirList(dirList),
286  m_filter(filter),
287  m_onlyExe(onlyExe),
288  m_onlyDir(onlyDir),
289  m_noHidden(noHidden),
290  m_appendSlashToDir(appendSlashToDir)
291  {}
292 
293  virtual void run();
294 
295 private:
296  QStringList m_dirList;
297  QString m_filter;
298  bool m_onlyExe;
299  bool m_onlyDir;
300  bool m_noHidden;
301  bool m_appendSlashToDir;
302 };
303 
304 void DirectoryListThread::run()
305 {
306  // Thread safety notes:
307  //
308  // There very possibly may be thread safety issues here, but I've done a check
309  // of all of the things that would seem to be problematic. Here are a few
310  // things that I have checked to be safe here (some used indirectly):
311  //
312  // QDir::currentPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName()
313  // QString::fromLocal8Bit(), QString::toLocal8Bit(), QTextCodec::codecForLocale()
314  //
315  // Also see (for POSIX functions):
316  // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
317 
318  // kDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size();
319 
320  QStringList::ConstIterator end = m_dirList.constEnd();
321  for (QStringList::ConstIterator it = m_dirList.constBegin();
322  it != end && !terminationRequested();
323  ++it) {
324  // kDebug() << "Scanning directory" << *it;
325 
326  // A trick from KIO that helps performance by a little bit:
327  // chdir to the directory so we won't have to deal with full paths
328  // with stat()
329 
330  QString path = QDir::currentPath();
331  QDir::setCurrent(*it);
332 
333  QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
334 
335  if (m_onlyExe)
336  iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
337  else if (m_onlyDir)
338  iterator_filter |= QDir::Dirs;
339  else
340  iterator_filter |= (QDir::Dirs | QDir::Files);
341 
342  QDirIterator current_dir_iterator(*it, iterator_filter);
343 
344  while (current_dir_iterator.hasNext()) {
345  current_dir_iterator.next();
346 
347  QFileInfo file_info = current_dir_iterator.fileInfo();
348  const QString file_name = file_info.fileName();
349 
350  //kDebug() << "Found" << file_name;
351 
352  if (m_filter.isEmpty() || file_name.startsWith(m_filter)) {
353 
354  QString toAppend = m_complete_url ? QUrl::toPercentEncoding(file_name) : file_name;
355  // Add '/' to directories
356  if (m_appendSlashToDir && file_info.isDir())
357  toAppend.append(QLatin1Char('/'));
358 
359  addMatch(m_prepend + toAppend);
360  }
361  }
362 
363  // chdir to the original directory
364  QDir::setCurrent(path);
365  }
366 
367  done();
368 }
369 
370 KUrlCompletionPrivate::~KUrlCompletionPrivate()
371 {
372  if (userListThread)
373  userListThread->requestTermination();
374  if (dirListThread)
375  dirListThread->requestTermination();
376 }
377 
380 // MyURL - wrapper for KUrl with some different functionality
381 //
382 
383 class KUrlCompletionPrivate::MyURL
384 {
385 public:
386  MyURL(const QString& url, const QString& cwd);
387  MyURL(const MyURL& url);
388  ~MyURL();
389 
390  KUrl kurl() const {
391  return m_kurl;
392  }
393 
394  QString protocol() const {
395  return m_kurl.protocol();
396  }
397  // The directory with a trailing '/'
398  QString dir() const {
399  return m_kurl.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
400  }
401  QString file() const {
402  return m_kurl.fileName(KUrl::ObeyTrailingSlash);
403  }
404 
405  // The initial, unparsed, url, as a string.
406  QString url() const {
407  return m_url;
408  }
409 
410  // Is the initial string a URL, or just a path (whether absolute or relative)
411  bool isURL() const {
412  return m_isURL;
413  }
414 
415  void filter(bool replace_user_dir, bool replace_env);
416 
417 private:
418  void init(const QString& url, const QString& cwd);
419 
420  KUrl m_kurl;
421  QString m_url;
422  bool m_isURL;
423 };
424 
425 KUrlCompletionPrivate::MyURL::MyURL(const QString& _url, const QString& cwd)
426 {
427  init(_url, cwd);
428 }
429 
430 KUrlCompletionPrivate::MyURL::MyURL(const MyURL& _url)
431  : m_kurl(_url.m_kurl)
432 {
433  m_url = _url.m_url;
434  m_isURL = _url.m_isURL;
435 }
436 
437 void KUrlCompletionPrivate::MyURL::init(const QString& _url, const QString& cwd)
438 {
439  // Save the original text
440  m_url = _url;
441 
442  // Non-const copy
443  QString url_copy = _url;
444 
445  // Special shortcuts for "man:" and "info:"
446  if (url_copy.startsWith(QLatin1Char('#'))) {
447  if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#'))
448  url_copy.replace(0, 2, QLatin1String("info:"));
449  else
450  url_copy.replace(0, 1, QLatin1String("man:"));
451  }
452 
453  // Look for a protocol in 'url'
454  QRegExp protocol_regex = QRegExp("^(?![A-Za-z]:)[^/\\s\\\\]*:");
455 
456  // Assume "file:" or whatever is given by 'cwd' if there is
457  // no protocol. (KUrl does this only for absolute paths)
458  if (protocol_regex.indexIn(url_copy) == 0) {
459  m_kurl = KUrl(url_copy);
460  m_isURL = true;
461  } else { // relative path or ~ or $something
462  m_isURL = false;
463  if (!QDir::isRelativePath(url_copy) ||
464  url_copy.startsWith(QLatin1Char('~')) ||
465  url_copy.startsWith(QLatin1Char('$'))) {
466  m_kurl = KUrl();
467  m_kurl.setPath(url_copy);
468  } else {
469  if (cwd.isEmpty()) {
470  m_kurl = KUrl(url_copy);
471  } else {
472  m_kurl = KUrl(cwd);
473  m_kurl.addPath(url_copy);
474  }
475  }
476  }
477 }
478 
479 KUrlCompletionPrivate::MyURL::~MyURL()
480 {
481 }
482 
483 void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env)
484 {
485  QString d = dir() + file();
486  if (replace_user_dir) expandTilde(d);
487  if (replace_env) expandEnv(d);
488  m_kurl.setPath(d);
489 }
490 
493 // KUrlCompletion
494 //
495 
496 KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this))
497 {
498  d->init();
499 }
500 
501 
502 KUrlCompletion::KUrlCompletion(Mode _mode)
503  : KCompletion(),
504  d(new KUrlCompletionPrivate(this))
505 {
506  d->init();
507  setMode(_mode);
508 }
509 
510 KUrlCompletion::~KUrlCompletion()
511 {
512  stop();
513  delete d;
514 }
515 
516 
517 void KUrlCompletionPrivate::init()
518 {
519  cwd = QDir::homePath();
520 
521  replace_home = true;
522  replace_env = true;
523  last_no_hidden = false;
524  last_compl_type = 0;
525  list_job = 0L;
526  mode = KUrlCompletion::FileCompletion;
527 
528  // Read settings
529  KConfigGroup cg(KGlobal::config(), "URLCompletion");
530 
531  url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
532  popup_append_slash = cg.readEntry("popupAppendSlash", true);
533  onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
534 
535  q->setIgnoreCase(true);
536 }
537 
538 void KUrlCompletion::setDir(const QString& _dir)
539 {
540  d->cwd = _dir;
541 }
542 
543 QString KUrlCompletion::dir() const
544 {
545  return d->cwd;
546 }
547 
548 KUrlCompletion::Mode KUrlCompletion::mode() const
549 {
550  return d->mode;
551 }
552 
553 void KUrlCompletion::setMode(Mode _mode)
554 {
555  d->mode = _mode;
556 }
557 
558 bool KUrlCompletion::replaceEnv() const
559 {
560  return d->replace_env;
561 }
562 
563 void KUrlCompletion::setReplaceEnv(bool replace)
564 {
565  d->replace_env = replace;
566 }
567 
568 bool KUrlCompletion::replaceHome() const
569 {
570  return d->replace_home;
571 }
572 
573 void KUrlCompletion::setReplaceHome(bool replace)
574 {
575  d->replace_home = replace;
576 }
577 
578 /*
579  * makeCompletion()
580  *
581  * Entry point for file name completion
582  */
583 QString KUrlCompletion::makeCompletion(const QString& text)
584 {
585  //kDebug() << text << "d->cwd=" << d->cwd;
586 
587  KUrlCompletionPrivate::MyURL url(text, d->cwd);
588 
589  d->compl_text = text;
590 
591  // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
592  // This is what gets prepended to the directory-listing matches.
593  int toRemove = url.file().length() - url.kurl().query().length();
594  if (url.kurl().hasRef())
595  toRemove += url.kurl().ref().length() + 1;
596  d->prepend = text.left(text.length() - toRemove);
597  d->complete_url = url.isURL();
598 
599  QString aMatch;
600 
601  // Environment variables
602  //
603  if (d->replace_env && d->envCompletion(url, &aMatch))
604  return aMatch;
605 
606  // User directories
607  //
608  if (d->replace_home && d->userCompletion(url, &aMatch))
609  return aMatch;
610 
611  // Replace user directories and variables
612  url.filter(d->replace_home, d->replace_env);
613 
614  //kDebug() << "Filtered: proto=" << url.protocol()
615  // << ", dir=" << url.dir()
616  // << ", file=" << url.file()
617  // << ", kurl url=" << *url.kurl();
618 
619  if (d->mode == ExeCompletion) {
620  // Executables
621  //
622  if (d->exeCompletion(url, &aMatch))
623  return aMatch;
624 
625  // KRun can run "man:" and "info:" etc. so why not treat them
626  // as executables...
627 
628  if (d->urlCompletion(url, &aMatch))
629  return aMatch;
630  } else {
631  // Local files, directories
632  //
633  if (d->fileCompletion(url, &aMatch))
634  return aMatch;
635 
636  // All other...
637  //
638  if (d->urlCompletion(url, &aMatch))
639  return aMatch;
640  }
641 
642  d->setListedUrl(CTNone);
643  stop();
644 
645  return QString();
646 }
647 
648 /*
649  * finished
650  *
651  * Go on and call KCompletion.
652  * Called when all matches have been added
653  */
654 QString KUrlCompletionPrivate::finished()
655 {
656  if (last_compl_type == CTInfo)
657  return q->KCompletion::makeCompletion(compl_text.toLower());
658  else
659  return q->KCompletion::makeCompletion(compl_text);
660 }
661 
662 /*
663  * isRunning
664  *
665  * Return true if either a KIO job or the DirLister
666  * is running
667  */
668 bool KUrlCompletion::isRunning() const
669 {
670  return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
671 }
672 
673 /*
674  * stop
675  *
676  * Stop and delete a running KIO job or the DirLister
677  */
678 void KUrlCompletion::stop()
679 {
680  if (d->list_job) {
681  d->list_job->kill();
682  d->list_job = 0L;
683  }
684 
685  if (d->dirListThread) {
686  d->dirListThread->requestTermination();
687  d->dirListThread = 0;
688  }
689 }
690 
691 /*
692  * Keep track of the last listed directory
693  */
694 void KUrlCompletionPrivate::setListedUrl(int complType,
695  const QString& directory,
696  const QString& filter,
697  bool no_hidden)
698 {
699  last_compl_type = complType;
700  last_path_listed = directory;
701  last_file_listed = filter;
702  last_no_hidden = (int) no_hidden;
703  last_prepend = prepend;
704 }
705 
706 bool KUrlCompletionPrivate::isListedUrl(int complType,
707  const QString& directory,
708  const QString& filter,
709  bool no_hidden)
710 {
711  return last_compl_type == complType
712  && (last_path_listed == directory
713  || (directory.isEmpty() && last_path_listed.isEmpty()))
714  && (filter.startsWith (last_file_listed)
715  || (filter.isEmpty() && last_file_listed.isEmpty()))
716  && last_no_hidden == (int) no_hidden
717  && last_prepend == prepend; // e.g. relative path vs absolute
718 }
719 
720 /*
721  * isAutoCompletion
722  *
723  * Returns true if completion mode is Auto or Popup
724  */
725 bool KUrlCompletionPrivate::isAutoCompletion()
726 {
727  return q->completionMode() == KGlobalSettings::CompletionAuto
728  || q->completionMode() == KGlobalSettings::CompletionPopup
729  || q->completionMode() == KGlobalSettings::CompletionMan
730  || q->completionMode() == KGlobalSettings::CompletionPopupAuto;
731 }
734 // User directories
735 //
736 
737 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
738 {
739  if (url.protocol() != QLatin1String("file")
740  || !url.dir().isEmpty()
741  || !url.file().startsWith(QLatin1Char('~')))
742  return false;
743 
744  if (!isListedUrl(CTUser)) {
745  q->stop();
746  q->clear();
747 
748  if (!userListThread) {
749  userListThread = new UserListThread(this);
750  userListThread->start();
751 
752  // If the thread finishes quickly make sure that the results
753  // are added to the first matching case.
754 
755  userListThread->wait(200);
756  const QStringList l = userListThread->matches();
757  addMatches(l);
758  }
759  }
760  *pMatch = finished();
761  return true;
762 }
763 
766 // Environment variables
767 //
768 
769 #ifndef Q_OS_WIN
770 extern char** environ; // Array of environment variables
771 #endif
772 
773 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
774 {
775  if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$'))
776  return false;
777 
778  if (!isListedUrl(CTEnv)) {
779  q->stop();
780  q->clear();
781 
782  char** env = environ;
783 
784  QString dollar = QLatin1String("$");
785 
786  QStringList l;
787 
788  while (*env) {
789  QString s = QString::fromLocal8Bit(*env);
790 
791  int pos = s.indexOf(QLatin1Char('='));
792 
793  if (pos == -1)
794  pos = s.length();
795 
796  if (pos > 0)
797  l.append(prepend + dollar + s.left(pos));
798 
799  env++;
800  }
801 
802  addMatches(l);
803  }
804 
805  setListedUrl(CTEnv);
806 
807  *pMatch = finished();
808  return true;
809 }
810 
813 // Executables
814 //
815 
816 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
817 {
818  if (url.protocol() != QLatin1String("file"))
819  return false;
820 
821  QString directory = unescape(url.dir()); // remove escapes
822 
823  // Find directories to search for completions, either
824  //
825  // 1. complete path given in url
826  // 2. current directory (d->cwd)
827  // 3. $PATH
828  // 4. no directory at all
829 
830  QStringList dirList;
831 
832  if (!url.file().isEmpty()) {
833  // $PATH
834  dirList = QString::fromLocal8Bit(qgetenv("PATH")).split(
835  KPATH_SEPARATOR, QString::SkipEmptyParts);
836 
837  QStringList::Iterator it = dirList.begin();
838 
839  for (; it != dirList.end(); ++it)
840  it->append(QLatin1Char('/'));
841  } else if (!QDir::isRelativePath(directory)) {
842  // complete path in url
843  dirList.append(directory);
844  } else if (!directory.isEmpty() && !cwd.isEmpty()) {
845  // current directory
846  dirList.append(cwd + QLatin1Char('/') + directory);
847  }
848 
849  // No hidden files unless the user types "."
850  bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
851 
852  // List files if needed
853  //
854  if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) {
855  q->stop();
856  q->clear();
857 
858  setListedUrl(CTExe, directory, url.file(), no_hidden_files);
859 
860  *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files);
861  } else if (!q->isRunning()) {
862  *pMatch = finished();
863  } else {
864  if (dirListThread)
865  setListedUrl(CTExe, directory, url.file(), no_hidden_files);
866  pMatch->clear();
867  }
868 
869  return true;
870 }
871 
874 // Local files
875 //
876 
877 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
878 {
879  if (url.protocol() != QLatin1String("file"))
880  return false;
881 
882  QString directory = unescape(url.dir());
883 
884  if (url.url().length() && url.url().at(0) == QLatin1Char('.')) {
885  if (url.url().length() == 1) {
886  *pMatch = (q->completionMode() == KGlobalSettings::CompletionMan) ?
887  QLatin1String(".") :
888  QLatin1String("..");
889  return true;
890  } else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.')) {
891  *pMatch = QLatin1String("..");
892  return true;
893  }
894  }
895 
896  //kDebug() << "fileCompletion" << url << "dir=" << dir;
897 
898  // Find directories to search for completions, either
899  //
900  // 1. complete path given in url
901  // 2. current directory (d->cwd)
902  // 3. no directory at all
903 
904  QStringList dirList;
905 
906  if (!QDir::isRelativePath(directory)) {
907  // complete path in url
908  dirList.append(directory);
909  } else if (!cwd.isEmpty()) {
910  // current directory
911  QString dirToAdd = cwd;
912  if (!directory.isEmpty()) {
913  if (!cwd.endsWith('/'))
914  dirToAdd.append(QLatin1Char('/'));
915  dirToAdd.append(directory);
916  }
917  dirList.append(dirToAdd);
918  }
919 
920  // No hidden files unless the user types "."
921  bool no_hidden_files = !url.file().startsWith(QLatin1Char('.'));
922 
923  // List files if needed
924  //
925  if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) {
926  q->stop();
927  q->clear();
928 
929  setListedUrl(CTFile, directory, QString(), no_hidden_files);
930 
931  // Append '/' to directories in Popup mode?
932  bool append_slash = (popup_append_slash
933  && (q->completionMode() == KGlobalSettings::CompletionPopup ||
934  q->completionMode() == KGlobalSettings::CompletionPopupAuto));
935 
936  bool only_dir = (mode == KUrlCompletion::DirCompletion);
937 
938  *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files,
939  append_slash);
940  } else if (!q->isRunning()) {
941  *pMatch = finished();
942  } else {
943  pMatch->clear();
944  }
945 
946  return true;
947 }
948 
951 // URLs not handled elsewhere...
952 //
953 
954 static bool isLocalProtocol(const QString& protocol)
955 {
956  return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local"));
957 }
958 
959 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
960 {
961  //kDebug() << *url.kurl();
962  if (onlyLocalProto && isLocalProtocol(url.protocol()))
963  return false;
964 
965  // Use d->cwd as base url in case url is not absolute
966  KUrl url_dir = url.kurl();
967  if (url_dir.isRelative() && !cwd.isEmpty()) {
968  const KUrl url_cwd (cwd);
969  // Create an URL with the directory to be listed
970  url_dir = KUrl(url_cwd, url_dir.url());
971  }
972 
973  // url is malformed
974  if (!url_dir.isValid())
975  return false;
976 
977  // non local urls
978  if (!isLocalProtocol(url.protocol())) {
979  // url does not specify host
980  if (url_dir.host().isEmpty())
981  return false;
982 
983  // url does not specify a valid directory
984  if (url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash).isEmpty())
985  return false;
986 
987  // automatic completion is disabled
988  if (isAutoCompletion() && !url_auto_completion)
989  return false;
990  }
991 
992  // url handler doesn't support listing
993  if (!KProtocolManager::supportsListing(url_dir))
994  return false;
995 
996  url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway...
997 
998  // Remove escapes
999  QString directory = unescape(url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash));
1000 
1001  url_dir.setPath(directory);
1002 
1003  // List files if needed
1004  //
1005  if (!isListedUrl(CTUrl, url_dir.prettyUrl(), url.file())) {
1006  q->stop();
1007  q->clear();
1008 
1009  setListedUrl(CTUrl, url_dir.prettyUrl(), QString());
1010 
1011  QList<KUrl> url_list;
1012  url_list.append(url_dir);
1013 
1014  listUrls(url_list, QString(), false);
1015 
1016  pMatch->clear();
1017  } else if (!q->isRunning()) {
1018  *pMatch = finished();
1019  } else {
1020  pMatch->clear();
1021  }
1022 
1023  return true;
1024 }
1025 
1028 // Directory and URL listing
1029 //
1030 
1031 /*
1032  * addMatches
1033  *
1034  * Called to add matches to KCompletion
1035  */
1036 void KUrlCompletionPrivate::addMatches(const QStringList& matchList)
1037 {
1038  q->insertItems(matchList);
1039 }
1040 
1041 /*
1042  * listDirectories
1043  *
1044  * List files starting with 'filter' in the given directories,
1045  * either using DirLister or listURLs()
1046  *
1047  * In either case, addMatches() is called with the listed
1048  * files, and eventually finished() when the listing is done
1049  *
1050  * Returns the match if available, or QString() if
1051  * DirLister timed out or using kio
1052  */
1053 QString KUrlCompletionPrivate::listDirectories(
1054  const QStringList& dirList,
1055  const QString& filter,
1056  bool only_exe,
1057  bool only_dir,
1058  bool no_hidden,
1059  bool append_slash_to_dir)
1060 {
1061  assert(!q->isRunning());
1062 
1063  if (qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty()) {
1064 
1065  //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO";
1066 
1067  // Don't use KIO
1068 
1069  if (dirListThread)
1070  dirListThread->requestTermination();
1071 
1072  QStringList dirs;
1073 
1074  QStringList::ConstIterator end = dirList.constEnd();
1075  for (QStringList::ConstIterator it = dirList.constBegin();
1076  it != end;
1077  ++it) {
1078  KUrl url;
1079  url.setPath(*it);
1080  if (KAuthorized::authorizeUrlAction(QLatin1String("list"), KUrl(), url))
1081  dirs.append(*it);
1082  }
1083 
1084  dirListThread = new DirectoryListThread(this, dirs, filter, only_exe, only_dir,
1085  no_hidden, append_slash_to_dir);
1086  dirListThread->start();
1087  dirListThread->wait(200);
1088  addMatches(dirListThread->matches());
1089 
1090  return finished();
1091  }
1092 
1093  // Use KIO
1094  //kDebug() << "Listing (listDirectories):" << dirList << "with KIO";
1095 
1096  QList<KUrl> url_list;
1097 
1098  QStringList::ConstIterator it = dirList.constBegin();
1099  QStringList::ConstIterator end = dirList.constEnd();
1100 
1101  for (; it != end; ++it) {
1102  url_list.append(KUrl(*it));
1103  }
1104 
1105  listUrls(url_list, filter, only_exe, no_hidden);
1106  // Will call addMatches() and finished()
1107 
1108  return QString();
1109 }
1110 
1111 /*
1112  * listURLs
1113  *
1114  * Use KIO to list the given urls
1115  *
1116  * addMatches() is called with the listed files
1117  * finished() is called when the listing is done
1118  */
1119 void KUrlCompletionPrivate::listUrls(
1120  const QList<KUrl> &urls,
1121  const QString& filter,
1122  bool only_exe,
1123  bool no_hidden)
1124 {
1125  assert(list_urls.isEmpty());
1126  assert(list_job == 0L);
1127 
1128  list_urls = urls;
1129  list_urls_filter = filter;
1130  list_urls_only_exe = only_exe;
1131  list_urls_no_hidden = no_hidden;
1132 
1133  //kDebug() << "Listing URLs:" << *urls[0] << ",...";
1134 
1135  // Start it off by calling _k_slotIOFinished
1136  //
1137  // This will start a new list job as long as there
1138  // are urls in d->list_urls
1139  //
1140  _k_slotIOFinished(0);
1141 }
1142 
1143 /*
1144  * _k_slotEntries
1145  *
1146  * Receive files listed by KIO and call addMatches()
1147  */
1148 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
1149 {
1150  QStringList matchList;
1151 
1152  KIO::UDSEntryList::ConstIterator it = entries.constBegin();
1153  const KIO::UDSEntryList::ConstIterator end = entries.constEnd();
1154 
1155  QString filter = list_urls_filter;
1156 
1157  int filter_len = filter.length();
1158 
1159  // Iterate over all files
1160  //
1161  for (; it != end; ++it) {
1162  const KIO::UDSEntry& entry = *it;
1163  const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL);
1164 
1165  QString entry_name;
1166  if (!url.isEmpty()) {
1167  // kDebug() << "url:" << url;
1168  entry_name = KUrl(url).fileName();
1169  } else {
1170  entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
1171  }
1172 
1173  // kDebug() << "name:" << name;
1174 
1175  if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) &&
1176  (list_urls_no_hidden ||
1177  entry_name.length() == 1 ||
1178  (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.'))))
1179  continue;
1180 
1181  const bool isDir = entry.isDir();
1182 
1183  if (mode == KUrlCompletion::DirCompletion && !isDir)
1184  continue;
1185 
1186  if (filter_len == 0 || entry_name.left(filter_len) == filter) {
1187 
1188  QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name;
1189 
1190  if (isDir)
1191  toAppend.append(QLatin1Char('/'));
1192 
1193  if (!list_urls_only_exe ||
1194  (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable
1195  ) {
1196  matchList.append(prepend + toAppend);
1197  }
1198  }
1199  }
1200 
1201  addMatches(matchList);
1202 }
1203 
1204 /*
1205  * _k_slotIOFinished
1206  *
1207  * Called when a KIO job is finished.
1208  *
1209  * Start a new list job if there are still urls in
1210  * list_urls, otherwise call finished()
1211  */
1212 void KUrlCompletionPrivate::_k_slotIOFinished(KJob* job)
1213 {
1214  assert(job == list_job); Q_UNUSED(job)
1215 
1216  if (list_urls.isEmpty()) {
1217 
1218  list_job = 0L;
1219 
1220  finished(); // will call KCompletion::makeCompletion()
1221 
1222  } else {
1223 
1224  KUrl kurl(list_urls.takeFirst());
1225 
1226 // list_urls.removeAll( kurl );
1227 
1228 // kDebug() << "Start KIO::listDir" << kurl;
1229 
1230  list_job = KIO::listDir(kurl, KIO::HideProgressInfo);
1231  list_job->addMetaData("no-auth-prompt", "true");
1232 
1233  assert(list_job);
1234 
1235  q->connect(list_job,
1236  SIGNAL(result(KJob*)),
1237  SLOT(_k_slotIOFinished(KJob*)));
1238 
1239  q->connect(list_job,
1240  SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)),
1241  SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList)));
1242  }
1243 }
1244 
1247 
1248 /*
1249  * postProcessMatch, postProcessMatches
1250  *
1251  * Called by KCompletion before emitting match() and matches()
1252  *
1253  * Append '/' to directories for file completion. This is
1254  * done here to avoid stat()'ing a lot of files
1255  */
1256 void KUrlCompletion::postProcessMatch(QString* pMatch) const
1257 {
1258 // kDebug() << *pMatch;
1259 
1260  if (!pMatch->isEmpty()) {
1261 
1262  // Add '/' to directories in file completion mode
1263  // unless it has already been done
1264  if (d->last_compl_type == CTFile
1265  && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) {
1266  QString copy;
1267 
1268  if (pMatch->startsWith(QLatin1String("file:")))
1269  copy = KUrl(*pMatch).toLocalFile();
1270  else
1271  copy = *pMatch;
1272 
1273  expandTilde(copy);
1274  expandEnv(copy);
1275 #ifdef Q_WS_WIN
1276  DWORD dwAttr = GetFileAttributesW((LPCWSTR) copy.utf16());
1277  if (dwAttr == INVALID_FILE_ATTRIBUTES) {
1278  kDebug() << "Could not get file attribs ( "
1279  << GetLastError()
1280  << " ) for "
1281  << copy;
1282  } else if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
1283  pMatch->append(QLatin1Char('/'));
1284 #else
1285  if (QDir::isRelativePath(copy))
1286  copy.prepend(d->cwd + QLatin1Char('/'));
1287 
1288 // kDebug() << "stat'ing" << copy;
1289 
1290  KDE_struct_stat sbuff;
1291 
1292  QByteArray file = QFile::encodeName(copy);
1293 
1294  if (KDE_stat(file.data(), &sbuff) == 0) {
1295  if (S_ISDIR(sbuff.st_mode))
1296  pMatch->append(QLatin1Char('/'));
1297  } else {
1298  kDebug() << "Could not stat file" << copy;
1299  }
1300 #endif
1301  }
1302  }
1303 }
1304 
1305 void KUrlCompletion::postProcessMatches(QStringList* /*matches*/) const
1306 {
1307  // Maybe '/' should be added to directories here as in
1308  // postProcessMatch() but it would slow things down
1309  // when there are a lot of matches...
1310 }
1311 
1312 void KUrlCompletion::postProcessMatches(KCompletionMatches* /*matches*/) const
1313 {
1314  // Maybe '/' should be added to directories here as in
1315  // postProcessMatch() but it would slow things down
1316  // when there are a lot of matches...
1317 }
1318 
1319 void KUrlCompletion::customEvent(QEvent* e)
1320 {
1321  if (e->type() == CompletionMatchEvent::uniqueType()) {
1322 
1323  CompletionMatchEvent* matchEvent = static_cast<CompletionMatchEvent*>(e);
1324 
1325  matchEvent->completionThread()->wait();
1326 
1327  if (!d->isListedUrl(CTUser)) {
1328  stop();
1329  clear();
1330  d->addMatches(matchEvent->completionThread()->matches());
1331  } else {
1332  d->setListedUrl(CTUser);
1333  }
1334 
1335  if (d->userListThread == matchEvent->completionThread())
1336  d->userListThread = 0;
1337 
1338  if (d->dirListThread == matchEvent->completionThread())
1339  d->dirListThread = 0;
1340 
1341  delete matchEvent->completionThread();
1342  }
1343 }
1344 
1345 // static
1346 QString KUrlCompletion::replacedPath(const QString& text, bool replaceHome, bool replaceEnv)
1347 {
1348  if (text.isEmpty())
1349  return text;
1350 
1351  KUrlCompletionPrivate::MyURL url(text, QString()); // no need to replace something of our current cwd
1352  if (!url.kurl().isLocalFile())
1353  return text;
1354 
1355  url.filter(replaceHome, replaceEnv);
1356  return url.dir() + url.file();
1357 }
1358 
1359 
1360 QString KUrlCompletion::replacedPath(const QString& text) const
1361 {
1362  return replacedPath(text, d->replace_home, d->replace_env);
1363 }
1364 
1367 // Static functions
1368 
1369 /*
1370  * expandEnv
1371  *
1372  * Expand environment variables in text. Escaped '$' are ignored.
1373  * Return true if expansion was made.
1374  */
1375 static bool expandEnv(QString& text)
1376 {
1377  // Find all environment variables beginning with '$'
1378  //
1379  int pos = 0;
1380 
1381  bool expanded = false;
1382 
1383  while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) {
1384 
1385  // Skip escaped '$'
1386  //
1387  if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) {
1388  pos++;
1389  }
1390  // Variable found => expand
1391  //
1392  else {
1393  // Find the end of the variable = next '/' or ' '
1394  //
1395  int pos2 = text.indexOf(QLatin1Char(' '), pos + 1);
1396  int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1);
1397 
1398  if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
1399  pos2 = pos_tmp;
1400 
1401  if (pos2 == -1)
1402  pos2 = text.length();
1403 
1404  // Replace if the variable is terminated by '/' or ' '
1405  // and defined
1406  //
1407  if (pos2 >= 0) {
1408  int len = pos2 - pos;
1409  QString key = text.mid(pos + 1, len - 1);
1410  QString value =
1411  QString::fromLocal8Bit(qgetenv(key.toLocal8Bit()));
1412 
1413  if (!value.isEmpty()) {
1414  expanded = true;
1415  text.replace(pos, len, value);
1416  pos = pos + value.length();
1417  } else {
1418  pos = pos2;
1419  }
1420  }
1421  }
1422  }
1423 
1424  return expanded;
1425 }
1426 
1427 /*
1428  * expandTilde
1429  *
1430  * Replace "~user" with the users home directory
1431  * Return true if expansion was made.
1432  */
1433 static bool expandTilde(QString& text)
1434 {
1435  if (text.isEmpty() || (text.at(0) != QLatin1Char('~')))
1436  return false;
1437 
1438  bool expanded = false;
1439 
1440  // Find the end of the user name = next '/' or ' '
1441  //
1442  int pos2 = text.indexOf(QLatin1Char(' '), 1);
1443  int pos_tmp = text.indexOf(QLatin1Char('/'), 1);
1444 
1445  if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
1446  pos2 = pos_tmp;
1447 
1448  if (pos2 == -1)
1449  pos2 = text.length();
1450 
1451  // Replace ~user if the user name is terminated by '/' or ' '
1452  //
1453  if (pos2 >= 0) {
1454 
1455  QString user = text.mid(1, pos2 - 1);
1456  QString dir;
1457 
1458  // A single ~ is replaced with $HOME
1459  //
1460  if (user.isEmpty()) {
1461  dir = QDir::homePath();
1462  }
1463  // ~user is replaced with the dir from passwd
1464  //
1465  else {
1466  struct passwd* pw = ::getpwnam(user.toLocal8Bit());
1467 
1468  if (pw)
1469  dir = QFile::decodeName(pw->pw_dir);
1470 
1471  ::endpwent();
1472  }
1473 
1474  if (!dir.isEmpty()) {
1475  expanded = true;
1476  text.replace(0, pos2, dir);
1477  }
1478  }
1479 
1480  return expanded;
1481 }
1482 
1483 /*
1484  * unescape
1485  *
1486  * Remove escapes and return the result in a new string
1487  *
1488  */
1489 static QString unescape(const QString& text)
1490 {
1491  QString result;
1492 
1493  for (int pos = 0; pos < text.length(); pos++)
1494  if (text.at(pos) != QLatin1Char('\\'))
1495  result.insert(result.length(), text.at(pos));
1496 
1497  return result;
1498 }
1499 
1500 #include "kurlcompletion.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Fri Nov 16 2012 15:10:12 by doxygen 1.8.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.8.5 API Reference

Skip menu "kdelibs-4.8.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal