summaryrefslogtreecommitdiffstats
path: root/kioslave/trash
diff options
context:
space:
mode:
Diffstat (limited to 'kioslave/trash')
-rw-r--r--kioslave/trash/DESIGN53
-rw-r--r--kioslave/trash/Makefile.am31
-rw-r--r--kioslave/trash/kfile-plugin/Makefile.am14
-rw-r--r--kioslave/trash/kfile-plugin/RETURNED_ITEMS4
-rw-r--r--kioslave/trash/kfile-plugin/kfile_trash.cpp93
-rw-r--r--kioslave/trash/kfile-plugin/kfile_trash.desktop79
-rw-r--r--kioslave/trash/kfile-plugin/kfile_trash.h42
-rw-r--r--kioslave/trash/kfile-plugin/kfile_trash_system.desktop79
-rw-r--r--kioslave/trash/kio_trash.cpp596
-rw-r--r--kioslave/trash/kio_trash.h71
-rw-r--r--kioslave/trash/ktrash.cpp102
-rw-r--r--kioslave/trash/testtrash.cpp1198
-rw-r--r--kioslave/trash/testtrash.h118
-rw-r--r--kioslave/trash/trash.protocol89
-rw-r--r--kioslave/trash/trashimpl.cpp962
-rw-r--r--kioslave/trash/trashimpl.h182
16 files changed, 3713 insertions, 0 deletions
diff --git a/kioslave/trash/DESIGN b/kioslave/trash/DESIGN
new file mode 100644
index 000000000..63179da08
--- /dev/null
+++ b/kioslave/trash/DESIGN
@@ -0,0 +1,53 @@
+DESIGN
+======
+kio_trash implements the XDG trash standard currently at http://www.ramendik.ru/docs/trashspec.html
+
+In case race conditions between the various instances of kio_trash
+are a problem, trashimpl could be moved to a kded module, and
+kio_trash would use DCOP to talk to it. It's a bit hard to come up
+with use cases where the race conditions would matter though.
+
+BUGS
+====
+* Undo of "restore" isn't available. Need to get origPath by metadata I guess.
+
+TODO
+====
+* Clean up konq_popupmenu.cc for Type=Link URL=trash:/ :(
+* Also, provide metainfo for trash contents for that desktop link.
+=> maybe we need a new mimetype?
+ Like application/x-trash-desktop, inheriting application/x-desktop.
+ And a "trash.trashdesktop" filename or so (ouch, migration issues...)
+
+* Detect removeable media to avoid .Trash-foo on it. How?
+
+* Trashcan properties (properties for trash:/? hmm. Easier with separate dialog)
+ - Maximum size for trash can (#18109 suggests a %, but a MB size is easier).
+ This means to delete the oldest files from the trash automatically. #79553
+
+* Err, should we support renaming? :) Difficult to disable...
+ In fact it's already not disabled in readonly directories (e.g. "/") -> todo
+ (for F2 and kpropertiesdialog)
+
+* Deleting oldest files when size is bigger than a certain configurable amount (#79553)
+
+Bugs closed by kio_trash
+========================
+#79826 (3.3 only)
+#62848 (configurable trash location)
+#78116 (.directory)
+#18109 (general one)
+#17744 (restore)
+#76380 #56821 (trashing on same partition)
+
+Choice of URL scheme
+====================
+We use trash:/trashid-fileid[/relativepath]
+This gave problems with CopyJob::startRenameJob which exposed trashid-fileid
+to the user as a filename when dropping a file out of the trash.
+But this was fixed with the fileNameUsedForCopying=Name setting.
+
+A previous experiment was trash:/filename[/relativepath]?t=trashid&id=fileid
+but this gives problems with going Up (it first removes the query),
+with KDirLister (it wouldn't know when to remove the query, to find the URL
+of the parent directory).
diff --git a/kioslave/trash/Makefile.am b/kioslave/trash/Makefile.am
new file mode 100644
index 000000000..7aaa28a9f
--- /dev/null
+++ b/kioslave/trash/Makefile.am
@@ -0,0 +1,31 @@
+INCLUDES = $(all_includes)
+METASOURCES = AUTO
+
+SUBDIRS = . kfile-plugin
+
+kde_module_LTLIBRARIES = kio_trash.la
+
+kio_trash_la_SOURCES = kio_trash.cpp
+kio_trash_la_LIBADD = libtrashcommon.la $(LIB_KIO)
+kio_trash_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN) -no-undefined
+
+bin_PROGRAMS = ktrash
+ktrash_SOURCES = ktrash.cpp
+ktrash_LDADD = $(LIB_KIO)
+ktrash_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+kde_services_DATA = trash.protocol
+
+noinst_LTLIBRARIES = libtrashcommon.la
+libtrashcommon_la_SOURCES = trashimpl.cpp
+
+check_PROGRAMS = testtrash
+testtrash_SOURCES = testtrash.cpp
+testtrash_LDADD = libtrashcommon.la $(LIB_KIO)
+testtrash_LDFLAGS = $(all_libraries)
+
+TESTS = testtrash
+
+messages:
+ $(XGETTEXT) `find . -name "*.cc" -o -name "*.cpp" -o -name "*.h"` -o $(podir)/kio_trash.pot
+
diff --git a/kioslave/trash/kfile-plugin/Makefile.am b/kioslave/trash/kfile-plugin/Makefile.am
new file mode 100644
index 000000000..0668553a8
--- /dev/null
+++ b/kioslave/trash/kfile-plugin/Makefile.am
@@ -0,0 +1,14 @@
+## Makefile.am for trash file meta info plugin
+
+AM_CPPFLAGS = $(all_includes)
+
+kde_module_LTLIBRARIES = kfile_trash.la
+
+kfile_trash_la_SOURCES = kfile_trash.cpp
+kfile_trash_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+kfile_trash_la_LIBADD = ../libtrashcommon.la $(LIB_KIO)
+
+METASOURCES = AUTO
+
+services_DATA = kfile_trash.desktop kfile_trash_system.desktop
+servicesdir = $(kde_servicesdir)
diff --git a/kioslave/trash/kfile-plugin/RETURNED_ITEMS b/kioslave/trash/kfile-plugin/RETURNED_ITEMS
new file mode 100644
index 000000000..3e34c5b6d
--- /dev/null
+++ b/kioslave/trash/kfile-plugin/RETURNED_ITEMS
@@ -0,0 +1,4 @@
+kfile_trash
+===========
+QString OriginalPath
+DateTime DateOfDeletion
diff --git a/kioslave/trash/kfile-plugin/kfile_trash.cpp b/kioslave/trash/kfile-plugin/kfile_trash.cpp
new file mode 100644
index 000000000..baa27143c
--- /dev/null
+++ b/kioslave/trash/kfile-plugin/kfile_trash.cpp
@@ -0,0 +1,93 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2004 David Faure <[email protected]>
+ * Based on kfile_txt.cpp by Nadeem Hasan <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation version 2.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "kfile_trash.h"
+
+#include <kgenericfactory.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+#include <qstringlist.h>
+#include <qdatetime.h>
+
+typedef KGenericFactory<KTrashPlugin> TrashFactory;
+
+K_EXPORT_COMPONENT_FACTORY(kfile_trash, TrashFactory("kfile_trash"))
+
+KTrashPlugin::KTrashPlugin(QObject *parent, const char *name,
+ const QStringList &args) : KFilePlugin(parent, name, args)
+{
+ KGlobal::locale()->insertCatalogue( "kio_trash" );
+
+ kdDebug(7034) << "Trash file meta info plugin\n";
+
+ makeMimeTypeInfo("trash");
+ makeMimeTypeInfo("system");
+
+ (void)impl.init();
+}
+
+void KTrashPlugin::makeMimeTypeInfo(const QString& mimeType)
+{
+ KFileMimeTypeInfo* info = addMimeTypeInfo( mimeType );
+
+ KFileMimeTypeInfo::GroupInfo* group =
+ addGroupInfo(info, "General", i18n("General"));
+
+ KFileMimeTypeInfo::ItemInfo* item;
+ item = addItemInfo(group, "OriginalPath", i18n("Original Path"), QVariant::String);
+ item = addItemInfo(group, "DateOfDeletion", i18n("Date of Deletion"), QVariant::DateTime);
+}
+
+bool KTrashPlugin::readInfo(KFileMetaInfo& info, uint)
+{
+ KURL url = info.url();
+
+ if ( url.protocol()=="system"
+ && url.path().startsWith("/trash") )
+ {
+ QString path = url.path();
+ path.remove(0, 6);
+ url.setProtocol("trash");
+ url.setPath(path);
+ }
+
+ //kdDebug() << k_funcinfo << info.url() << endl;
+ if ( url.protocol() != "trash" )
+ return false;
+
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ if ( !TrashImpl::parseURL( url, trashId, fileId, relativePath ) )
+ return false;
+
+ TrashImpl::TrashedFileInfo trashInfo;
+ if ( !impl.infoForFile( trashId, fileId, trashInfo ) )
+ return false;
+
+ KFileMetaInfoGroup group = appendGroup(info, "General");
+ appendItem(group, "OriginalPath", trashInfo.origPath);
+ appendItem(group, "DateOfDeletion", trashInfo.deletionDate);
+
+ return true;
+}
+
+#include "kfile_trash.moc"
diff --git a/kioslave/trash/kfile-plugin/kfile_trash.desktop b/kioslave/trash/kfile-plugin/kfile_trash.desktop
new file mode 100644
index 000000000..02d90422a
--- /dev/null
+++ b/kioslave/trash/kfile-plugin/kfile_trash.desktop
@@ -0,0 +1,79 @@
+[Desktop Entry]
+Type=Service
+Name=Trash File Info
+Name[af]=Asblik inligting
+Name[ar]=معلومات مفل المهملات
+Name[az]=Zibil Faylı Məlumatı
+Name[be]=Інфармацыя аб файле сметніцы
+Name[bg]=Информация за кошчето
+Name[bn]=আবর্জনা ফাইল তথ্য
+Name[br]=Titouroù diwar-benn ar pod-lastez
+Name[bs]=Smeće informacije o datoteci
+Name[ca]=Informació del fitxer paperera
+Name[cs]=Info o koši
+Name[csb]=Wëdowiédzô ò lopkù w kòszu
+Name[da]=Fil-info om affald
+Name[de]=Mülleimer-Information
+Name[el]=Πληροφορίες για τον Κάδο Απορριμμάτων
+Name[en_GB]=Wastebin File Info
+Name[eo]=Rubuja informo
+Name[es]=Información de la papelera
+Name[et]=Prügikasti failiinfo
+Name[eu]=Zakarontziaren infoa
+Name[fa]=اطلاعات پروندۀ زباله
+Name[fi]=Roskakorin tiedot
+Name[fr]=Info Fichier Corbeille
+Name[fy]=Jiskefet ynformaasje
+Name[gl]=Información do Lixo
+Name[he]=מידע אודות קובץ אשפה
+Name[hi]=रद्दी फ़ाइल जानकारी
+Name[hr]=Podaci o otpadu
+Name[hu]=Szemétkosár-jellemzők
+Name[is]=Upplýsingar um ruslaskrá
+Name[it]=Informazioni file del cestino
+Name[ja]=ごみ箱情報
+Name[ka]=ურნაში არსებული ფაილის შესახებ ცნობი
+Name[kk]=Өшірілген файл мәліметі
+Name[km]=ព័ត៌មាន​ឯកសារ​សំរាម
+Name[ko]=휴지통 파일 정보
+Name[lt]=Šiukšlių bylos informacija
+Name[lv]=Atkritumu faila informācija
+Name[mk]=Инфо. за датотека од Корпата
+Name[ms]=Maklumat Fail Sampah
+Name[mt]=Skart
+Name[nb]=Søppelfilinformasjon
+Name[nds]=Affalltünn-Informatschonen
+Name[ne]=रद्दीटोकरी फाइल सूचना
+Name[nl]=Prullenbakinformatie
+Name[nn]=Søppelfilinformasjon
+Name[pa]=ਰੱਦੀ ਫਾਇਲ ਜਾਣਕਾਰੀ
+Name[pl]=Informacja o pliku w koszu
+Name[pt]=Informações de Ficheiros no Lixo
+Name[pt_BR]=Informações sobre o Arquivo de Lixo
+Name[ro]=Informații fișier șters
+Name[ru]=Сведения о файле в корзине
+Name[rw]=Ibisobanuro byo Guta Idosiye
+Name[se]=Ruskalihttedieđut
+Name[sk]=Informácie o koši
+Name[sl]=Informacije o Smeteh
+Name[sr]=Информације о фајлу у смећу
+Name[sr@Latn]=Informacije o fajlu u smeću
+Name[sv]=Information om filer i papperskorgen
+Name[ta]=குப்பைத்தொட்டி கோப்பு தகவல்
+Name[te]=చెత్త బుట్ట దస్త్ర వివరాలు
+Name[tg]=Файли ахборотии ахлотдон
+Name[th]=ข้อมูลแฟ้มถังขยะ
+Name[tr]=Çöp Dosya Bilgisi
+Name[tt]=Taşlanğan Birem Turında
+Name[uk]=Інформація про файл у смітнику
+Name[uz]=Chiqindilar qutisi haqida maʼlumot
+Name[uz@cyrillic]=Чиқиндилар қутиси ҳақида маълумот
+Name[vi]=Thông tin về Tập tin trong Thùng rác
+Name[wa]=Informåcion sol fitchî batch
+Name[zh_CN]=回收站文件信息
+Name[zh_TW]=資源回收桶檔案資訊
+ServiceTypes=KFilePlugin
+X-KDE-Library=kfile_trash
+X-KDE-Protocol=trash
+PreferredGroups=General
+PreferredItems=OriginalPath,DateOfDeletion
diff --git a/kioslave/trash/kfile-plugin/kfile_trash.h b/kioslave/trash/kfile-plugin/kfile_trash.h
new file mode 100644
index 000000000..8316f7402
--- /dev/null
+++ b/kioslave/trash/kfile-plugin/kfile_trash.h
@@ -0,0 +1,42 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2004 David Faure <[email protected]>
+ * Based on kfile_txt.h by Nadeem Hasan <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation version 2.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef __KFILE_TRASH_H_
+#define __KFILE_TRASH_H_
+
+#include <kfilemetainfo.h>
+#include "../trashimpl.h"
+
+class QStringList;
+
+class KTrashPlugin: public KFilePlugin
+{
+ Q_OBJECT
+
+public:
+ KTrashPlugin(QObject *parent, const char *name, const QStringList& args);
+ virtual bool readInfo(KFileMetaInfo& info, uint what);
+
+private:
+ void makeMimeTypeInfo(const QString& mimeType);
+ TrashImpl impl;
+};
+
+#endif
diff --git a/kioslave/trash/kfile-plugin/kfile_trash_system.desktop b/kioslave/trash/kfile-plugin/kfile_trash_system.desktop
new file mode 100644
index 000000000..5f57b5af3
--- /dev/null
+++ b/kioslave/trash/kfile-plugin/kfile_trash_system.desktop
@@ -0,0 +1,79 @@
+[Desktop Entry]
+Type=Service
+Name=Trash File Info
+Name[af]=Asblik inligting
+Name[ar]=معلومات مفل المهملات
+Name[az]=Zibil Faylı Məlumatı
+Name[be]=Інфармацыя аб файле сметніцы
+Name[bg]=Информация за кошчето
+Name[bn]=আবর্জনা ফাইল তথ্য
+Name[br]=Titouroù diwar-benn ar pod-lastez
+Name[bs]=Smeće informacije o datoteci
+Name[ca]=Informació del fitxer paperera
+Name[cs]=Info o koši
+Name[csb]=Wëdowiédzô ò lopkù w kòszu
+Name[da]=Fil-info om affald
+Name[de]=Mülleimer-Information
+Name[el]=Πληροφορίες για τον Κάδο Απορριμμάτων
+Name[en_GB]=Wastebin File Info
+Name[eo]=Rubuja informo
+Name[es]=Información de la papelera
+Name[et]=Prügikasti failiinfo
+Name[eu]=Zakarontziaren infoa
+Name[fa]=اطلاعات پروندۀ زباله
+Name[fi]=Roskakorin tiedot
+Name[fr]=Info Fichier Corbeille
+Name[fy]=Jiskefet ynformaasje
+Name[gl]=Información do Lixo
+Name[he]=מידע אודות קובץ אשפה
+Name[hi]=रद्दी फ़ाइल जानकारी
+Name[hr]=Podaci o otpadu
+Name[hu]=Szemétkosár-jellemzők
+Name[is]=Upplýsingar um ruslaskrá
+Name[it]=Informazioni file del cestino
+Name[ja]=ごみ箱情報
+Name[ka]=ურნაში არსებული ფაილის შესახებ ცნობი
+Name[kk]=Өшірілген файл мәліметі
+Name[km]=ព័ត៌មាន​ឯកសារ​សំរាម
+Name[ko]=휴지통 파일 정보
+Name[lt]=Šiukšlių bylos informacija
+Name[lv]=Atkritumu faila informācija
+Name[mk]=Инфо. за датотека од Корпата
+Name[ms]=Maklumat Fail Sampah
+Name[mt]=Skart
+Name[nb]=Søppelfilinformasjon
+Name[nds]=Affalltünn-Informatschonen
+Name[ne]=रद्दीटोकरी फाइल सूचना
+Name[nl]=Prullenbakinformatie
+Name[nn]=Søppelfilinformasjon
+Name[pa]=ਰੱਦੀ ਫਾਇਲ ਜਾਣਕਾਰੀ
+Name[pl]=Informacja o pliku w koszu
+Name[pt]=Informações de Ficheiros no Lixo
+Name[pt_BR]=Informações sobre o Arquivo de Lixo
+Name[ro]=Informații fișier șters
+Name[ru]=Сведения о файле в корзине
+Name[rw]=Ibisobanuro byo Guta Idosiye
+Name[se]=Ruskalihttedieđut
+Name[sk]=Informácie o koši
+Name[sl]=Informacije o Smeteh
+Name[sr]=Информације о фајлу у смећу
+Name[sr@Latn]=Informacije o fajlu u smeću
+Name[sv]=Information om filer i papperskorgen
+Name[ta]=குப்பைத்தொட்டி கோப்பு தகவல்
+Name[te]=చెత్త బుట్ట దస్త్ర వివరాలు
+Name[tg]=Файли ахборотии ахлотдон
+Name[th]=ข้อมูลแฟ้มถังขยะ
+Name[tr]=Çöp Dosya Bilgisi
+Name[tt]=Taşlanğan Birem Turında
+Name[uk]=Інформація про файл у смітнику
+Name[uz]=Chiqindilar qutisi haqida maʼlumot
+Name[uz@cyrillic]=Чиқиндилар қутиси ҳақида маълумот
+Name[vi]=Thông tin về Tập tin trong Thùng rác
+Name[wa]=Informåcion sol fitchî batch
+Name[zh_CN]=回收站文件信息
+Name[zh_TW]=資源回收桶檔案資訊
+ServiceTypes=KFilePlugin
+X-KDE-Library=kfile_trash
+X-KDE-Protocol=system
+PreferredGroups=General
+PreferredItems=OriginalPath,DateOfDeletion
diff --git a/kioslave/trash/kio_trash.cpp b/kioslave/trash/kio_trash.cpp
new file mode 100644
index 000000000..7912cbb7c
--- /dev/null
+++ b/kioslave/trash/kio_trash.cpp
@@ -0,0 +1,596 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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 "kio_trash.h"
+#include <kio/job.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <klargefile.h>
+#include <kcmdlineargs.h>
+#include <kmimetype.h>
+#include <kprocess.h>
+
+#include <dcopclient.h>
+#include <qdatastream.h>
+#include <qtextstream.h>
+#include <qfile.h>
+#include <qeventloop.h>
+
+#include <time.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+static const KCmdLineOptions options[] =
+{
+ { "+protocol", I18N_NOOP( "Protocol name" ), 0 },
+ { "+pool", I18N_NOOP( "Socket name" ), 0 },
+ { "+app", I18N_NOOP( "Socket name" ), 0 },
+ KCmdLineLastOption
+};
+
+extern "C" {
+ int KDE_EXPORT kdemain( int argc, char **argv )
+ {
+ //KInstance instance( "kio_trash" );
+ // KApplication is necessary to use kio_file
+ putenv(strdup("SESSION_MANAGER="));
+ KApplication::disableAutoDcopRegistration();
+ KCmdLineArgs::init(argc, argv, "kio_trash", 0, 0, 0, 0);
+ KCmdLineArgs::addCmdLineOptions( options );
+ KApplication app( false, false );
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ TrashProtocol slave( args->arg(0), args->arg(1), args->arg(2) );
+ slave.dispatchLoop();
+ return 0;
+ }
+}
+
+#define INIT_IMPL \
+ if ( !impl.init() ) { \
+ error( impl.lastErrorCode(), impl.lastErrorMessage() ); \
+ return; \
+ }
+
+TrashProtocol::TrashProtocol( const QCString& protocol, const QCString &pool, const QCString &app)
+ : SlaveBase(protocol, pool, app )
+{
+ struct passwd *user = getpwuid( getuid() );
+ if ( user )
+ m_userName = QString::fromLatin1(user->pw_name);
+ struct group *grp = getgrgid( getgid() );
+ if ( grp )
+ m_groupName = QString::fromLatin1(grp->gr_name);
+}
+
+TrashProtocol::~TrashProtocol()
+{
+}
+
+void TrashProtocol::restore( const KURL& trashURL )
+{
+ int trashId;
+ QString fileId, relativePath;
+ bool ok = TrashImpl::parseURL( trashURL, trashId, fileId, relativePath );
+ if ( !ok ) {
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( trashURL.prettyURL() ) );
+ return;
+ }
+ TrashedFileInfo info;
+ ok = impl.infoForFile( trashId, fileId, info );
+ if ( !ok ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ return;
+ }
+ KURL dest;
+ dest.setPath( info.origPath );
+ if ( !relativePath.isEmpty() )
+ dest.addPath( relativePath );
+
+ // Check that the destination directory exists, to improve the error code in case it doesn't.
+ const QString destDir = dest.directory();
+ KDE_struct_stat buff;
+ if ( KDE_lstat( QFile::encodeName( destDir ), &buff ) == -1 ) {
+ error( KIO::ERR_SLAVE_DEFINED,
+ i18n( "The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. "
+ "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it." ).arg( destDir ) );
+ return;
+ }
+
+ copyOrMove( trashURL, dest, false /*overwrite*/, Move );
+}
+
+void TrashProtocol::rename( const KURL &oldURL, const KURL &newURL, bool overwrite )
+{
+ INIT_IMPL;
+
+ kdDebug()<<"TrashProtocol::rename(): old="<<oldURL<<" new="<<newURL<<" overwrite=" << overwrite<<endl;
+
+ if ( oldURL.protocol() == "trash" && newURL.protocol() == "trash" ) {
+ error( KIO::ERR_CANNOT_RENAME, oldURL.prettyURL() );
+ return;
+ }
+
+ copyOrMove( oldURL, newURL, overwrite, Move );
+}
+
+void TrashProtocol::copy( const KURL &src, const KURL &dest, int /*permissions*/, bool overwrite )
+{
+ INIT_IMPL;
+
+ kdDebug()<<"TrashProtocol::copy(): " << src << " " << dest << endl;
+
+ if ( src.protocol() == "trash" && dest.protocol() == "trash" ) {
+ error( KIO::ERR_UNSUPPORTED_ACTION, i18n( "This file is already in the trash bin." ) );
+ return;
+ }
+
+ copyOrMove( src, dest, overwrite, Copy );
+}
+
+void TrashProtocol::copyOrMove( const KURL &src, const KURL &dest, bool overwrite, CopyOrMove action )
+{
+ if ( src.protocol() == "trash" && dest.isLocalFile() ) {
+ // Extracting (e.g. via dnd). Ignore original location stored in info file.
+ int trashId;
+ QString fileId, relativePath;
+ bool ok = TrashImpl::parseURL( src, trashId, fileId, relativePath );
+ if ( !ok ) {
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( src.prettyURL() ) );
+ return;
+ }
+ const QString destPath = dest.path();
+ if ( QFile::exists( destPath ) ) {
+ if ( overwrite ) {
+ ok = QFile::remove( destPath );
+ Q_ASSERT( ok ); // ### TODO
+ } else {
+ error( KIO::ERR_FILE_ALREADY_EXIST, destPath );
+ return;
+ }
+ }
+
+ if ( action == Move ) {
+ kdDebug() << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.moveFromTrash( destPath, trashId, fileId, relativePath );
+ } else { // Copy
+ kdDebug() << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.copyFromTrash( destPath, trashId, fileId, relativePath );
+ }
+ if ( !ok ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else {
+ if ( action == Move && relativePath.isEmpty() )
+ (void)impl.deleteInfo( trashId, fileId );
+ finished();
+ }
+ return;
+ } else if ( src.isLocalFile() && dest.protocol() == "trash" ) {
+ QString dir = dest.directory();
+ //kdDebug() << "trashing a file to " << dir << endl;
+ // Trashing a file
+ // We detect the case where this isn't normal trashing, but
+ // e.g. if kwrite tries to save (moving tempfile over destination)
+ if ( dir.length() <= 1 && src.fileName() == dest.fileName() ) // new toplevel entry
+ {
+ const QString srcPath = src.path();
+ // In theory we should use TrashImpl::parseURL to give the right filename to createInfo,
+ // in case the trash URL didn't contain the same filename as srcPath.
+ // But this can only happen with copyAs/moveAs, not available in the GUI
+ // for the trash (New/... or Rename from iconview/listview).
+ int trashId;
+ QString fileId;
+ if ( !impl.createInfo( srcPath, trashId, fileId ) ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else {
+ bool ok;
+ if ( action == Move ) {
+ kdDebug() << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.moveToTrash( srcPath, trashId, fileId );
+ } else { // Copy
+ kdDebug() << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")" << endl;
+ ok = impl.copyToTrash( srcPath, trashId, fileId );
+ }
+ if ( !ok ) {
+ (void)impl.deleteInfo( trashId, fileId );
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else {
+ // Inform caller of the final URL. Used by konq_undo.
+ const KURL url = impl.makeURL( trashId, fileId, QString::null );
+ setMetaData( "trashURL-" + srcPath, url.url() );
+ finished();
+ }
+ }
+ return;
+ } else {
+ kdDebug() << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory" << endl;
+ // It's not allowed to add a file to an existing trash directory.
+ error( KIO::ERR_ACCESS_DENIED, dest.prettyURL() );
+ return;
+ }
+ } else
+ error( KIO::ERR_UNSUPPORTED_ACTION, "should never happen" );
+}
+
+static void addAtom(KIO::UDSEntry& entry, unsigned int ID, long long l, const QString& s = QString::null)
+{
+ KIO::UDSAtom atom;
+ atom.m_uds = ID;
+ atom.m_long = l;
+ atom.m_str = s;
+ entry.append(atom);
+}
+
+void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry& entry)
+{
+ entry.clear();
+ addAtom(entry, KIO::UDS_NAME, 0, ".");
+ addAtom(entry, KIO::UDS_FILE_TYPE, S_IFDIR);
+ addAtom(entry, KIO::UDS_ACCESS, 0700);
+ addAtom(entry, KIO::UDS_MIME_TYPE, 0, "inode/directory");
+ addAtom(entry, KIO::UDS_USER, 0, m_userName);
+ addAtom(entry, KIO::UDS_GROUP, 0, m_groupName);
+}
+
+void TrashProtocol::stat(const KURL& url)
+{
+ INIT_IMPL;
+ const QString path = url.path();
+ if( path.isEmpty() || path == "/" ) {
+ // The root is "virtual" - it's not a single physical directory
+ KIO::UDSEntry entry;
+ createTopLevelDirEntry( entry );
+ statEntry( entry );
+ finished();
+ } else {
+ int trashId;
+ QString fileId, relativePath;
+
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+
+ if ( !ok ) {
+ // ######## do we still need this?
+ kdDebug() << k_funcinfo << url << " looks fishy, returning does-not-exist" << endl;
+ // A URL like trash:/file simply means that CopyJob is trying to see if
+ // the destination exists already (it made up the URL by itself).
+ error( KIO::ERR_DOES_NOT_EXIST, url.prettyURL() );
+ //error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) );
+ return;
+ }
+
+ const QString filePath = impl.physicalPath( trashId, fileId, relativePath );
+ if ( filePath.isEmpty() ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ return;
+ }
+
+ QString fileName = filePath.section('/', -1, -1, QString::SectionSkipEmpty);
+
+ QString fileURL = QString::null;
+ if ( url.path().length() > 1 ) {
+ fileURL = url.url();
+ }
+
+ KIO::UDSEntry entry;
+ TrashedFileInfo info;
+ ok = impl.infoForFile( trashId, fileId, info );
+ if ( ok )
+ ok = createUDSEntry( filePath, fileName, fileURL, entry, info );
+
+ if ( !ok ) {
+ error( KIO::ERR_COULD_NOT_STAT, url.prettyURL() );
+ }
+
+ statEntry( entry );
+ finished();
+ }
+}
+
+void TrashProtocol::del( const KURL &url, bool /*isfile*/ )
+{
+ int trashId;
+ QString fileId, relativePath;
+
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ if ( !ok ) {
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) );
+ return;
+ }
+
+ ok = relativePath.isEmpty();
+ if ( !ok ) {
+ error( KIO::ERR_ACCESS_DENIED, url.prettyURL() );
+ return;
+ }
+
+ ok = impl.del(trashId, fileId);
+ if ( !ok ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ return;
+ }
+
+ finished();
+}
+
+void TrashProtocol::listDir(const KURL& url)
+{
+ INIT_IMPL;
+ kdDebug() << "listdir: " << url << endl;
+ if ( url.path().length() <= 1 ) {
+ listRoot();
+ return;
+ }
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ if ( !ok ) {
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) );
+ return;
+ }
+ //was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
+
+ // Get info for deleted directory - the date of deletion and orig path will be used
+ // for all the items in it, and we need the physicalPath.
+ TrashedFileInfo info;
+ ok = impl.infoForFile( trashId, fileId, info );
+ if ( !ok || info.physicalPath.isEmpty() ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ return;
+ }
+ if ( !relativePath.isEmpty() ) {
+ info.physicalPath += "/";
+ info.physicalPath += relativePath;
+ }
+
+ // List subdir. Can't use kio_file here since we provide our own info...
+ kdDebug() << k_funcinfo << "listing " << info.physicalPath << endl;
+ QStrList entryNames = impl.listDir( info.physicalPath );
+ totalSize( entryNames.count() );
+ KIO::UDSEntry entry;
+ QStrListIterator entryIt( entryNames );
+ for (; entryIt.current(); ++entryIt) {
+ QString fileName = QFile::decodeName( entryIt.current() );
+ if ( fileName == ".." )
+ continue;
+ const QString filePath = info.physicalPath + "/" + fileName;
+ // shouldn't be necessary
+ //const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + "/" + fileName );
+ entry.clear();
+ TrashedFileInfo infoForItem( info );
+ infoForItem.origPath += '/';
+ infoForItem.origPath += fileName;
+ if ( ok && createUDSEntry( filePath, fileName, QString::null /*url*/, entry, infoForItem ) ) {
+ listEntry( entry, false );
+ }
+ }
+ entry.clear();
+ listEntry( entry, true );
+ finished();
+}
+
+bool TrashProtocol::createUDSEntry( const QString& physicalPath, const QString& fileName, const QString& url, KIO::UDSEntry& entry, const TrashedFileInfo& info )
+{
+ QCString physicalPath_c = QFile::encodeName( physicalPath );
+ KDE_struct_stat buff;
+ if ( KDE_lstat( physicalPath_c, &buff ) == -1 ) {
+ kdWarning() << "couldn't stat " << physicalPath << endl;
+ return false;
+ }
+ if (S_ISLNK(buff.st_mode)) {
+ char buffer2[ 1000 ];
+ int n = readlink( physicalPath_c, buffer2, 1000 );
+ if ( n != -1 ) {
+ buffer2[ n ] = 0;
+ }
+
+ addAtom( entry, KIO::UDS_LINK_DEST, 0, QFile::decodeName( buffer2 ) );
+ // Follow symlink
+ // That makes sense in kio_file, but not in the trash, especially for the size
+ // #136876
+#if 0
+ if ( KDE_stat( physicalPath_c, &buff ) == -1 ) {
+ // It is a link pointing to nowhere
+ buff.st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
+ buff.st_mtime = 0;
+ buff.st_atime = 0;
+ buff.st_size = 0;
+ }
+#endif
+ }
+ mode_t type = buff.st_mode & S_IFMT; // extract file type
+ mode_t access = buff.st_mode & 07777; // extract permissions
+ access &= 07555; // make it readonly, since it's in the trashcan
+ addAtom( entry, KIO::UDS_NAME, 0, fileName );
+ addAtom( entry, KIO::UDS_FILE_TYPE, type );
+ if ( !url.isEmpty() )
+ addAtom( entry, KIO::UDS_URL, 0, url );
+
+ KMimeType::Ptr mt = KMimeType::findByPath( physicalPath, buff.st_mode );
+ addAtom( entry, KIO::UDS_MIME_TYPE, 0, mt->name() );
+ addAtom( entry, KIO::UDS_ACCESS, access );
+ addAtom( entry, KIO::UDS_SIZE, buff.st_size );
+ addAtom( entry, KIO::UDS_USER, 0, m_userName ); // assumption
+ addAtom( entry, KIO::UDS_GROUP, 0, m_groupName ); // assumption
+ addAtom( entry, KIO::UDS_MODIFICATION_TIME, buff.st_mtime );
+ addAtom( entry, KIO::UDS_ACCESS_TIME, buff.st_atime ); // ## or use it for deletion time?
+ addAtom( entry, KIO::UDS_EXTRA, 0, info.origPath );
+ addAtom( entry, KIO::UDS_EXTRA, 0, info.deletionDate.toString( Qt::ISODate ) );
+ return true;
+}
+
+void TrashProtocol::listRoot()
+{
+ INIT_IMPL;
+ const TrashedFileInfoList lst = impl.list();
+ totalSize( lst.count() );
+ KIO::UDSEntry entry;
+ createTopLevelDirEntry( entry );
+ listEntry( entry, false );
+ for ( TrashedFileInfoList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
+ const KURL url = TrashImpl::makeURL( (*it).trashId, (*it).fileId, QString::null );
+ KURL origURL;
+ origURL.setPath( (*it).origPath );
+ entry.clear();
+ if ( createUDSEntry( (*it).physicalPath, origURL.fileName(), url.url(), entry, *it ) )
+ listEntry( entry, false );
+ }
+ entry.clear();
+ listEntry( entry, true );
+ finished();
+}
+
+void TrashProtocol::special( const QByteArray & data )
+{
+ INIT_IMPL;
+ QDataStream stream( data, IO_ReadOnly );
+ int cmd;
+ stream >> cmd;
+
+ switch (cmd) {
+ case 1:
+ if ( impl.emptyTrash() )
+ finished();
+ else
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ break;
+ case 2:
+ impl.migrateOldTrash();
+ finished();
+ break;
+ case 3:
+ {
+ KURL url;
+ stream >> url;
+ restore( url );
+ break;
+ }
+ default:
+ kdWarning(7116) << "Unknown command in special(): " << cmd << endl;
+ error( KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd) );
+ break;
+ }
+}
+
+void TrashProtocol::put( const KURL& url, int /*permissions*/, bool /*overwrite*/, bool /*resume*/ )
+{
+ INIT_IMPL;
+ kdDebug() << "put: " << url << endl;
+ // create deleted file. We need to get the mtime and original location from metadata...
+ // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed...
+ error( KIO::ERR_ACCESS_DENIED, url.prettyURL() );
+}
+
+void TrashProtocol::get( const KURL& url )
+{
+ INIT_IMPL;
+ kdDebug() << "get() : " << url << endl;
+ if ( !url.isValid() ) {
+ kdDebug() << kdBacktrace() << endl;
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.url() ) );
+ return;
+ }
+ if ( url.path().length() <= 1 ) {
+ error( KIO::ERR_IS_DIRECTORY, url.prettyURL() );
+ return;
+ }
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ if ( !ok ) {
+ error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.prettyURL() ) );
+ return;
+ }
+ const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
+ if ( physicalPath.isEmpty() ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ return;
+ }
+
+ // Usually we run jobs in TrashImpl (for e.g. future kdedmodule)
+ // But for this one we wouldn't use DCOP for every bit of data...
+ KURL fileURL;
+ fileURL.setPath( physicalPath );
+ KIO::Job* job = KIO::get( fileURL );
+ connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
+ this, SLOT( slotData( KIO::Job*, const QByteArray& ) ) );
+ connect( job, SIGNAL( mimetype( KIO::Job*, const QString& ) ),
+ this, SLOT( slotMimetype( KIO::Job*, const QString& ) ) );
+ connect( job, SIGNAL( result(KIO::Job *) ),
+ this, SLOT( jobFinished(KIO::Job *) ) );
+ qApp->eventLoop()->enterLoop();
+}
+
+void TrashProtocol::slotData( KIO::Job*, const QByteArray&arr )
+{
+ data( arr );
+}
+
+void TrashProtocol::slotMimetype( KIO::Job*, const QString& mt )
+{
+ mimeType( mt );
+}
+
+void TrashProtocol::jobFinished( KIO::Job* job )
+{
+ if ( job->error() )
+ error( job->error(), job->errorText() );
+ else
+ finished();
+ qApp->eventLoop()->exitLoop();
+}
+
+#if 0
+void TrashProtocol::mkdir( const KURL& url, int /*permissions*/ )
+{
+ INIT_IMPL;
+ // create info about deleted dir
+ // ############ Problem: we don't know the original path.
+ // Let's try to avoid this case (we should get to copy() instead, for local files)
+ kdDebug() << "mkdir: " << url << endl;
+ QString dir = url.directory();
+
+ if ( dir.length() <= 1 ) // new toplevel entry
+ {
+ // ## we should use TrashImpl::parseURL to give the right filename to createInfo
+ int trashId;
+ QString fileId;
+ if ( !impl.createInfo( url.path(), trashId, fileId ) ) {
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else {
+ if ( !impl.mkdir( trashId, fileId, permissions ) ) {
+ (void)impl.deleteInfo( trashId, fileId );
+ error( impl.lastErrorCode(), impl.lastErrorMessage() );
+ } else
+ finished();
+ }
+ } else {
+ // Well it's not allowed to add a directory to an existing deleted directory.
+ error( KIO::ERR_ACCESS_DENIED, url.prettyURL() );
+ }
+}
+#endif
+
+#include "kio_trash.moc"
diff --git a/kioslave/trash/kio_trash.h b/kioslave/trash/kio_trash.h
new file mode 100644
index 000000000..726db431e
--- /dev/null
+++ b/kioslave/trash/kio_trash.h
@@ -0,0 +1,71 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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.
+*/
+
+#ifndef KIO_TRASH_H
+#define KIO_TRASH_H
+
+#include <kio/slavebase.h>
+#include "trashimpl.h"
+namespace KIO { class Job; }
+
+typedef TrashImpl::TrashedFileInfo TrashedFileInfo;
+typedef TrashImpl::TrashedFileInfoList TrashedFileInfoList;
+
+class TrashProtocol : public QObject, public KIO::SlaveBase
+{
+ Q_OBJECT
+public:
+ TrashProtocol( const QCString& protocol, const QCString &pool, const QCString &app);
+ virtual ~TrashProtocol();
+ virtual void stat(const KURL& url);
+ virtual void listDir(const KURL& url);
+ virtual void get( const KURL& url );
+ virtual void put( const KURL& url, int , bool overwrite, bool );
+ virtual void rename( const KURL &, const KURL &, bool );
+ virtual void copy( const KURL &src, const KURL &dest, int permissions, bool overwrite );
+ // TODO (maybe) chmod( const KURL& url, int permissions );
+ virtual void del( const KURL &url, bool isfile );
+ /**
+ * Special actions: (first int in the byte array)
+ * 1 : empty trash
+ * 2 : migrate old (pre-kde-3.4) trash contents
+ * 3 : restore a file to its original location. Args: KURL trashURL.
+ */
+ virtual void special( const QByteArray & data );
+
+private slots:
+ void slotData( KIO::Job*, const QByteArray& );
+ void slotMimetype( KIO::Job*, const QString& );
+ void jobFinished( KIO::Job* job );
+
+private:
+ typedef enum CopyOrMove { Copy, Move };
+ void copyOrMove( const KURL& src, const KURL& dest, bool overwrite, CopyOrMove action );
+ void createTopLevelDirEntry(KIO::UDSEntry& entry);
+ bool createUDSEntry( const QString& physicalPath, const QString& fileName, const QString& url,
+ KIO::UDSEntry& entry, const TrashedFileInfo& info );
+ void listRoot();
+ void restore( const KURL& trashURL );
+
+ TrashImpl impl;
+ QString m_userName;
+ QString m_groupName;
+};
+
+#endif
diff --git a/kioslave/trash/ktrash.cpp b/kioslave/trash/ktrash.cpp
new file mode 100644
index 000000000..4fa5ccd27
--- /dev/null
+++ b/kioslave/trash/ktrash.cpp
@@ -0,0 +1,102 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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 <kapplication.h>
+#include <kio/netaccess.h>
+#include <kio/job.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+#include <kdirnotify_stub.h>
+#include <kdebug.h>
+
+static KCmdLineOptions options[] =
+{
+ { "empty", I18N_NOOP( "Empty the contents of the trash" ), 0 },
+ //{ "migrate", I18N_NOOP( "Migrate contents of old trash" ), 0 },
+ { "restore <file>", I18N_NOOP( "Restore a trashed file to its original location" ), 0 },
+ // This hack is for the servicemenu on trash.desktop which uses Exec=ktrash -empty. %f is implied...
+ { "+[ignored]", I18N_NOOP( "Ignored" ), 0 },
+ KCmdLineLastOption
+};
+
+int main(int argc, char *argv[])
+{
+ KApplication::disableAutoDcopRegistration();
+ KCmdLineArgs::init( argc, argv, "ktrash",
+ I18N_NOOP( "ktrash" ),
+ I18N_NOOP( "Helper program to handle the KDE trash can\n"
+ "Note: to move files to the trash, do not use ktrash, but \"kfmclient move 'url' trash:/\"" ),
+ KDE_VERSION_STRING );
+ KCmdLineArgs::addCmdLineOptions( options );
+ KApplication app;
+
+ KCmdLineArgs* args = KCmdLineArgs::parsedArgs();
+ if ( args->isSet( "empty" ) ) {
+ // We use a kio job instead of linking to TrashImpl, for a smaller binary
+ // (and the possibility of a central service at some point)
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)1;
+ KIO::Job* job = KIO::special( "trash:/", packedArgs );
+ (void)KIO::NetAccess::synchronousRun( job, 0 );
+
+ // Update konq windows opened on trash:/
+ KDirNotify_stub allDirNotify("*", "KDirNotify*");
+ allDirNotify.FilesAdded( "trash:/" ); // yeah, files were removed, but we don't know which ones...
+ return 0;
+ }
+
+#if 0
+ // This is only for testing. KDesktop handles it automatically.
+ if ( args->isSet( "migrate" ) ) {
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)2;
+ KIO::Job* job = KIO::special( "trash:/", packedArgs );
+ (void)KIO::NetAccess::synchronousRun( job, 0 );
+ return 0;
+ }
+#endif
+
+ QCString restoreArg = args->getOption( "restore" );
+ if ( !restoreArg.isEmpty() ) {
+
+ if (restoreArg.find("system:/trash")==0) {
+ restoreArg.remove(0, 13);
+ restoreArg.prepend("trash:");
+ }
+
+ KURL trashURL( restoreArg );
+ if ( !trashURL.isValid() || trashURL.protocol() != "trash" ) {
+ kdError() << "Invalid URL for restoring a trashed file:" << trashURL << endl;
+ return 1;
+ }
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)3 << trashURL;
+ KIO::Job* job = KIO::special( trashURL, packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ if ( !ok )
+ kdError() << KIO::NetAccess::lastErrorString() << endl;
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/kioslave/trash/testtrash.cpp b/kioslave/trash/testtrash.cpp
new file mode 100644
index 000000000..4557c5031
--- /dev/null
+++ b/kioslave/trash/testtrash.cpp
@@ -0,0 +1,1198 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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.
+*/
+
+// Get those asserts to work
+#undef NDEBUG
+#undef NO_DEBUG
+
+#include "kio_trash.h"
+#include "testtrash.h"
+
+#include <config.h>
+
+#include <kurl.h>
+#include <klocale.h>
+#include <kapplication.h>
+#include <kio/netaccess.h>
+#include <kio/job.h>
+#include <kdebug.h>
+#include <kcmdlineargs.h>
+
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qvaluevector.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <assert.h>
+#include <kfileitem.h>
+#include <kstandarddirs.h>
+
+static bool check(const QString& txt, QString a, QString b)
+{
+ if (a.isEmpty())
+ a = QString::null;
+ if (b.isEmpty())
+ b = QString::null;
+ if (a == b) {
+ kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "ok" << endl;
+ }
+ else {
+ kdDebug() << txt << " : checking '" << a << "' against expected value '" << b << "'... " << "KO !" << endl;
+ exit(1);
+ }
+ return true;
+}
+
+// There are two ways to test encoding things:
+// * with utf8 filenames
+// * with latin1 filenames
+//
+//#define UTF8TEST 1
+
+int main(int argc, char *argv[])
+{
+ // Ensure a known QFile::encodeName behavior for trashUtf8FileFromHome
+ // However this assume your $HOME doesn't use characters from other locales...
+ setenv( "LC_ALL", "en_GB.ISO-8859-1", 1 );
+#ifdef UTF8TEST
+ setenv( "KDE_UTF8_FILENAMES", "true", 1 );
+#else
+ unsetenv( "KDE_UTF8_FILENAMES" );
+#endif
+
+ // Use another directory than the real one, just to keep things clean
+ setenv( "XDG_DATA_HOME", QFile::encodeName( QDir::homeDirPath() + "/.local-testtrash" ), true );
+ setenv( "KDE_FORK_SLAVES", "yes", true );
+
+ KApplication::disableAutoDcopRegistration();
+ KCmdLineArgs::init(argc,argv,"testtrash", 0, 0, 0, 0);
+ KApplication app;
+
+ TestTrash test;
+ test.setup();
+ test.runAll();
+ kdDebug() << "All tests OK." << endl;
+ return 0; // success. The exit(1) in check() is what happens in case of failure.
+}
+
+QString TestTrash::homeTmpDir() const
+{
+ return QDir::homeDirPath() + "/.kde/testtrash/";
+}
+
+QString TestTrash::readOnlyDirPath() const
+{
+ return homeTmpDir() + QString( "readonly" );
+}
+
+QString TestTrash::otherTmpDir() const
+{
+ // This one needs to be on another partition
+ return "/tmp/testtrash/";
+}
+
+QString TestTrash::utf8FileName() const
+{
+ return QString( "test" ) + QChar( 0x2153 ); // "1/3" character, not part of latin1
+}
+
+QString TestTrash::umlautFileName() const
+{
+ return QString( "umlaut" ) + QChar( 0xEB );
+}
+
+static void removeFile( const QString& trashDir, const QString& fileName )
+{
+ QDir dir;
+ dir.remove( trashDir + fileName );
+ assert( !QDir( trashDir + fileName ).exists() );
+}
+
+static void removeDir( const QString& trashDir, const QString& dirName )
+{
+ QDir dir;
+ dir.rmdir( trashDir + dirName );
+ assert( !QDir( trashDir + dirName ).exists() );
+}
+
+void TestTrash::setup()
+{
+ m_trashDir = KGlobal::dirs()->localxdgdatadir() + "Trash";
+ kdDebug() << "setup: using trash directory " << m_trashDir << endl;
+
+ // Look for another writable partition than $HOME (not mandatory)
+ TrashImpl impl;
+ impl.init();
+
+ TrashImpl::TrashDirMap trashDirs = impl.trashDirectories();
+ TrashImpl::TrashDirMap topDirs = impl.topDirectories();
+ bool foundTrashDir = false;
+ m_otherPartitionId = 0;
+ m_tmpIsWritablePartition = false;
+ m_tmpTrashId = -1;
+ QValueVector<int> writableTopDirs;
+ for ( TrashImpl::TrashDirMap::ConstIterator it = trashDirs.begin(); it != trashDirs.end() ; ++it ) {
+ if ( it.key() == 0 ) {
+ assert( it.data() == m_trashDir );
+ assert( topDirs.find( 0 ) == topDirs.end() );
+ foundTrashDir = true;
+ } else {
+ assert( topDirs.find( it.key() ) != topDirs.end() );
+ const QString topdir = topDirs[it.key()];
+ if ( QFileInfo( topdir ).isWritable() ) {
+ writableTopDirs.append( it.key() );
+ if ( topdir == "/tmp/" ) {
+ m_tmpIsWritablePartition = true;
+ m_tmpTrashId = it.key();
+ kdDebug() << "/tmp is on its own partition (trashid=" << m_tmpTrashId << "), some tests will be skipped" << endl;
+ removeFile( it.data(), "/info/fileFromOther.trashinfo" );
+ removeFile( it.data(), "/files/fileFromOther" );
+ removeFile( it.data(), "/info/symlinkFromOther.trashinfo" );
+ removeFile( it.data(), "/files/symlinkFromOther" );
+ removeFile( it.data(), "/info/trashDirFromOther.trashinfo" );
+ removeFile( it.data(), "/files/trashDirFromOther/testfile" );
+ removeDir( it.data(), "/files/trashDirFromOther" );
+ }
+ }
+ }
+ }
+ for ( QValueVector<int>::const_iterator it = writableTopDirs.begin(); it != writableTopDirs.end(); ++it ) {
+ const QString topdir = topDirs[ *it ];
+ const QString trashdir = trashDirs[ *it ];
+ assert( !topdir.isEmpty() );
+ assert( !trashDirs.isEmpty() );
+ if ( topdir != "/tmp/" || // we'd prefer not to use /tmp here, to separate the tests
+ ( writableTopDirs.count() > 1 ) ) // but well, if we have no choice, take it
+ {
+ m_otherPartitionTopDir = topdir;
+ m_otherPartitionTrashDir = trashdir;
+ m_otherPartitionId = *it;
+ kdDebug() << "OK, found another writable partition: topDir=" << m_otherPartitionTopDir
+ << " trashDir=" << m_otherPartitionTrashDir << " id=" << m_otherPartitionId << endl;
+ break;
+ }
+ }
+ // Check that m_trashDir got listed
+ assert( foundTrashDir );
+ if ( m_otherPartitionTrashDir.isEmpty() )
+ kdWarning() << "No writable partition other than $HOME found, some tests will be skipped" << endl;
+
+ // Start with a clean base dir
+ if ( QFileInfo( homeTmpDir() ).exists() ) {
+ bool ok = KIO::NetAccess::del( homeTmpDir(), 0 );
+ if ( !ok )
+ kdFatal() << "Couldn't delete " << homeTmpDir() << endl;
+ }
+ if ( QFileInfo( otherTmpDir() ).exists() ) {
+ bool ok = KIO::NetAccess::del( otherTmpDir(), 0 );
+ if ( !ok )
+ kdFatal() << "Couldn't delete " << otherTmpDir() << endl;
+ }
+ QDir dir; // TT: why not a static method?
+ bool ok = dir.mkdir( homeTmpDir() );
+ if ( !ok )
+ kdFatal() << "Couldn't create " << homeTmpDir() << endl;
+ ok = dir.mkdir( otherTmpDir() );
+ if ( !ok )
+ kdFatal() << "Couldn't create " << otherTmpDir() << endl;
+ cleanTrash();
+}
+
+
+void TestTrash::cleanTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ // Start with a relatively clean trash too
+ removeFile( m_trashDir, "/info/fileFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromHome" );
+ removeFile( m_trashDir, "/info/fileFromHome_1.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromHome_1" );
+ removeFile( m_trashDir, "/info/file%2f.trashinfo" );
+ removeFile( m_trashDir, "/files/file%2f" );
+ removeFile( m_trashDir, "/info/" + utf8FileName() + ".trashinfo" );
+ removeFile( m_trashDir, "/files/" + utf8FileName() );
+ removeFile( m_trashDir, "/info/" + umlautFileName() + ".trashinfo" );
+ removeFile( m_trashDir, "/files/" + umlautFileName() );
+ removeFile( m_trashDir, "/info/fileFromOther.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromOther" );
+ removeFile( m_trashDir, "/info/symlinkFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/symlinkFromHome" );
+ removeFile( m_trashDir, "/info/symlinkFromOther.trashinfo" );
+ removeFile( m_trashDir, "/files/symlinkFromOther" );
+ removeFile( m_trashDir, "/info/brokenSymlinkFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/brokenSymlinkFromHome" );
+ removeFile( m_trashDir, "/info/trashDirFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/trashDirFromHome/testfile" );
+ removeFile( m_trashDir, "/info/readonly.trashinfo" );
+ removeDir( m_trashDir, "/files/trashDirFromHome" );
+ removeFile( m_trashDir, "/info/trashDirFromHome_1.trashinfo" );
+ removeFile( m_trashDir, "/files/trashDirFromHome_1/testfile" );
+ removeDir( m_trashDir, "/files/trashDirFromHome_1" );
+ removeFile( m_trashDir, "/info/trashDirFromOther.trashinfo" );
+ removeFile( m_trashDir, "/files/trashDirFromOther/testfile" );
+ removeDir( m_trashDir, "/files/trashDirFromOther" );
+ KIO::NetAccess::del( m_trashDir + "/files/readonly", 0 );
+ // for trashDirectoryOwnedByRoot
+ KIO::NetAccess::del( m_trashDir + "/files/cups", 0 );
+ KIO::NetAccess::del( m_trashDir + "/files/boot", 0 );
+ KIO::NetAccess::del( m_trashDir + "/files/etc", 0 );
+
+ //system( "find ~/.local-testtrash/share/Trash" );
+}
+
+void TestTrash::runAll()
+{
+ urlTestFile();
+ urlTestDirectory();
+ urlTestSubDirectory();
+
+ trashFileFromHome();
+ trashPercentFileFromHome();
+#ifdef UTF8TEST
+ trashUtf8FileFromHome();
+#endif
+ trashUmlautFileFromHome();
+ trashReadOnlyDirFromHome();
+ testTrashNotEmpty();
+ trashFileFromOther();
+ trashFileIntoOtherPartition();
+ trashFileOwnedByRoot();
+ trashSymlinkFromHome();
+ trashSymlinkFromOther();
+ trashBrokenSymlinkFromHome();
+ trashDirectoryFromHome();
+ trashDirectoryFromOther();
+ trashDirectoryOwnedByRoot();
+
+ tryRenameInsideTrash();
+
+ statRoot();
+ statFileInRoot();
+ statDirectoryInRoot();
+ statSymlinkInRoot();
+ statFileInDirectory();
+
+ copyFileFromTrash();
+ // To test case of already-existing destination, uncomment this.
+ // This brings up the "rename" dialog though, so it can't be fully automated
+ //copyFileFromTrash();
+ copyFileInDirectoryFromTrash();
+ copyDirectoryFromTrash();
+ copySymlinkFromTrash();
+
+ moveFileFromTrash();
+ moveFileInDirectoryFromTrash();
+ moveDirectoryFromTrash();
+ moveSymlinkFromTrash();
+
+ listRootDir();
+ listRecursiveRootDir();
+ listSubDir();
+
+ delRootFile();
+ delFileInDirectory();
+ delDirectory();
+
+ getFile();
+ restoreFile();
+ restoreFileFromSubDir();
+ restoreFileToDeletedDirectory();
+
+ emptyTrash();
+
+ // TODO: test
+ // - trash migration
+ // - the actual updating of the trash icon on the desktop
+}
+
+void TestTrash::urlTestFile()
+{
+ const KURL url = TrashImpl::makeURL( 1, "fileId", QString::null );
+ check( "makeURL for a file", url.url(), "trash:/1-fileId" );
+
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ assert( ok );
+ check( "parseURL: trashId", QString::number( trashId ), "1" );
+ check( "parseURL: fileId", fileId, "fileId" );
+ check( "parseURL: relativePath", relativePath, QString::null );
+}
+
+void TestTrash::urlTestDirectory()
+{
+ const KURL url = TrashImpl::makeURL( 1, "fileId", "subfile" );
+ check( "makeURL", url.url(), "trash:/1-fileId/subfile" );
+
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ assert( ok );
+ check( "parseURL: trashId", QString::number( trashId ), "1" );
+ check( "parseURL: fileId", fileId, "fileId" );
+ check( "parseURL: relativePath", relativePath, "subfile" );
+}
+
+void TestTrash::urlTestSubDirectory()
+{
+ const KURL url = TrashImpl::makeURL( 1, "fileId", "subfile/foobar" );
+ check( "makeURL", url.url(), "trash:/1-fileId/subfile/foobar" );
+
+ int trashId;
+ QString fileId;
+ QString relativePath;
+ bool ok = TrashImpl::parseURL( url, trashId, fileId, relativePath );
+ assert( ok );
+ check( "parseURL: trashId", QString::number( trashId ), "1" );
+ check( "parseURL: fileId", fileId, "fileId" );
+ check( "parseURL: relativePath", relativePath, "subfile/foobar" );
+}
+
+static void checkInfoFile( const QString& infoPath, const QString& origFilePath )
+{
+ kdDebug() << k_funcinfo << infoPath << endl;
+ QFileInfo info( infoPath );
+ assert( info.exists() );
+ assert( info.isFile() );
+ KSimpleConfig infoFile( info.absFilePath(), true );
+ if ( !infoFile.hasGroup( "Trash Info" ) )
+ kdFatal() << "no Trash Info group in " << info.absFilePath() << endl;
+ infoFile.setGroup( "Trash Info" );
+ const QString origPath = infoFile.readEntry( "Path" );
+ assert( !origPath.isEmpty() );
+ assert( origPath == KURL::encode_string( origFilePath, KGlobal::locale()->fileEncodingMib() ) );
+ const QString date = infoFile.readEntry( "DeletionDate" );
+ assert( !date.isEmpty() );
+ assert( date.contains( "T" ) );
+}
+
+static void createTestFile( const QString& path )
+{
+ QFile f( path );
+ if ( !f.open( IO_WriteOnly ) )
+ kdFatal() << "Can't create " << path << endl;
+ f.writeBlock( "Hello world\n", 12 );
+ f.close();
+ assert( QFile::exists( path ) );
+}
+
+void TestTrash::trashFile( const QString& origFilePath, const QString& fileId )
+{
+ // setup
+ if ( !QFile::exists( origFilePath ) )
+ createTestFile( origFilePath );
+ KURL u;
+ u.setPath( origFilePath );
+
+ // test
+ KIO::Job* job = KIO::move( u, "trash:/" );
+ QMap<QString, QString> metaData;
+ //bool ok = KIO::NetAccess::move( u, "trash:/" );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ if ( !ok )
+ kdError() << "moving " << u << " to trash failed with error " << KIO::NetAccess::lastError() << " " << KIO::NetAccess::lastErrorString() << endl;
+ assert( ok );
+ if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
+ kdDebug() << " TESTS SKIPPED" << endl;
+ } else {
+ checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( files.isFile() );
+ assert( files.size() == 12 );
+ }
+
+ // coolo suggests testing that the original file is actually gone, too :)
+ assert( !QFile::exists( origFilePath ) );
+
+ assert( !metaData.isEmpty() );
+ bool found = false;
+ QMap<QString, QString>::ConstIterator it = metaData.begin();
+ for ( ; it != metaData.end() ; ++it ) {
+ if ( it.key().startsWith( "trashURL" ) ) {
+ const QString origPath = it.key().mid( 9 );
+ KURL trashURL( it.data() );
+ kdDebug() << trashURL << endl;
+ assert( !trashURL.isEmpty() );
+ assert( trashURL.protocol() == "trash" );
+ int trashId = 0;
+ if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition )
+ trashId = m_tmpTrashId;
+ assert( trashURL.path() == "/" + QString::number( trashId ) + "-" + fileId );
+ found = true;
+ }
+ }
+ assert( found );
+}
+
+void TestTrash::trashFileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "fileFromHome";
+ trashFile( homeTmpDir() + fileName, fileName );
+
+ // Do it again, check that we got a different id
+ trashFile( homeTmpDir() + fileName, fileName + "_1" );
+}
+
+void TestTrash::trashPercentFileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "file%2f";
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::trashUtf8FileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = utf8FileName();
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::trashUmlautFileFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = umlautFileName();
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::testTrashNotEmpty()
+{
+ KSimpleConfig cfg( "trashrc", true );
+ assert( cfg.hasGroup( "Status" ) );
+ cfg.setGroup( "Status" );
+ assert( cfg.readBoolEntry( "Empty", true ) == false );
+}
+
+void TestTrash::trashFileFromOther()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "fileFromOther";
+ trashFile( otherTmpDir() + fileName, fileName );
+}
+
+void TestTrash::trashFileIntoOtherPartition()
+{
+ if ( m_otherPartitionTrashDir.isEmpty() ) {
+ kdDebug() << k_funcinfo << " - SKIPPED" << endl;
+ return;
+ }
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "testtrash-file";
+ const QString origFilePath = m_otherPartitionTopDir + fileName;
+ const QString fileId = fileName;
+ // cleanup
+ QFile::remove( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo" );
+ QFile::remove( m_otherPartitionTrashDir + "/files/" + fileId );
+
+ // setup
+ if ( !QFile::exists( origFilePath ) )
+ createTestFile( origFilePath );
+ KURL u;
+ u.setPath( origFilePath );
+
+ // test
+ KIO::Job* job = KIO::move( u, "trash:/" );
+ QMap<QString, QString> metaData;
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ assert( ok );
+ // Note that the Path stored in the info file is relative, on other partitions (#95652)
+ checkInfoFile( m_otherPartitionTrashDir + "/info/" + fileId + ".trashinfo", fileName );
+
+ QFileInfo files( m_otherPartitionTrashDir + "/files/" + fileId );
+ assert( files.isFile() );
+ assert( files.size() == 12 );
+
+ // coolo suggests testing that the original file is actually gone, too :)
+ assert( !QFile::exists( origFilePath ) );
+
+ assert( !metaData.isEmpty() );
+ bool found = false;
+ QMap<QString, QString>::ConstIterator it = metaData.begin();
+ for ( ; it != metaData.end() ; ++it ) {
+ if ( it.key().startsWith( "trashURL" ) ) {
+ const QString origPath = it.key().mid( 9 );
+ KURL trashURL( it.data() );
+ kdDebug() << trashURL << endl;
+ assert( !trashURL.isEmpty() );
+ assert( trashURL.protocol() == "trash" );
+ assert( trashURL.path() == QString( "/%1-%2" ).arg( m_otherPartitionId ).arg( fileId ) );
+ found = true;
+ }
+ }
+ assert( found );
+}
+
+void TestTrash::trashFileOwnedByRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL u;
+ u.setPath( "/etc/passwd" );
+ const QString fileId = "passwd";
+
+ KIO::CopyJob* job = KIO::move( u, "trash:/" );
+ job->setInteractive( false ); // no skip dialog, thanks
+ QMap<QString, QString> metaData;
+ //bool ok = KIO::NetAccess::move( u, "trash:/" );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ assert( !ok );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
+ const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( !QFile::exists( infoPath ) );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( !files.exists() );
+
+ assert( QFile::exists( u.path() ) );
+}
+
+void TestTrash::trashSymlink( const QString& origFilePath, const QString& fileId, bool broken )
+{
+ kdDebug() << k_funcinfo << endl;
+ // setup
+ const char* target = broken ? "/nonexistent" : "/tmp";
+ bool ok = ::symlink( target, QFile::encodeName( origFilePath ) ) == 0;
+ assert( ok );
+ KURL u;
+ u.setPath( origFilePath );
+
+ // test
+ ok = KIO::NetAccess::move( u, "trash:/" );
+ assert( ok );
+ if ( origFilePath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
+ kdDebug() << " TESTS SKIPPED" << endl;
+ return;
+ }
+ checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origFilePath );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( files.isSymLink() );
+ assert( files.readLink() == QFile::decodeName( target ) );
+ assert( !QFile::exists( origFilePath ) );
+}
+
+void TestTrash::trashSymlinkFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "symlinkFromHome";
+ trashSymlink( homeTmpDir() + fileName, fileName, false );
+}
+
+void TestTrash::trashSymlinkFromOther()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "symlinkFromOther";
+ trashSymlink( otherTmpDir() + fileName, fileName, false );
+}
+
+void TestTrash::trashBrokenSymlinkFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileName = "brokenSymlinkFromHome";
+ trashSymlink( homeTmpDir() + fileName, fileName, true );
+}
+
+void TestTrash::trashDirectory( const QString& origPath, const QString& fileId )
+{
+ kdDebug() << k_funcinfo << fileId << endl;
+ // setup
+ if ( !QFileInfo( origPath ).exists() ) {
+ QDir dir;
+ bool ok = dir.mkdir( origPath );
+ Q_ASSERT( ok );
+ }
+ createTestFile( origPath + "/testfile" );
+ KURL u; u.setPath( origPath );
+
+ // test
+ bool ok = KIO::NetAccess::move( u, "trash:/" );
+ assert( ok );
+ if ( origPath.startsWith( "/tmp" ) && m_tmpIsWritablePartition ) {
+ kdDebug() << " TESTS SKIPPED" << endl;
+ return;
+ }
+ checkInfoFile( m_trashDir + "/info/" + fileId + ".trashinfo", origPath );
+
+ QFileInfo filesDir( m_trashDir + "/files/" + fileId );
+ assert( filesDir.isDir() );
+ QFileInfo files( m_trashDir + "/files/" + fileId + "/testfile" );
+ assert( files.exists() );
+ assert( files.isFile() );
+ assert( files.size() == 12 );
+ assert( !QFile::exists( origPath ) );
+}
+
+void TestTrash::trashDirectoryFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ QString dirName = "trashDirFromHome";
+ trashDirectory( homeTmpDir() + dirName, dirName );
+ // Do it again, check that we got a different id
+ trashDirectory( homeTmpDir() + dirName, dirName + "_1" );
+}
+
+void TestTrash::trashReadOnlyDirFromHome()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString dirName = readOnlyDirPath();
+ QDir dir;
+ bool ok = dir.mkdir( dirName );
+ Q_ASSERT( ok );
+ // #130780
+ const QString subDirPath = dirName + "/readonly_subdir";
+ ok = dir.mkdir( subDirPath );
+ Q_ASSERT( ok );
+ createTestFile( subDirPath + "/testfile_in_subdir" );
+ ::chmod( QFile::encodeName( subDirPath ), 0500 );
+
+ trashDirectory( dirName, "readonly" );
+}
+
+void TestTrash::trashDirectoryFromOther()
+{
+ kdDebug() << k_funcinfo << endl;
+ QString dirName = "trashDirFromOther";
+ trashDirectory( otherTmpDir() + dirName, dirName );
+}
+
+void TestTrash::tryRenameInsideTrash()
+{
+ kdDebug() << k_funcinfo << " with file_move" << endl;
+ bool worked = KIO::NetAccess::file_move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" );
+ assert( !worked );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME );
+
+ kdDebug() << k_funcinfo << " with move" << endl;
+ worked = KIO::NetAccess::move( "trash:/0-tryRenameInsideTrash", "trash:/foobar" );
+ assert( !worked );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_CANNOT_RENAME );
+}
+
+void TestTrash::delRootFile()
+{
+ kdDebug() << k_funcinfo << endl;
+
+ // test deleting a trashed file
+ bool ok = KIO::NetAccess::del( "trash:/0-fileFromHome", 0L );
+ assert( ok );
+
+ QFileInfo file( m_trashDir + "/files/fileFromHome" );
+ assert( !file.exists() );
+ QFileInfo info( m_trashDir + "/info/fileFromHome.trashinfo" );
+ assert( !info.exists() );
+
+ // trash it again, we might need it later
+ const QString fileName = "fileFromHome";
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::delFileInDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+
+ // test deleting a file inside a trashed directory -> not allowed
+ bool ok = KIO::NetAccess::del( "trash:/0-trashDirFromHome/testfile", 0L );
+ assert( !ok );
+ assert( KIO::NetAccess::lastError() == KIO::ERR_ACCESS_DENIED );
+
+ QFileInfo dir( m_trashDir + "/files/trashDirFromHome" );
+ assert( dir.exists() );
+ QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" );
+ assert( file.exists() );
+ QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" );
+ assert( info.exists() );
+}
+
+void TestTrash::delDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+
+ // test deleting a trashed directory
+ bool ok = KIO::NetAccess::del( "trash:/0-trashDirFromHome", 0L );
+ assert( ok );
+
+ QFileInfo dir( m_trashDir + "/files/trashDirFromHome" );
+ assert( !dir.exists() );
+ QFileInfo file( m_trashDir + "/files/trashDirFromHome/testfile" );
+ assert( !file.exists() );
+ QFileInfo info( m_trashDir + "/info/trashDirFromHome.trashinfo" );
+ assert( !info.exists() );
+
+ // trash it again, we'll need it later
+ QString dirName = "trashDirFromHome";
+ trashDirectory( homeTmpDir() + dirName, dirName );
+}
+
+void TestTrash::statRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isDir() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "." );
+ assert( item.acceptsDrops() );
+}
+
+void TestTrash::statFileInRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-fileFromHome" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isFile() );
+ assert( !item.isDir() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "fileFromHome" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::statDirectoryInRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-trashDirFromHome" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isDir() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "trashDirFromHome" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::statSymlinkInRoot()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-symlinkFromHome" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isLink() );
+ assert( item.linkDest() == "/tmp" );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "symlinkFromHome" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::statFileInDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+ KURL url( "trash:/0-trashDirFromHome/testfile" );
+ KIO::UDSEntry entry;
+ bool ok = KIO::NetAccess::stat( url, entry, 0 );
+ assert( ok );
+ KFileItem item( entry, url );
+ assert( item.isFile() );
+ assert( !item.isLink() );
+ assert( item.isReadable() );
+ assert( !item.isWritable() );
+ assert( !item.isHidden() );
+ assert( item.name() == "testfile" );
+ assert( !item.acceptsDrops() );
+}
+
+void TestTrash::copyFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath )
+{
+ KURL src( "trash:/0-" + fileId );
+ if ( !relativePath.isEmpty() )
+ src.addPath( relativePath );
+ KURL dest;
+ dest.setPath( destPath );
+
+ assert( KIO::NetAccess::exists( src, true, (QWidget*)0 ) );
+
+ // A dnd would use copy(), but we use copyAs to ensure the final filename
+ //kdDebug() << k_funcinfo << "copyAs:" << src << " -> " << dest << endl;
+ KIO::Job* job = KIO::copyAs( src, dest );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( QFile::exists( infoFile ) );
+
+ QFileInfo filesItem( m_trashDir + "/files/" + fileId );
+ assert( filesItem.exists() );
+
+ assert( QFile::exists( destPath ) );
+}
+
+void TestTrash::copyFileFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "fileFromHome_copied";
+ copyFromTrash( "fileFromHome", destPath );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+}
+
+void TestTrash::copyFileInDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "testfile_copied";
+ copyFromTrash( "trashDirFromHome", destPath, "testfile" );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+}
+
+void TestTrash::copyDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "trashDirFromHome_copied";
+ copyFromTrash( "trashDirFromHome", destPath );
+ assert( QFileInfo( destPath ).isDir() );
+}
+
+void TestTrash::copySymlinkFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "symlinkFromHome_copied";
+ copyFromTrash( "symlinkFromHome", destPath );
+ assert( QFileInfo( destPath ).isSymLink() );
+}
+
+void TestTrash::moveFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath )
+{
+ KURL src( "trash:/0-" + fileId );
+ if ( !relativePath.isEmpty() )
+ src.addPath( relativePath );
+ KURL dest;
+ dest.setPath( destPath );
+
+ assert( KIO::NetAccess::exists( src, true, (QWidget*)0 ) );
+
+ // A dnd would use move(), but we use moveAs to ensure the final filename
+ KIO::Job* job = KIO::moveAs( src, dest );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( !QFile::exists( infoFile ) );
+
+ QFileInfo filesItem( m_trashDir + "/files/" + fileId );
+ assert( !filesItem.exists() );
+
+ assert( QFile::exists( destPath ) );
+}
+
+void TestTrash::moveFileFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "fileFromHome_restored";
+ moveFromTrash( "fileFromHome", destPath );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+
+ // trash it again for later
+ const QString fileName = "fileFromHome";
+ trashFile( homeTmpDir() + fileName, fileName );
+}
+
+void TestTrash::moveFileInDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "testfile_restored";
+ copyFromTrash( "trashDirFromHome", destPath, "testfile" );
+ assert( QFileInfo( destPath ).isFile() );
+ assert( QFileInfo( destPath ).size() == 12 );
+}
+
+void TestTrash::moveDirectoryFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "trashDirFromHome_restored";
+ moveFromTrash( "trashDirFromHome", destPath );
+ assert( QFileInfo( destPath ).isDir() );
+
+ // trash it again, we'll need it later
+ QString dirName = "trashDirFromHome";
+ trashDirectory( homeTmpDir() + dirName, dirName );
+}
+
+void TestTrash::trashDirectoryOwnedByRoot()
+{
+ KURL u;
+ if ( QFile::exists( "/etc/cups" ) )
+ u.setPath( "/etc/cups" );
+ else if ( QFile::exists( "/boot" ) )
+ u.setPath( "/boot" );
+ else
+ u.setPath( "/etc" );
+ const QString fileId = u.path();
+ kdDebug() << k_funcinfo << "fileId=" << fileId << endl;
+
+ KIO::CopyJob* job = KIO::move( u, "trash:/" );
+ job->setInteractive( false ); // no skip dialog, thanks
+ QMap<QString, QString> metaData;
+ bool ok = KIO::NetAccess::synchronousRun( job, 0, 0, 0, &metaData );
+ assert( !ok );
+ const int err = KIO::NetAccess::lastError();
+ assert( err == KIO::ERR_ACCESS_DENIED
+ || err == KIO::ERR_CANNOT_OPEN_FOR_READING );
+
+ const QString infoPath( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ assert( !QFile::exists( infoPath ) );
+
+ QFileInfo files( m_trashDir + "/files/" + fileId );
+ assert( !files.exists() );
+
+ assert( QFile::exists( u.path() ) );
+}
+
+void TestTrash::moveSymlinkFromTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString destPath = otherTmpDir() + "symlinkFromHome_restored";
+ moveFromTrash( "symlinkFromHome", destPath );
+ assert( QFileInfo( destPath ).isSymLink() );
+}
+
+void TestTrash::getFile()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileId = "fileFromHome_1";
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ QString tmpFile;
+ bool ok = KIO::NetAccess::download( url, tmpFile, 0 );
+ assert( ok );
+ QFile file( tmpFile );
+ ok = file.open( IO_ReadOnly );
+ assert( ok );
+ QByteArray str = file.readAll();
+ QCString cstr( str.data(), str.size() + 1 );
+ if ( cstr != "Hello world\n" )
+ kdFatal() << "get() returned the following data:" << cstr << endl;
+ file.close();
+ KIO::NetAccess::removeTempFile( tmpFile );
+}
+
+void TestTrash::restoreFile()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileId = "fileFromHome_1";
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ const QString filesItem( m_trashDir + "/files/" + fileId );
+
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)3 << url;
+ KIO::Job* job = KIO::special( url, packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+
+ assert( !QFile::exists( infoFile ) );
+ assert( !QFile::exists( filesItem ) );
+
+ const QString destPath = homeTmpDir() + "fileFromHome";
+ assert( QFile::exists( destPath ) );
+}
+
+void TestTrash::restoreFileFromSubDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString fileId = "trashDirFromHome_1/testfile";
+ assert( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
+
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ const QString infoFile( m_trashDir + "/info/trashDirFromHome_1.trashinfo" );
+ const QString filesItem( m_trashDir + "/files/trashDirFromHome_1/testfile" );
+
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)3 << url;
+ KIO::Job* job = KIO::special( url, packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( !ok );
+ // dest dir doesn't exist -> error message
+ assert( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED );
+
+ // check that nothing happened
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+ assert( !QFile::exists( homeTmpDir() + "trashDirFromHome_1" ) );
+}
+
+void TestTrash::restoreFileToDeletedDirectory()
+{
+ kdDebug() << k_funcinfo << endl;
+ // Ensure we'll get "fileFromHome" as fileId
+ removeFile( m_trashDir, "/info/fileFromHome.trashinfo" );
+ removeFile( m_trashDir, "/files/fileFromHome" );
+ trashFileFromHome();
+ // Delete orig dir
+ bool delOK = KIO::NetAccess::del( homeTmpDir(), 0 );
+ assert( delOK );
+
+ const QString fileId = "fileFromHome";
+ const KURL url = TrashImpl::makeURL( 0, fileId, QString::null );
+ const QString infoFile( m_trashDir + "/info/" + fileId + ".trashinfo" );
+ const QString filesItem( m_trashDir + "/files/" + fileId );
+
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)3 << url;
+ KIO::Job* job = KIO::special( url, packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( !ok );
+ // dest dir doesn't exist -> error message
+ assert( KIO::NetAccess::lastError() == KIO::ERR_SLAVE_DEFINED );
+
+ // check that nothing happened
+ assert( QFile::exists( infoFile ) );
+ assert( QFile::exists( filesItem ) );
+
+ const QString destPath = homeTmpDir() + "fileFromHome";
+ assert( !QFile::exists( destPath ) );
+}
+
+void TestTrash::listRootDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_entryCount = 0;
+ m_listResult.clear();
+ KIO::ListJob* job = KIO::listDir( "trash:/" );
+ connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
+ SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl;
+ assert( m_entryCount > 1 );
+
+ kdDebug() << k_funcinfo << m_listResult << endl;
+ assert( m_listResult.contains( "." ) == 1 ); // found it, and only once
+}
+
+void TestTrash::listRecursiveRootDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_entryCount = 0;
+ m_listResult.clear();
+ KIO::ListJob* job = KIO::listRecursive( "trash:/" );
+ connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
+ SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl;
+ assert( m_entryCount > 1 );
+
+ kdDebug() << k_funcinfo << m_listResult << endl;
+ assert( m_listResult.contains( "." ) == 1 ); // found it, and only once
+}
+
+void TestTrash::listSubDir()
+{
+ kdDebug() << k_funcinfo << endl;
+ m_entryCount = 0;
+ m_listResult.clear();
+ KIO::ListJob* job = KIO::listDir( "trash:/0-trashDirFromHome" );
+ connect( job, SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList& ) ),
+ SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& ) ) );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+ kdDebug() << "listDir done - m_entryCount=" << m_entryCount << endl;
+ assert( m_entryCount == 2 );
+
+ kdDebug() << k_funcinfo << m_listResult << endl;
+ assert( m_listResult.contains( "." ) == 1 ); // found it, and only once
+ assert( m_listResult.contains( "testfile" ) == 1 ); // found it, and only once
+}
+
+void TestTrash::slotEntries( KIO::Job*, const KIO::UDSEntryList& lst )
+{
+ for( KIO::UDSEntryList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
+ KIO::UDSEntry::ConstIterator it2 = (*it).begin();
+ QString displayName;
+ KURL url;
+ for( ; it2 != (*it).end(); it2++ ) {
+ switch ((*it2).m_uds) {
+ case KIO::UDS_NAME:
+ displayName = (*it2).m_str;
+ break;
+ case KIO::UDS_URL:
+ url = (*it2).m_str;
+ break;
+ }
+ }
+ kdDebug() << k_funcinfo << displayName << " " << url << endl;
+ if ( !url.isEmpty() ) {
+ assert( url.protocol() == "trash" );
+ }
+ m_listResult << displayName;
+ }
+ m_entryCount += lst.count();
+}
+
+void TestTrash::emptyTrash()
+{
+ // ## Even though we use a custom XDG_DATA_HOME value, emptying the
+ // trash would still empty the other trash directories in other partitions.
+ // So we can't activate this test by default.
+#if 0
+ kdDebug() << k_funcinfo << endl;
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)1;
+ KIO::Job* job = KIO::special( "trash:/", packedArgs );
+ bool ok = KIO::NetAccess::synchronousRun( job, 0 );
+ assert( ok );
+
+ KSimpleConfig cfg( "trashrc", true );
+ assert( cfg.hasGroup( "Status" ) );
+ cfg.setGroup( "Status" );
+ assert( cfg.readBoolEntry( "Empty", false ) == true );
+
+ assert( !QFile::exists( m_trashDir + "/files/fileFromHome" ) );
+ assert( !QFile::exists( m_trashDir + "/files/readonly" ) );
+ assert( !QFile::exists( m_trashDir + "/info/readonly.trashinfo" ) );
+
+#else
+ kdDebug() << k_funcinfo << " : SKIPPED" << endl;
+#endif
+}
+
+#include "testtrash.moc"
diff --git a/kioslave/trash/testtrash.h b/kioslave/trash/testtrash.h
new file mode 100644
index 000000000..70d06dc8b
--- /dev/null
+++ b/kioslave/trash/testtrash.h
@@ -0,0 +1,118 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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.
+*/
+
+#ifndef TESTTRASH_H
+#define TESTTRASH_H
+
+#include <qobject.h>
+
+class TestTrash : public QObject
+{
+ Q_OBJECT
+
+public:
+ TestTrash() {}
+ void setup();
+ void cleanTrash();
+ void runAll();
+
+ // tests
+
+ void urlTestFile();
+ void urlTestDirectory();
+ void urlTestSubDirectory();
+
+ void trashFileFromHome();
+ void trashPercentFileFromHome();
+ void trashUtf8FileFromHome();
+ void trashUmlautFileFromHome();
+ void testTrashNotEmpty();
+ void trashFileFromOther();
+ void trashFileIntoOtherPartition();
+ void trashFileOwnedByRoot();
+ void trashSymlinkFromHome();
+ void trashSymlinkFromOther();
+ void trashBrokenSymlinkFromHome();
+ void trashDirectoryFromHome();
+ void trashReadOnlyDirFromHome();
+ void trashDirectoryFromOther();
+ void trashDirectoryOwnedByRoot();
+
+ void tryRenameInsideTrash();
+
+ void statRoot();
+ void statFileInRoot();
+ void statDirectoryInRoot();
+ void statSymlinkInRoot();
+ void statFileInDirectory();
+
+ void copyFileFromTrash();
+ void copyFileInDirectoryFromTrash();
+ void copyDirectoryFromTrash();
+ void copySymlinkFromTrash();
+
+ void moveFileFromTrash();
+ void moveFileInDirectoryFromTrash();
+ void moveDirectoryFromTrash();
+ void moveSymlinkFromTrash();
+
+ void listRootDir();
+ void listRecursiveRootDir();
+ void listSubDir();
+
+ void delRootFile();
+ void delFileInDirectory();
+ void delDirectory();
+
+ void getFile();
+ void restoreFile();
+ void restoreFileFromSubDir();
+ void restoreFileToDeletedDirectory();
+
+ void emptyTrash();
+
+private slots:
+ void slotEntries( KIO::Job*, const KIO::UDSEntryList& );
+
+private:
+ void trashFile( const QString& origFilePath, const QString& fileId );
+ void trashSymlink( const QString& origFilePath, const QString& fileName, bool broken );
+ void trashDirectory( const QString& origPath, const QString& fileName );
+ void copyFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath = QString::null );
+ void moveFromTrash( const QString& fileId, const QString& destPath, const QString& relativePath = QString::null );
+
+ QString homeTmpDir() const;
+ QString otherTmpDir() const;
+ QString utf8FileName() const;
+ QString umlautFileName() const;
+ QString readOnlyDirPath() const;
+
+ QString m_trashDir;
+
+ QString m_otherPartitionTopDir;
+ QString m_otherPartitionTrashDir;
+ bool m_tmpIsWritablePartition;
+ int m_tmpTrashId;
+ int m_otherPartitionId;
+
+ int m_entryCount;
+ QStringList m_listResult;
+};
+
+#endif
diff --git a/kioslave/trash/trash.protocol b/kioslave/trash/trash.protocol
new file mode 100644
index 000000000..8387fdcb7
--- /dev/null
+++ b/kioslave/trash/trash.protocol
@@ -0,0 +1,89 @@
+[Protocol]
+exec=kio_trash
+protocol=trash
+input=none
+output=filesystem
+# AccessDate doesn't make sense.
+# OTOH we need deletion date :)
+listing=Name,Type,Size,Extra1,Extra2,Date,Access,Owner,Group,Link
+reading=true
+writing=true
+makedir=false
+deleting=true
+linking=false
+moving=true
+Icon=trashcan_full
+maxInstances=2
+Class=:local
+renameFromFile=true
+renameToFile=true
+copyFromFile=true
+copyToFile=true
+deleteRecursive=true
+fileNameUsedForCopying=Name
+#TODO DocPath
+ExtraNames=Original Path,Deletion Date
+ExtraNames[af]=Oorspronklike gids, Uitvee datum
+ExtraNames[ar]=المسار الأصلي ، تاريخ المحو
+ExtraNames[be]=Арыгінальнае месцазнаходжанне,Час выдалення
+ExtraNames[bg]=Местоположение, дата на изтриване
+ExtraNames[bn]=পূর্বতন পাথ, মোছার তারিখ
+ExtraNames[bs]=Originalni put,Datum brisanja
+ExtraNames[ca]=Camí original,Data d'esborrat
+ExtraNames[cs]=Původní cesta,Datum smazání
+ExtraNames[csb]=Òriginalnô stegna,Datum remniãcô
+ExtraNames[da]=Original sti, sletningsdato
+ExtraNames[de]=Ursprünglicher Pfad, Löschzeitpunkt
+ExtraNames[el]=Αρχική διαδρομή,Ημερομηνία διαγραφής
+ExtraNames[eo]=Originala Vojo,Forigo Dato
+ExtraNames[es]=Ruta original,Fecha de borrado
+ExtraNames[et]=Algne asukoht,Kustutamisaeg
+ExtraNames[eu]=Jatorrizko bideizena, ezabatzeko data
+ExtraNames[fa]=مسیر اصلی، تاریخ حذف
+ExtraNames[fi]=Alkuperäinen polku, poistopäivä
+ExtraNames[fr]=Emplacement d'origine, date de suppression
+ExtraNames[fy]=Oarspronklike lokaasje,datum fan wiskjen
+ExtraNames[ga]=Bunchonair,Dáta Scriosta
+ExtraNames[gl]=Rota Orixinal,Data de Eliminación
+ExtraNames[he]=נתיב מקורי, תאריך מחיקה
+ExtraNames[hi]=मूल पथ, मिटाने की तिथि
+ExtraNames[hr]=Izvorna putanja,Datum brisanja
+ExtraNames[hu]=Eredeti elérési út,Törlési dátum
+ExtraNames[is]=Upprunaleg slóð,dagsetning á eyðingu
+ExtraNames[it]=Percorso originale,ora di eliminazione
+ExtraNames[ja]=元のパス,削除日
+ExtraNames[ka]=საწყისი გეზი, წაშლის თარიღი
+ExtraNames[kk]=Бұрынғы жолы, Өшірілген кезі
+ExtraNames[km]=ផ្លូវ​លំនាំដើម កាលបរិច្ឆេទ​លុប
+ExtraNames[ko]=원본 경로,삭제 날짜
+ExtraNames[lt]=Originalus kelias,Trynimo data
+ExtraNames[lv]=Orģinālais ceļš,Dzēšanas datums
+ExtraNames[mk]=Оригинална патека,Датум на бришење
+ExtraNames[ms]=Laluan Asli, Tarikh Penghapusan
+ExtraNames[nb]=Opprinnelig sti, slettedato
+ExtraNames[nds]=Orginaalpadd,Wegdodatum
+ExtraNames[ne]=मौलिक मार्ग, मेट्ने मिति
+ExtraNames[nl]=Oorspronkelijke locatie,Datum van verwijdering
+ExtraNames[nn]=Opprinneleg stig, slettedato
+ExtraNames[pa]=ਅਸਲੀ ਮਾਰਗ,ਹਟਾਉਣ ਮਿਤੀ
+ExtraNames[pl]=Ścieżka oryginalna,Data usunięcia
+ExtraNames[pt]=Localização Original,Data de Remoção
+ExtraNames[pt_BR]=Caminho Original,Data da Remoção
+ExtraNames[ro]=Calea originală,Data ștergerii
+ExtraNames[ru]=Исходный путь, дата удаления
+ExtraNames[rw]=Inzira Mwimerere, Itariki y'Ihanagura
+ExtraNames[sl]=Prvotna pot,Datum brisanja
+ExtraNames[sr]=првобитна путања,датум брисања
+ExtraNames[sr@Latn]=prvobitna putanja,datum brisanja
+ExtraNames[sv]=Ursprunglig sökväg,Borttagsdatum
+ExtraNames[ta]=மூல பாதை,நீக்கப்பட்ட தேதி
+ExtraNames[te]=అసలు దారు, తీసివేసిన తేది
+ExtraNames[th]=พาธเดิม,วันที่ที่ลบ
+ExtraNames[tr]=Orjinal Yol, Silinme Tarihi
+ExtraNames[tt]=Çığanaq Yulı,Beterelü Çağı
+ExtraNames[uk]=Шлях,Дата вилучення
+ExtraNames[vi]=Đường dẫn Trước khi vứt,Ngày Vứt
+ExtraNames[wa]=Oridjinå tchmin,Date di disfaçaedje
+ExtraNames[zh_CN]=原始路径,删除日期
+ExtraNames[zh_TW]=原始路徑,刪除日期
+ExtraTypes=QString,QDateTime
diff --git a/kioslave/trash/trashimpl.cpp b/kioslave/trash/trashimpl.cpp
new file mode 100644
index 000000000..9507c77ab
--- /dev/null
+++ b/kioslave/trash/trashimpl.cpp
@@ -0,0 +1,962 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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 "trashimpl.h"
+#include <klocale.h>
+#include <klargefile.h>
+#include <kio/global.h>
+#include <kio/renamedlg.h>
+#include <kio/job.h>
+#include <kdebug.h>
+#include <kurl.h>
+#include <kdirnotify_stub.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <kglobalsettings.h>
+#include <kmountpoint.h>
+#include <kfileitem.h>
+#include <kio/chmodjob.h>
+
+#include <dcopref.h>
+
+#include <qapplication.h>
+#include <qeventloop.h>
+#include <qfile.h>
+#include <qdir.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <errno.h>
+
+TrashImpl::TrashImpl() :
+ QObject(),
+ m_lastErrorCode( 0 ),
+ m_initStatus( InitToBeDone ),
+ m_lastId( 0 ),
+ m_homeDevice( 0 ),
+ m_trashDirectoriesScanned( false ),
+ m_mibEnum( KGlobal::locale()->fileEncodingMib() ),
+ // not using kio_trashrc since KIO uses that one already for kio_trash
+ // so better have a separate one, for faster parsing by e.g. kmimetype.cpp
+ m_config( "trashrc" )
+{
+ KDE_struct_stat buff;
+ if ( KDE_lstat( QFile::encodeName( QDir::homeDirPath() ), &buff ) == 0 ) {
+ m_homeDevice = buff.st_dev;
+ } else {
+ kdError() << "Should never happen: couldn't stat $HOME " << strerror( errno ) << endl;
+ }
+}
+
+/**
+ * Test if a directory exists, create otherwise
+ * @param _name full path of the directory
+ * @return errorcode, or 0 if the dir was created or existed already
+ * Warning, don't use return value like a bool
+ */
+int TrashImpl::testDir( const QString &_name ) const
+{
+ DIR *dp = opendir( QFile::encodeName(_name) );
+ if ( dp == NULL )
+ {
+ QString name = _name;
+ if ( name.endsWith( "/" ) )
+ name.truncate( name.length() - 1 );
+ QCString path = QFile::encodeName(name);
+
+ bool ok = ::mkdir( path, S_IRWXU ) == 0;
+ if ( !ok && errno == EEXIST ) {
+#if 0 // this would require to use SlaveBase's method to ask the question
+ //int ret = KMessageBox::warningYesNo( 0, i18n("%1 is a file, but KDE needs it to be a directory. Move it to %2.orig and create directory?").arg(name).arg(name) );
+ //if ( ret == KMessageBox::Yes ) {
+#endif
+ if ( ::rename( path, path + ".orig" ) == 0 ) {
+ ok = ::mkdir( path, S_IRWXU ) == 0;
+ } else { // foo.orig existed already. How likely is that?
+ ok = false;
+ }
+ if ( !ok ) {
+ return KIO::ERR_DIR_ALREADY_EXIST;
+ }
+#if 0
+ //} else {
+ // return 0;
+ //}
+#endif
+ }
+ if ( !ok )
+ {
+ //KMessageBox::sorry( 0, i18n( "Couldn't create directory %1. Check for permissions." ).arg( name ) );
+ kdWarning() << "could not create " << name << endl;
+ return KIO::ERR_COULD_NOT_MKDIR;
+ } else {
+ kdDebug() << name << " created." << endl;
+ }
+ }
+ else // exists already
+ {
+ closedir( dp );
+ }
+ return 0; // success
+}
+
+bool TrashImpl::init()
+{
+ if ( m_initStatus == InitOK )
+ return true;
+ if ( m_initStatus == InitError )
+ return false;
+
+ // Check the trash directory and its info and files subdirs
+ // see also kdesktop/init.cc for first time initialization
+ m_initStatus = InitError;
+ // $XDG_DATA_HOME/Trash, i.e. ~/.local/share/Trash by default.
+ const QString xdgDataDir = KGlobal::dirs()->localxdgdatadir();
+ if ( !KStandardDirs::makeDir( xdgDataDir, 0700 ) ) {
+ kdWarning() << "failed to create " << xdgDataDir << endl;
+ return false;
+ }
+
+ const QString trashDir = xdgDataDir + "Trash";
+ int err;
+ if ( ( err = testDir( trashDir ) ) ) {
+ error( err, trashDir );
+ return false;
+ }
+ if ( ( err = testDir( trashDir + "/info" ) ) ) {
+ error( err, trashDir + "/info" );
+ return false;
+ }
+ if ( ( err = testDir( trashDir + "/files" ) ) ) {
+ error( err, trashDir + "/files" );
+ return false;
+ }
+ m_trashDirectories.insert( 0, trashDir );
+ m_initStatus = InitOK;
+ kdDebug() << k_funcinfo << "initialization OK, home trash dir: " << trashDir << endl;
+ return true;
+}
+
+void TrashImpl::migrateOldTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString oldTrashDir = KGlobalSettings::trashPath();
+ const QStrList entries = listDir( oldTrashDir );
+ bool allOK = true;
+ QStrListIterator entryIt( entries );
+ for (; entryIt.current(); ++entryIt) {
+ QString srcPath = QFile::decodeName( *entryIt );
+ if ( srcPath == "." || srcPath == ".." || srcPath == ".directory" )
+ continue;
+ srcPath.prepend( oldTrashDir ); // make absolute
+ int trashId;
+ QString fileId;
+ if ( !createInfo( srcPath, trashId, fileId ) ) {
+ kdWarning() << "Trash migration: failed to create info for " << srcPath << endl;
+ allOK = false;
+ } else {
+ bool ok = moveToTrash( srcPath, trashId, fileId );
+ if ( !ok ) {
+ (void)deleteInfo( trashId, fileId );
+ kdWarning() << "Trash migration: failed to create info for " << srcPath << endl;
+ allOK = false;
+ } else {
+ kdDebug() << "Trash migration: moved " << srcPath << endl;
+ }
+ }
+ }
+ if ( allOK ) {
+ // We need to remove the old one, otherwise the desktop will have two trashcans...
+ kdDebug() << "Trash migration: all OK, removing old trash directory" << endl;
+ synchronousDel( oldTrashDir, false, true );
+ }
+}
+
+bool TrashImpl::createInfo( const QString& origPath, int& trashId, QString& fileId )
+{
+ kdDebug() << k_funcinfo << origPath << endl;
+ // Check source
+ const QCString origPath_c( QFile::encodeName( origPath ) );
+ KDE_struct_stat buff_src;
+ if ( KDE_lstat( origPath_c.data(), &buff_src ) == -1 ) {
+ if ( errno == EACCES )
+ error( KIO::ERR_ACCESS_DENIED, origPath );
+ else
+ error( KIO::ERR_DOES_NOT_EXIST, origPath );
+ return false;
+ }
+
+ // Choose destination trash
+ trashId = findTrashDirectory( origPath );
+ if ( trashId < 0 ) {
+ kdWarning() << "OUCH - internal error, TrashImpl::findTrashDirectory returned " << trashId << endl;
+ return false; // ### error() needed?
+ }
+ kdDebug() << k_funcinfo << "trashing to " << trashId << endl;
+
+ // Grab original filename
+ KURL url;
+ url.setPath( origPath );
+ const QString origFileName = url.fileName();
+
+ // Make destination file in info/
+ url.setPath( infoPath( trashId, origFileName ) ); // we first try with origFileName
+ KURL baseDirectory;
+ baseDirectory.setPath( url.directory() );
+ // Here we need to use O_EXCL to avoid race conditions with other kioslave processes
+ int fd = 0;
+ do {
+ kdDebug() << k_funcinfo << "trying to create " << url.path() << endl;
+ fd = ::open( QFile::encodeName( url.path() ), O_WRONLY | O_CREAT | O_EXCL, 0600 );
+ if ( fd < 0 ) {
+ if ( errno == EEXIST ) {
+ url.setFileName( KIO::RenameDlg::suggestName( baseDirectory, url.fileName() ) );
+ // and try again on the next iteration
+ } else {
+ error( KIO::ERR_COULD_NOT_WRITE, url.path() );
+ return false;
+ }
+ }
+ } while ( fd < 0 );
+ const QString infoPath = url.path();
+ fileId = url.fileName();
+ Q_ASSERT( fileId.endsWith( ".trashinfo" ) );
+ fileId.truncate( fileId.length() - 10 ); // remove .trashinfo from fileId
+
+ FILE* file = ::fdopen( fd, "w" );
+ if ( !file ) { // can't see how this would happen
+ error( KIO::ERR_COULD_NOT_WRITE, infoPath );
+ return false;
+ }
+
+ // Contents of the info file. We could use KSimpleConfig, but that would
+ // mean closing and reopening fd, i.e. opening a race condition...
+ QCString info = "[Trash Info]\n";
+ info += "Path=";
+ // Escape filenames according to the way they are encoded on the filesystem
+ // All this to basically get back to the raw 8-bit representation of the filename...
+ if ( trashId == 0 ) // home trash: absolute path
+ info += KURL::encode_string( origPath, m_mibEnum ).latin1();
+ else
+ info += KURL::encode_string( makeRelativePath( topDirectoryPath( trashId ), origPath ), m_mibEnum ).latin1();
+ info += "\n";
+ info += "DeletionDate=";
+ info += QDateTime::currentDateTime().toString( Qt::ISODate ).latin1();
+ info += "\n";
+ size_t sz = info.size() - 1; // avoid trailing 0 from QCString
+
+ size_t written = ::fwrite(info.data(), 1, sz, file);
+ if ( written != sz ) {
+ ::fclose( file );
+ QFile::remove( infoPath );
+ error( KIO::ERR_DISK_FULL, infoPath );
+ return false;
+ }
+
+ ::fclose( file );
+
+ kdDebug() << k_funcinfo << "info file created in trashId=" << trashId << " : " << fileId << endl;
+ return true;
+}
+
+QString TrashImpl::makeRelativePath( const QString& topdir, const QString& path )
+{
+ const QString realPath = KStandardDirs::realFilePath( path );
+ // topdir ends with '/'
+ if ( realPath.startsWith( topdir ) ) {
+ const QString rel = realPath.mid( topdir.length() );
+ Q_ASSERT( rel[0] != '/' );
+ return rel;
+ } else { // shouldn't happen...
+ kdWarning() << "Couldn't make relative path for " << realPath << " (" << path << "), with topdir=" << topdir << endl;
+ return realPath;
+ }
+}
+
+QString TrashImpl::infoPath( int trashId, const QString& fileId ) const
+{
+ QString trashPath = trashDirectoryPath( trashId );
+ trashPath += "/info/";
+ trashPath += fileId;
+ trashPath += ".trashinfo";
+ return trashPath;
+}
+
+QString TrashImpl::filesPath( int trashId, const QString& fileId ) const
+{
+ QString trashPath = trashDirectoryPath( trashId );
+ trashPath += "/files/";
+ trashPath += fileId;
+ return trashPath;
+}
+
+bool TrashImpl::deleteInfo( int trashId, const QString& fileId )
+{
+ bool ok = QFile::remove( infoPath( trashId, fileId ) );
+ if ( ok )
+ fileRemoved();
+ return ok;
+}
+
+bool TrashImpl::moveToTrash( const QString& origPath, int trashId, const QString& fileId )
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString dest = filesPath( trashId, fileId );
+ if ( !move( origPath, dest ) ) {
+ // Maybe the move failed due to no permissions to delete source.
+ // In that case, delete dest to keep things consistent, since KIO doesn't do it.
+ if ( QFileInfo( dest ).isFile() )
+ QFile::remove( dest );
+ else
+ synchronousDel( dest, false, true );
+ return false;
+ }
+ fileAdded();
+ return true;
+}
+
+bool TrashImpl::moveFromTrash( const QString& dest, int trashId, const QString& fileId, const QString& relativePath )
+{
+ QString src = filesPath( trashId, fileId );
+ if ( !relativePath.isEmpty() ) {
+ src += '/';
+ src += relativePath;
+ }
+ if ( !move( src, dest ) )
+ return false;
+ return true;
+}
+
+bool TrashImpl::move( const QString& src, const QString& dest )
+{
+ if ( directRename( src, dest ) ) {
+ // This notification is done by KIO::moveAs when using the code below
+ // But if we do a direct rename we need to do the notification ourselves
+ KDirNotify_stub allDirNotify( "*", "KDirNotify*" );
+ KURL urlDest; urlDest.setPath( dest );
+ urlDest.setPath( urlDest.directory() );
+ allDirNotify.FilesAdded( urlDest );
+ return true;
+ }
+ if ( m_lastErrorCode != KIO::ERR_UNSUPPORTED_ACTION )
+ return false;
+
+ KURL urlSrc, urlDest;
+ urlSrc.setPath( src );
+ urlDest.setPath( dest );
+ kdDebug() << k_funcinfo << urlSrc << " -> " << urlDest << endl;
+ KIO::CopyJob* job = KIO::moveAs( urlSrc, urlDest, false );
+#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE
+ job->setInteractive( false );
+#endif
+ connect( job, SIGNAL( result(KIO::Job *) ),
+ this, SLOT( jobFinished(KIO::Job *) ) );
+ qApp->eventLoop()->enterLoop();
+
+ return m_lastErrorCode == 0;
+}
+
+void TrashImpl::jobFinished(KIO::Job* job)
+{
+ kdDebug() << k_funcinfo << " error=" << job->error() << endl;
+ error( job->error(), job->errorText() );
+ qApp->eventLoop()->exitLoop();
+}
+
+bool TrashImpl::copyToTrash( const QString& origPath, int trashId, const QString& fileId )
+{
+ kdDebug() << k_funcinfo << endl;
+ const QString dest = filesPath( trashId, fileId );
+ if ( !copy( origPath, dest ) )
+ return false;
+ fileAdded();
+ return true;
+}
+
+bool TrashImpl::copyFromTrash( const QString& dest, int trashId, const QString& fileId, const QString& relativePath )
+{
+ QString src = filesPath( trashId, fileId );
+ if ( !relativePath.isEmpty() ) {
+ src += '/';
+ src += relativePath;
+ }
+ return copy( src, dest );
+}
+
+bool TrashImpl::copy( const QString& src, const QString& dest )
+{
+ // kio_file's copy() method is quite complex (in order to be fast), let's just call it...
+ m_lastErrorCode = 0;
+ KURL urlSrc;
+ urlSrc.setPath( src );
+ KURL urlDest;
+ urlDest.setPath( dest );
+ kdDebug() << k_funcinfo << "copying " << src << " to " << dest << endl;
+ KIO::CopyJob* job = KIO::copyAs( urlSrc, urlDest, false );
+#ifdef KIO_COPYJOB_HAS_SETINTERACTIVE
+ job->setInteractive( false );
+#endif
+ connect( job, SIGNAL( result( KIO::Job* ) ),
+ this, SLOT( jobFinished( KIO::Job* ) ) );
+ qApp->eventLoop()->enterLoop();
+
+ return m_lastErrorCode == 0;
+}
+
+bool TrashImpl::directRename( const QString& src, const QString& dest )
+{
+ kdDebug() << k_funcinfo << src << " -> " << dest << endl;
+ if ( ::rename( QFile::encodeName( src ), QFile::encodeName( dest ) ) != 0 ) {
+ if (errno == EXDEV) {
+ error( KIO::ERR_UNSUPPORTED_ACTION, QString::fromLatin1("rename") );
+ } else {
+ if (( errno == EACCES ) || (errno == EPERM)) {
+ error( KIO::ERR_ACCESS_DENIED, dest );
+ } else if (errno == EROFS) { // The file is on a read-only filesystem
+ error( KIO::ERR_CANNOT_DELETE, src );
+ } else {
+ error( KIO::ERR_CANNOT_RENAME, src );
+ }
+ }
+ return false;
+ }
+ return true;
+}
+
+#if 0
+bool TrashImpl::mkdir( int trashId, const QString& fileId, int permissions )
+{
+ const QString path = filesPath( trashId, fileId );
+ if ( ::mkdir( QFile::encodeName( path ), permissions ) != 0 ) {
+ if ( errno == EACCES ) {
+ error( KIO::ERR_ACCESS_DENIED, path );
+ return false;
+ } else if ( errno == ENOSPC ) {
+ error( KIO::ERR_DISK_FULL, path );
+ return false;
+ } else {
+ error( KIO::ERR_COULD_NOT_MKDIR, path );
+ return false;
+ }
+ } else {
+ if ( permissions != -1 )
+ ::chmod( QFile::encodeName( path ), permissions );
+ }
+ return true;
+}
+#endif
+
+bool TrashImpl::del( int trashId, const QString& fileId )
+{
+ QString info = infoPath(trashId, fileId);
+ QString file = filesPath(trashId, fileId);
+
+ QCString info_c = QFile::encodeName(info);
+
+ KDE_struct_stat buff;
+ if ( KDE_lstat( info_c.data(), &buff ) == -1 ) {
+ if ( errno == EACCES )
+ error( KIO::ERR_ACCESS_DENIED, file );
+ else
+ error( KIO::ERR_DOES_NOT_EXIST, file );
+ return false;
+ }
+
+ if ( !synchronousDel( file, true, QFileInfo(file).isDir() ) )
+ return false;
+
+ QFile::remove( info );
+ fileRemoved();
+ return true;
+}
+
+bool TrashImpl::synchronousDel( const QString& path, bool setLastErrorCode, bool isDir )
+{
+ const int oldErrorCode = m_lastErrorCode;
+ const QString oldErrorMsg = m_lastErrorMessage;
+ KURL url;
+ url.setPath( path );
+
+ // First ensure that all dirs have u+w permissions,
+ // otherwise we won't be able to delete files in them (#130780).
+ if ( isDir ) {
+ kdDebug() << k_funcinfo << "chmod'ing " << url << endl;
+ KFileItem fileItem( url, "inode/directory", KFileItem::Unknown );
+ KFileItemList fileItemList;
+ fileItemList.append( &fileItem );
+ KIO::ChmodJob* chmodJob = KIO::chmod( fileItemList, 0200, 0200, QString::null, QString::null, true /*recursive*/, false /*showProgressInfo*/ );
+ connect( chmodJob, SIGNAL( result(KIO::Job *) ),
+ this, SLOT( jobFinished(KIO::Job *) ) );
+ qApp->eventLoop()->enterLoop();
+ }
+
+ kdDebug() << k_funcinfo << "deleting " << url << endl;
+ KIO::DeleteJob *job = KIO::del( url, false, false );
+ connect( job, SIGNAL( result(KIO::Job *) ),
+ this, SLOT( jobFinished(KIO::Job *) ) );
+ qApp->eventLoop()->enterLoop();
+ bool ok = m_lastErrorCode == 0;
+ if ( !setLastErrorCode ) {
+ m_lastErrorCode = oldErrorCode;
+ m_lastErrorMessage = oldErrorMsg;
+ }
+ return ok;
+}
+
+bool TrashImpl::emptyTrash()
+{
+ kdDebug() << k_funcinfo << endl;
+ // The naive implementation "delete info and files in every trash directory"
+ // breaks when deleted directories contain files owned by other users.
+ // We need to ensure that the .trashinfo file is only removed when the
+ // corresponding files could indeed be removed.
+
+ const TrashedFileInfoList fileInfoList = list();
+
+ TrashedFileInfoList::const_iterator it = fileInfoList.begin();
+ const TrashedFileInfoList::const_iterator end = fileInfoList.end();
+ for ( ; it != end ; ++it ) {
+ const TrashedFileInfo& info = *it;
+ const QString filesPath = info.physicalPath;
+ if ( synchronousDel( filesPath, true, true ) ) {
+ QFile::remove( infoPath( info.trashId, info.fileId ) );
+ } // else error code is set
+ }
+ fileRemoved();
+
+ return m_lastErrorCode == 0;
+}
+
+TrashImpl::TrashedFileInfoList TrashImpl::list()
+{
+ // Here we scan for trash directories unconditionally. This allows
+ // noticing plugged-in [e.g. removeable] devices, or new mounts etc.
+ scanTrashDirectories();
+
+ TrashedFileInfoList lst;
+ // For each known trash directory...
+ TrashDirMap::const_iterator it = m_trashDirectories.begin();
+ for ( ; it != m_trashDirectories.end() ; ++it ) {
+ const int trashId = it.key();
+ QString infoPath = it.data();
+ infoPath += "/info";
+ // Code taken from kio_file
+ QStrList entryNames = listDir( infoPath );
+ //char path_buffer[PATH_MAX];
+ //getcwd(path_buffer, PATH_MAX - 1);
+ //if ( chdir( infoPathEnc ) )
+ // continue;
+ QStrListIterator entryIt( entryNames );
+ for (; entryIt.current(); ++entryIt) {
+ QString fileName = QFile::decodeName( *entryIt );
+ if ( fileName == "." || fileName == ".." )
+ continue;
+ if ( !fileName.endsWith( ".trashinfo" ) ) {
+ kdWarning() << "Invalid info file found in " << infoPath << " : " << fileName << endl;
+ continue;
+ }
+ fileName.truncate( fileName.length() - 10 );
+
+ TrashedFileInfo info;
+ if ( infoForFile( trashId, fileName, info ) )
+ lst << info;
+ }
+ }
+ return lst;
+}
+
+// Returns the entries in a given directory - including "." and ".."
+QStrList TrashImpl::listDir( const QString& physicalPath )
+{
+ const QCString physicalPathEnc = QFile::encodeName( physicalPath );
+ kdDebug() << k_funcinfo << "listing " << physicalPath << endl;
+ QStrList entryNames;
+ DIR *dp = opendir( physicalPathEnc );
+ if ( dp == 0 )
+ return entryNames;
+ KDE_struct_dirent *ep;
+ while ( ( ep = KDE_readdir( dp ) ) != 0L )
+ entryNames.append( ep->d_name );
+ closedir( dp );
+ return entryNames;
+}
+
+bool TrashImpl::infoForFile( int trashId, const QString& fileId, TrashedFileInfo& info )
+{
+ kdDebug() << k_funcinfo << trashId << " " << fileId << endl;
+ info.trashId = trashId; // easy :)
+ info.fileId = fileId; // equally easy
+ info.physicalPath = filesPath( trashId, fileId );
+ return readInfoFile( infoPath( trashId, fileId ), info, trashId );
+}
+
+bool TrashImpl::readInfoFile( const QString& infoPath, TrashedFileInfo& info, int trashId )
+{
+ KSimpleConfig cfg( infoPath, true );
+ if ( !cfg.hasGroup( "Trash Info" ) ) {
+ error( KIO::ERR_CANNOT_OPEN_FOR_READING, infoPath );
+ return false;
+ }
+ cfg.setGroup( "Trash Info" );
+ info.origPath = KURL::decode_string( cfg.readEntry( "Path" ), m_mibEnum );
+ if ( info.origPath.isEmpty() )
+ return false; // path is mandatory...
+ if ( trashId == 0 )
+ Q_ASSERT( info.origPath[0] == '/' );
+ else {
+ const QString topdir = topDirectoryPath( trashId ); // includes trailing slash
+ info.origPath.prepend( topdir );
+ }
+ QString line = cfg.readEntry( "DeletionDate" );
+ if ( !line.isEmpty() ) {
+ info.deletionDate = QDateTime::fromString( line, Qt::ISODate );
+ }
+ return true;
+}
+
+QString TrashImpl::physicalPath( int trashId, const QString& fileId, const QString& relativePath )
+{
+ QString filePath = filesPath( trashId, fileId );
+ if ( !relativePath.isEmpty() ) {
+ filePath += "/";
+ filePath += relativePath;
+ }
+ return filePath;
+}
+
+void TrashImpl::error( int e, const QString& s )
+{
+ if ( e )
+ kdDebug() << k_funcinfo << e << " " << s << endl;
+ m_lastErrorCode = e;
+ m_lastErrorMessage = s;
+}
+
+bool TrashImpl::isEmpty() const
+{
+ // For each known trash directory...
+ if ( !m_trashDirectoriesScanned )
+ scanTrashDirectories();
+ TrashDirMap::const_iterator it = m_trashDirectories.begin();
+ for ( ; it != m_trashDirectories.end() ; ++it ) {
+ QString infoPath = it.data();
+ infoPath += "/info";
+
+ DIR *dp = opendir( QFile::encodeName( infoPath ) );
+ if ( dp )
+ {
+ struct dirent *ep;
+ ep = readdir( dp );
+ ep = readdir( dp ); // ignore '.' and '..' dirent
+ ep = readdir( dp ); // look for third file
+ closedir( dp );
+ if ( ep != 0 ) {
+ //kdDebug() << ep->d_name << " in " << infoPath << " -> not empty" << endl;
+ return false; // not empty
+ }
+ }
+ }
+ return true;
+}
+
+void TrashImpl::fileAdded()
+{
+ m_config.setGroup( "Status" );
+ if ( m_config.readBoolEntry( "Empty", true ) == true ) {
+ m_config.writeEntry( "Empty", false );
+ m_config.sync();
+ }
+ // The apps showing the trash (e.g. kdesktop) will be notified
+ // of this change when KDirNotify::FilesAdded("trash:/") is emitted,
+ // which will be done by the job soon after this.
+}
+
+void TrashImpl::fileRemoved()
+{
+ if ( isEmpty() ) {
+ m_config.setGroup( "Status" );
+ m_config.writeEntry( "Empty", true );
+ m_config.sync();
+ }
+ // The apps showing the trash (e.g. kdesktop) will be notified
+ // of this change when KDirNotify::FilesRemoved(...) is emitted,
+ // which will be done by the job soon after this.
+}
+
+int TrashImpl::findTrashDirectory( const QString& origPath )
+{
+ kdDebug() << k_funcinfo << origPath << endl;
+ // First check if same device as $HOME, then we use the home trash right away.
+ KDE_struct_stat buff;
+ if ( KDE_lstat( QFile::encodeName( origPath ), &buff ) == 0
+ && buff.st_dev == m_homeDevice )
+ return 0;
+
+ QString mountPoint = KIO::findPathMountPoint( origPath );
+ const QString trashDir = trashForMountPoint( mountPoint, true );
+ kdDebug() << "mountPoint=" << mountPoint << " trashDir=" << trashDir << endl;
+ if ( trashDir.isEmpty() )
+ return 0; // no trash available on partition
+ int id = idForTrashDirectory( trashDir );
+ if ( id > -1 ) {
+ kdDebug() << " known with id " << id << endl;
+ return id;
+ }
+ // new trash dir found, register it
+ // but we need stability in the trash IDs, so that restoring or asking
+ // for properties works even kio_trash gets killed because idle.
+#if 0
+ kdDebug() << k_funcinfo << "found " << trashDir << endl;
+ m_trashDirectories.insert( ++m_lastId, trashDir );
+ if ( !mountPoint.endsWith( "/" ) )
+ mountPoint += '/';
+ m_topDirectories.insert( m_lastId, mountPoint );
+ return m_lastId;
+#endif
+ scanTrashDirectories();
+ return idForTrashDirectory( trashDir );
+}
+
+void TrashImpl::scanTrashDirectories() const
+{
+ const KMountPoint::List lst = KMountPoint::currentMountPoints();
+ for ( KMountPoint::List::ConstIterator it = lst.begin() ; it != lst.end() ; ++it ) {
+ const QCString str = (*it)->mountType().latin1();
+ // Skip pseudo-filesystems, there's no chance we'll find a .Trash on them :)
+ // ## Maybe we should also skip readonly filesystems
+ if ( str != "proc" && str != "devfs" && str != "usbdevfs" &&
+ str != "sysfs" && str != "devpts" && str != "subfs" /* #96259 */ &&
+ str != "autofs" /* #101116 */ ) {
+ QString topdir = (*it)->mountPoint();
+ QString trashDir = trashForMountPoint( topdir, false );
+ if ( !trashDir.isEmpty() ) {
+ // OK, trashDir is a valid trash directory. Ensure it's registered.
+ int trashId = idForTrashDirectory( trashDir );
+ if ( trashId == -1 ) {
+ // new trash dir found, register it
+ m_trashDirectories.insert( ++m_lastId, trashDir );
+ kdDebug() << k_funcinfo << "found " << trashDir << " gave it id " << m_lastId << endl;
+ if ( !topdir.endsWith( "/" ) )
+ topdir += '/';
+ m_topDirectories.insert( m_lastId, topdir );
+ }
+ }
+ }
+ }
+ m_trashDirectoriesScanned = true;
+}
+
+TrashImpl::TrashDirMap TrashImpl::trashDirectories() const
+{
+ if ( !m_trashDirectoriesScanned )
+ scanTrashDirectories();
+ return m_trashDirectories;
+}
+
+TrashImpl::TrashDirMap TrashImpl::topDirectories() const
+{
+ if ( !m_trashDirectoriesScanned )
+ scanTrashDirectories();
+ return m_topDirectories;
+}
+
+QString TrashImpl::trashForMountPoint( const QString& topdir, bool createIfNeeded ) const
+{
+ // (1) Administrator-created $topdir/.Trash directory
+
+ const QString rootTrashDir = topdir + "/.Trash";
+ const QCString rootTrashDir_c = QFile::encodeName( rootTrashDir );
+ // Can't use QFileInfo here since we need to test for the sticky bit
+ uid_t uid = getuid();
+ KDE_struct_stat buff;
+ const uint requiredBits = S_ISVTX; // Sticky bit required
+ if ( KDE_lstat( rootTrashDir_c, &buff ) == 0 ) {
+ if ( (S_ISDIR(buff.st_mode)) // must be a dir
+ && (!S_ISLNK(buff.st_mode)) // not a symlink
+ && ((buff.st_mode & requiredBits) == requiredBits)
+ && (::access(rootTrashDir_c, W_OK))
+ ) {
+ const QString trashDir = rootTrashDir + "/" + QString::number( uid );
+ const QCString trashDir_c = QFile::encodeName( trashDir );
+ if ( KDE_lstat( trashDir_c, &buff ) == 0 ) {
+ if ( (buff.st_uid == uid) // must be owned by user
+ && (S_ISDIR(buff.st_mode)) // must be a dir
+ && (!S_ISLNK(buff.st_mode)) // not a symlink
+ && (buff.st_mode & 0777) == 0700 ) { // rwx for user
+ return trashDir;
+ }
+ kdDebug() << "Directory " << trashDir << " exists but didn't pass the security checks, can't use it" << endl;
+ }
+ else if ( createIfNeeded && initTrashDirectory( trashDir_c ) ) {
+ return trashDir;
+ }
+ } else {
+ kdDebug() << "Root trash dir " << rootTrashDir << " exists but didn't pass the security checks, can't use it" << endl;
+ }
+ }
+
+ // (2) $topdir/.Trash-$uid
+ const QString trashDir = topdir + "/.Trash-" + QString::number( uid );
+ const QCString trashDir_c = QFile::encodeName( trashDir );
+ if ( KDE_lstat( trashDir_c, &buff ) == 0 )
+ {
+ if ( (buff.st_uid == uid) // must be owned by user
+ && (S_ISDIR(buff.st_mode)) // must be a dir
+ && (!S_ISLNK(buff.st_mode)) // not a symlink
+ && ((buff.st_mode & 0777) == 0700) ) { // rwx for user, ------ for group and others
+
+ if ( checkTrashSubdirs( trashDir_c ) )
+ return trashDir;
+ }
+ kdDebug() << "Directory " << trashDir << " exists but didn't pass the security checks, can't use it" << endl;
+ // Exists, but not useable
+ return QString::null;
+ }
+ if ( createIfNeeded && initTrashDirectory( trashDir_c ) ) {
+ return trashDir;
+ }
+ return QString::null;
+}
+
+int TrashImpl::idForTrashDirectory( const QString& trashDir ) const
+{
+ // If this is too slow we can always use a reverse map...
+ TrashDirMap::ConstIterator it = m_trashDirectories.begin();
+ for ( ; it != m_trashDirectories.end() ; ++it ) {
+ if ( it.data() == trashDir ) {
+ return it.key();
+ }
+ }
+ return -1;
+}
+
+bool TrashImpl::initTrashDirectory( const QCString& trashDir_c ) const
+{
+ if ( ::mkdir( trashDir_c, 0700 ) != 0 )
+ return false;
+ // This trash dir will be useable only if the directory is owned by user.
+ // In theory this is the case, but not on e.g. USB keys...
+ uid_t uid = getuid();
+ KDE_struct_stat buff;
+ if ( KDE_lstat( trashDir_c, &buff ) != 0 )
+ return false; // huh?
+ if ( (buff.st_uid == uid) // must be owned by user
+ && ((buff.st_mode & 0777) == 0700) ) { // rwx for user, --- for group and others
+
+ return checkTrashSubdirs( trashDir_c );
+
+ } else {
+ kdDebug() << trashDir_c << " just created, by it doesn't have the right permissions, must be a FAT partition. Removing it again." << endl;
+ // Not good, e.g. USB key. Delete again.
+ // I'm paranoid, it would be better to find a solution that allows
+ // to trash directly onto the USB key, but I don't see how that would
+ // pass the security checks. It would also make the USB key appears as
+ // empty when it's in fact full...
+ ::rmdir( trashDir_c );
+ return false;
+ }
+ return true;
+}
+
+bool TrashImpl::checkTrashSubdirs( const QCString& trashDir_c ) const
+{
+ // testDir currently works with a QString - ## optimize
+ QString trashDir = QFile::decodeName( trashDir_c );
+ const QString info = trashDir + "/info";
+ if ( testDir( info ) != 0 )
+ return false;
+ const QString files = trashDir + "/files";
+ if ( testDir( files ) != 0 )
+ return false;
+ return true;
+}
+
+QString TrashImpl::trashDirectoryPath( int trashId ) const
+{
+ // Never scanned for trash dirs? (This can happen after killing kio_trash
+ // and reusing a directory listing from the earlier instance.)
+ if ( !m_trashDirectoriesScanned )
+ scanTrashDirectories();
+ Q_ASSERT( m_trashDirectories.contains( trashId ) );
+ return m_trashDirectories[trashId];
+}
+
+QString TrashImpl::topDirectoryPath( int trashId ) const
+{
+ if ( !m_trashDirectoriesScanned )
+ scanTrashDirectories();
+ assert( trashId != 0 );
+ Q_ASSERT( m_topDirectories.contains( trashId ) );
+ return m_topDirectories[trashId];
+}
+
+// Helper method. Creates a URL with the format trash:/trashid-fileid or
+// trash:/trashid-fileid/relativePath/To/File for a file inside a trashed directory.
+KURL TrashImpl::makeURL( int trashId, const QString& fileId, const QString& relativePath )
+{
+ KURL url;
+ url.setProtocol( "trash" );
+ QString path = "/";
+ path += QString::number( trashId );
+ path += '-';
+ path += fileId;
+ if ( !relativePath.isEmpty() ) {
+ path += '/';
+ path += relativePath;
+ }
+ url.setPath( path );
+ return url;
+}
+
+// Helper method. Parses a trash URL with the URL scheme defined in makeURL.
+// The trash:/ URL itself isn't parsed here, must be caught by the caller before hand.
+bool TrashImpl::parseURL( const KURL& url, int& trashId, QString& fileId, QString& relativePath )
+{
+ if ( url.protocol() != "trash" )
+ return false;
+ const QString path = url.path();
+ int start = 0;
+ if ( path[0] == '/' ) // always true I hope
+ start = 1;
+ int slashPos = path.find( '-', 0 ); // don't match leading slash
+ if ( slashPos <= 0 )
+ return false;
+ bool ok = false;
+ trashId = path.mid( start, slashPos - start ).toInt( &ok );
+ Q_ASSERT( ok );
+ if ( !ok )
+ return false;
+ start = slashPos + 1;
+ slashPos = path.find( '/', start );
+ if ( slashPos <= 0 ) {
+ fileId = path.mid( start );
+ relativePath = QString::null;
+ return true;
+ }
+ fileId = path.mid( start, slashPos - start );
+ relativePath = path.mid( slashPos + 1 );
+ return true;
+}
+
+#include "trashimpl.moc"
diff --git a/kioslave/trash/trashimpl.h b/kioslave/trash/trashimpl.h
new file mode 100644
index 000000000..2b16ec67e
--- /dev/null
+++ b/kioslave/trash/trashimpl.h
@@ -0,0 +1,182 @@
+/* This file is part of the KDE project
+ Copyright (C) 2004 David Faure <[email protected]>
+
+ 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.
+*/
+
+#ifndef TRASHIMPL_H
+#define TRASHIMPL_H
+
+#include <kio/jobclasses.h>
+#include <ksimpleconfig.h>
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <qmap.h>
+#include <qvaluelist.h>
+#include <qstrlist.h>
+#include <assert.h>
+
+/**
+ * Implementation of all low-level operations done by kio_trash
+ * The structure of the trash directory follows the freedesktop.org standard <TODO URL>
+ */
+class TrashImpl : public QObject
+{
+ Q_OBJECT
+public:
+ TrashImpl();
+
+ /// Check the "home" trash directory
+ /// This MUST be called before doing anything else
+ bool init();
+
+ /// Create info for a file to be trashed
+ /// Returns trashId and fileId
+ /// The caller is then responsible for actually trashing the file
+ bool createInfo( const QString& origPath, int& trashId, QString& fileId );
+
+ /// Delete info file for a file to be trashed
+ /// Usually used for undoing what createInfo did if trashing failed
+ bool deleteInfo( int trashId, const QString& fileId );
+
+ /// Moving a file or directory into the trash. The ids come from createInfo.
+ bool moveToTrash( const QString& origPath, int trashId, const QString& fileId );
+
+ /// Moving a file or directory out of the trash. The ids come from createInfo.
+ bool moveFromTrash( const QString& origPath, int trashId, const QString& fileId, const QString& relativePath );
+
+ /// Copying a file or directory into the trash. The ids come from createInfo.
+ bool copyToTrash( const QString& origPath, int trashId, const QString& fileId );
+
+ /// Copying a file or directory out of the trash. The ids come from createInfo.
+ bool copyFromTrash( const QString& origPath, int trashId, const QString& fileId, const QString& relativePath );
+
+ /// Create a top-level trashed directory
+ //bool mkdir( int trashId, const QString& fileId, int permissions );
+
+ /// Get rid of a trashed file
+ bool del( int trashId, const QString& fileId );
+
+ /// Empty trash, i.e. delete all trashed files
+ bool emptyTrash();
+
+ /// Return true if the trash is empty
+ bool isEmpty() const;
+
+ struct TrashedFileInfo {
+ int trashId; // for the url
+ QString fileId; // for the url
+ QString physicalPath; // for stat'ing etc.
+ QString origPath; // from info file
+ QDateTime deletionDate; // from info file
+ };
+ /// List trashed files
+ typedef QValueList<TrashedFileInfo> TrashedFileInfoList;
+ TrashedFileInfoList list();
+
+ /// Return the info for a given trashed file
+ bool infoForFile( int trashId, const QString& fileId, TrashedFileInfo& info );
+
+ /// Return the physicalPath for a given trashed file - helper method which
+ /// encapsulates the call to infoForFile. Don't use if you need more info from TrashedFileInfo.
+ QString physicalPath( int trashId, const QString& fileId, const QString& relativePath );
+
+ /// Move data from the old trash system to the new one
+ void migrateOldTrash();
+
+ /// KIO error code
+ int lastErrorCode() const { return m_lastErrorCode; }
+ QString lastErrorMessage() const { return m_lastErrorMessage; }
+
+ QStrList listDir( const QString& physicalPath );
+
+ static KURL makeURL( int trashId, const QString& fileId, const QString& relativePath );
+ static bool parseURL( const KURL& url, int& trashId, QString& fileId, QString& relativePath );
+
+ typedef QMap<int, QString> TrashDirMap;
+ /// @internal This method is for TestTrash only. Home trash is included (id 0).
+ TrashDirMap trashDirectories() const;
+ /// @internal This method is for TestTrash only. No entry with id 0.
+ TrashDirMap topDirectories() const;
+
+private:
+ /// Helper method. Moves a file or directory using the appropriate method.
+ bool move( const QString& src, const QString& dest );
+ bool copy( const QString& src, const QString& dest );
+ /// Helper method. Tries to call ::rename(src,dest) and does error handling.
+ bool directRename( const QString& src, const QString& dest );
+
+ void fileAdded();
+ void fileRemoved();
+
+ // Warning, returns error code, not a bool
+ int testDir( const QString& name ) const;
+ void error( int e, const QString& s );
+
+ bool readInfoFile( const QString& infoPath, TrashedFileInfo& info, int trashId );
+
+ QString infoPath( int trashId, const QString& fileId ) const;
+ QString filesPath( int trashId, const QString& fileId ) const;
+
+ /// Find the trash dir to use for a given file to delete, based on original path
+ int findTrashDirectory( const QString& origPath );
+
+ QString trashDirectoryPath( int trashId ) const;
+ QString topDirectoryPath( int trashId ) const;
+
+ bool synchronousDel( const QString& path, bool setLastErrorCode, bool isDir );
+
+ void scanTrashDirectories() const;
+
+ int idForTrashDirectory( const QString& trashDir ) const;
+ bool initTrashDirectory( const QCString& trashDir_c ) const;
+ bool checkTrashSubdirs( const QCString& trashDir_c ) const;
+ QString trashForMountPoint( const QString& topdir, bool createIfNeeded ) const;
+ static QString makeRelativePath( const QString& topdir, const QString& path );
+
+private slots:
+ void jobFinished(KIO::Job *job);
+
+private:
+ /// Last error code stored in class to simplify API.
+ /// Note that this means almost no method can be const.
+ int m_lastErrorCode;
+ QString m_lastErrorMessage;
+
+ enum { InitToBeDone, InitOK, InitError } m_initStatus;
+
+ // A "trash directory" is a physical directory on disk,
+ // e.g. $HOME/.local/share/Trash/$uid or /mnt/foo/.Trash/$uid
+ // It has an id (number) and a path.
+ // The home trash has id 0.
+ mutable TrashDirMap m_trashDirectories; // id -> path of trash directory
+ mutable TrashDirMap m_topDirectories; // id -> $topdir of partition
+ mutable int m_lastId;
+ dev_t m_homeDevice;
+ mutable bool m_trashDirectoriesScanned;
+ int m_mibEnum;
+
+ KSimpleConfig m_config;
+
+ // We don't cache any data related to the trashed files.
+ // Another kioslave could change that behind our feet.
+ // If we want to start caching data - and avoiding some race conditions -,
+ // we should turn this class into a kded module and use DCOP to talk to it
+ // from the kioslave.
+};
+
+#endif