1
// Krusader modifications:
3
// Replace: CopyJob -> PreserveAttrCopyJob
4
// Replace: copyjob -> preserveattrcopyjob
7
/* This file is part of the KDE libraries
8
Copyright 2000 Stephan Kulow <coolo@kde.org>
9
Copyright 2000-2006 David Faure <faure@kde.org>
10
Copyright 2000 Waldo Bastian <bastian@kde.org>
12
This library is free software; you can redistribute it and/or
13
modify it under the terms of the GNU Library General Public
14
License as published by the Free Software Foundation; either
15
version 2 of the License, or (at your option) any later version.
17
This library is distributed in the hope that it will be useful,
18
but WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20
Library General Public License for more details.
22
You should have received a copy of the GNU Library General Public License
23
along with this library; see the file COPYING.LIB. If not, write to
24
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25
Boston, MA 02110-1301, USA.
28
#include "preserveattrcopyjob.h"
30
#if defined(Q_OS_UNIX) || defined(Q_WS_WIN)
34
#include <sys/stat.h> // mode_t
35
#include <sys/types.h>
39
#include <QtCore/QTimer>
40
#include <QtCore/QFile>
41
#include <QtCore/QPointer>
44
#include <kuiserverjobtracker.h>
45
#include <OrgKdeKDirNotifyInterface>
47
#include <KDesktopFile>
50
#include <KProtocolManager>
51
#include <KTemporaryFile>
53
#include <KIO/Scheduler>
54
#include <KIO/JobUiDelegate>
55
#include <KIO/DeleteJob>
57
Attributes::Attributes()
66
Attributes::Attributes(time_t tIn, uid_t uIn, gid_t gIn, mode_t modeIn, const QString & aclIn)
68
time = tIn, uid = uIn, gid = gIn, mode = modeIn, acl = aclIn;
71
Attributes::Attributes(time_t tIn, QString user, QString group, mode_t modeIn, const QString & aclIn)
75
struct passwd* pw = getpwnam(QFile::encodeName(user));
79
struct group* g = getgrnam(QFile::encodeName(group));
88
//this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
89
#define REPORT_TIMEOUT 200
91
#define KIO_ARGS QByteArray packedArgs; QDataStream stream( &packedArgs, QIODevice::WriteOnly ); stream
93
PreserveAttrCopyJob::PreserveAttrCopyJob(const KUrl::List& src, const KUrl& dest,
94
CopyJob::CopyMode mode, bool asMethod)
95
: Job(), m_globalDest(dest)
96
, m_globalDestinationState(DEST_NOT_STATED)
97
, m_defaultPermissions(false)
100
, m_asMethod(asMethod)
101
, destinationState(DEST_NOT_STATED)
102
, state(STATE_STATING)
105
, m_fileProcessedSize(0)
106
, m_processedFiles(0)
109
, m_currentStatSrc(m_srcList.begin())
110
, m_bCurrentOperationIsLink(false)
111
, m_bSingleFileCopy(false)
112
, m_bOnlyRenames(mode == CopyJob::Move)
115
, m_bOverwriteAll(false)
120
QTimer::singleShot(0, this, SLOT(slotStart()));
123
PreserveAttrCopyJob::~PreserveAttrCopyJob()
127
KUrl::List PreserveAttrCopyJob::srcUrls() const
132
KUrl PreserveAttrCopyJob::destUrl() const
137
void PreserveAttrCopyJob::slotStart()
140
We call the functions directly instead of using signals.
141
Calling a function via a signal takes approx. 65 times the time
142
compared to calling it directly (at least on my machine). aleXXX
144
m_reportTimer = new QTimer(this);
146
connect(m_reportTimer, SIGNAL(timeout()), this, SLOT(slotReport()));
147
m_reportTimer->start(REPORT_TIMEOUT);
148
connect(this, SIGNAL(result(KJob *)), this, SLOT(slotFinished()));
151
KIO::Job * job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
152
//kDebug(7007) << "PreserveAttrCopyJob:stating the dest " << m_dest;
156
// For unit test purposes
157
static bool kio_resolve_local_urls = true;
159
void PreserveAttrCopyJob::slotResultStating(KJob *job)
161
//kDebug(7007) << "PreserveAttrCopyJob::slotResultStating";
162
// Was there an error while stating the src ?
163
if (job->error() && destinationState != DEST_NOT_STATED) {
164
KUrl srcurl = ((SimpleJob*)job)->url();
165
if (!srcurl.isLocalFile()) {
166
// Probably : src doesn't exist. Well, over some protocols (e.g. FTP)
167
// this info isn't really reliable (thanks to MS FTP servers).
168
// We'll assume a file, and try to download anyway.
169
//kDebug(7007) << "Error while stating source. Activating hack";
171
assert(!hasSubjobs()); // We should have only one job at a time ...
172
struct CopyInfo info;
173
info.permissions = (mode_t) - 1;
174
info.mtime = (time_t) - 1;
175
info.ctime = (time_t) - 1;
176
info.size = (KIO::filesize_t) - 1;
177
info.uSource = srcurl;
179
// Append filename or dirname to destination URL, if allowed
180
if (destinationState == DEST_IS_DIR && !m_asMethod)
181
info.uDest.addPath(srcurl.fileName());
187
// Local file. If stat fails, the file definitely doesn't exist.
188
// yes, Job::, because we don't want to call our override
189
Job::slotResult(job); // will set the error and emit result(this)
193
// Keep copy of the stat result
194
const UDSEntry entry = static_cast<StatJob*>(job)->statResult();
195
const QString sLocalPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
196
const bool isDir = entry.isDir();
198
if (destinationState == DEST_NOT_STATED)
199
// we were stating the dest
202
destinationState = DEST_DOESNT_EXIST;
204
// Treat symlinks to dirs as dirs here, so no test on isLink
205
destinationState = isDir ? DEST_IS_DIR : DEST_IS_FILE;
206
//kDebug(7007) << "PreserveAttrCopyJob::slotResultStating dest is dir:" << bDir;
208
const bool isGlobalDest = m_dest == m_globalDest;
210
m_globalDestinationState = destinationState;
212
if (!sLocalPath.isEmpty() && kio_resolve_local_urls) {
214
m_dest.setPath(sLocalPath);
216
m_globalDest = m_dest;
220
assert(!hasSubjobs());
222
// After knowing what the dest is, we can start stat'ing the first src.
227
// Is it a file or a dir ?
228
const QString sName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
230
// We were stating the current source URL
231
m_currentDest = m_dest; // used by slotEntries
232
// Create a dummy list with it, for slotEntries
236
// There 6 cases, and all end up calling slotEntries(job, lst) first :
237
// 1 - src is a dir, destination is a directory,
238
// slotEntries will append the source-dir-name to the destination
239
// 2 - src is a dir, destination is a file, ERROR (done later on)
240
// 3 - src is a dir, destination doesn't exist, then it's the destination dirname,
241
// so slotEntries will use it as destination.
243
// 4 - src is a file, destination is a directory,
244
// slotEntries will append the filename to the destination.
245
// 5 - src is a file, destination is a file, m_dest is the exact destination name
246
// 6 - src is a file, destination doesn't exist, m_dest is the exact destination name
247
// Tell slotEntries not to alter the src url
248
m_bCurrentSrcIsDir = false;
249
slotEntries(static_cast<KIO::Job*>(job), lst);
252
if (!sLocalPath.isEmpty())
253
srcurl.setPath(sLocalPath);
255
srcurl = ((SimpleJob*)job)->url();
258
assert(!hasSubjobs()); // We should have only one job at a time ...
261
// treat symlinks as files (no recursion)
263
&& m_mode != CopyJob::Link) { // No recursion in Link mode either.
264
//kDebug(7007) << " Source is a directory ";
266
m_bCurrentSrcIsDir = true; // used by slotEntries
267
if (destinationState == DEST_IS_DIR) { // (case 1)
269
// Use <desturl>/<directory_copied> as destination, from now on
270
QString directory = srcurl.fileName();
271
if (!sName.isEmpty() && KProtocolManager::fileNameUsedForCopying(srcurl) == KProtocolInfo::Name) {
274
m_currentDest.addPath(directory);
276
} else if (destinationState == DEST_IS_FILE) { // (case 2)
277
setError(ERR_IS_FILE);
278
setErrorText(m_dest.prettyUrl());
282
// otherwise dest is new name for toplevel dir
283
// so the destination exists, in fact, from now on.
284
// (This even works with other src urls in the list, since the
285
// dir has effectively been created)
286
destinationState = DEST_IS_DIR;
287
if (m_dest == m_globalDest)
288
m_globalDestinationState = destinationState;
291
startListing(srcurl);
293
//kDebug(7007) << " Source is a file (or a symlink), or we are linking -> no recursive listing ";
298
bool PreserveAttrCopyJob::doSuspend()
301
return Job::doSuspend();
304
void PreserveAttrCopyJob::slotReport()
308
// If showProgressInfo was set, progressId() is > 0.
310
case STATE_COPYING_FILES:
311
setProcessedAmount(KJob::Files, m_processedFiles);
313
// Only emit urls when they changed. This saves time, and fixes #66281
315
if (m_mode == CopyJob::Move) {
316
emit description(this, i18nc("@title job", "Moving"),
317
qMakePair(i18n("Source"), m_currentSrcURL.prettyUrl()),
318
qMakePair(i18n("Destination"), m_currentDestURL.prettyUrl()));
319
emit moving(this, m_currentSrcURL, m_currentDestURL);
320
} else if (m_mode == CopyJob::Link) {
321
emit description(this, i18nc("@title job", "Copying"),
322
qMakePair(i18n("Source"), m_currentSrcURL.prettyUrl()),
323
qMakePair(i18n("Destination"), m_currentDestURL.prettyUrl()));
324
emit linking(this, m_currentSrcURL.path(), m_currentDestURL);
326
emit description(this, i18nc("@title job", "Copying"),
327
qMakePair(i18n("Source"), m_currentSrcURL.prettyUrl()),
328
qMakePair(i18n("Destination"), m_currentDestURL.prettyUrl()));
329
emit copying(this, m_currentSrcURL, m_currentDestURL);
334
case STATE_CREATING_DIRS:
335
setProcessedAmount(KJob::Directories, m_processedDirs);
338
emit description(this, i18nc("@title job", "Creating directory"),
339
qMakePair(i18n("Directory"), m_currentDestURL.prettyUrl()));
340
emit creatingDir(this, m_currentDestURL);
348
emit description(this, i18nc("@title job", "Copying"),
349
qMakePair(i18n("Source"), m_currentSrcURL.prettyUrl()),
350
qMakePair(i18n("Destination"), m_currentDestURL.prettyUrl()));
352
setTotalAmount(KJob::Bytes, m_totalSize);
353
setTotalAmount(KJob::Files, files.count());
354
setTotalAmount(KJob::Directories, dirs.count());
362
void PreserveAttrCopyJob::slotEntries(KIO::Job* job, const UDSEntryList& list)
364
saveEntries(job, list);
366
UDSEntryList::ConstIterator it = list.begin();
367
UDSEntryList::ConstIterator end = list.end();
368
for (; it != end; ++it) {
369
const UDSEntry& entry = *it;
370
struct CopyInfo info;
371
info.permissions = entry.numberValue(KIO::UDSEntry::UDS_ACCESS, -1);
372
info.mtime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
373
info.ctime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
374
info.size = (KIO::filesize_t) entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1);
375
if (info.size != (KIO::filesize_t) - 1)
376
m_totalSize += info.size;
378
// recursive listing, displayName can be a/b/c/d
379
const QString displayName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
380
const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
382
if (!urlStr.isEmpty())
384
QString localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
385
const bool isDir = entry.isDir();
386
info.linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
388
if (displayName != ".." && displayName != ".") {
389
bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty();
391
// Make URL from displayName
392
url = static_cast<SimpleJob *>(job)->url();
393
if (m_bCurrentSrcIsDir) { // Only if src is a directory. Otherwise uSource is fine as is
394
//kDebug(7007) << "adding path " << displayName;
395
url.addPath(displayName);
398
//kDebug(7007) << "displayName=" << displayName << " url=" << url;
399
if (!localPath.isEmpty() && kio_resolve_local_urls) {
401
url.setPath(localPath);
405
info.uDest = m_currentDest;
406
//kDebug(7007) << " uSource=" << info.uSource << " uDest(1)=" << info.uDest;
407
// Append filename or dirname to destination URL, if allowed
408
if (destinationState == DEST_IS_DIR &&
409
// "copy/move as <foo>" means 'foo' is the dest for the base srcurl
410
// (passed here during stating) but not its children (during listing)
411
(!(m_asMethod && state == STATE_STATING))) {
412
QString destFileName;
414
KProtocolManager::fileNameUsedForCopying(url) == KProtocolInfo::FromUrl) {
415
//destFileName = url.fileName(); // Doesn't work for recursive listing
416
// Count the number of prefixes used by the recursive listjob
417
int numberOfSlashes = displayName.count('/'); // don't make this a find()!
418
QString path = url.path();
420
for (int n = 0; n < numberOfSlashes + 1; ++n) {
421
pos = path.lastIndexOf('/', pos - 1);
422
if (pos == -1) { // error
423
kWarning(7007) << "kioslave bug: not enough slashes in UDS_URL " << path << " - looking for " << numberOfSlashes << " slashes";
428
destFileName = path.mid(pos + 1);
431
} else { // destination filename taken from UDS_NAME
432
destFileName = displayName;
435
// Here we _really_ have to add some filename to the dest.
436
// Otherwise, we end up with e.g. dest=..../Desktop/ itself.
437
// (This can happen when dropping a link to a webpage with no path)
438
if (destFileName.isEmpty())
439
destFileName = KIO::encodeFileName(info.uSource.prettyUrl());
441
//kDebug(7007) << " adding destFileName=" << destFileName;
442
info.uDest.addPath(destFileName);
444
//kDebug(7007) << " uDest(2)=" << info.uDest;
445
//kDebug(7007) << " " << info.uSource << " -> " << info.uDest;
446
if (info.linkDest.isEmpty() && isDir && m_mode != CopyJob::Link) { // Dir
447
dirs.append(info); // Directories
448
if (m_mode == CopyJob::Move)
449
dirsToRemove.append(info.uSource);
451
files.append(info); // Files and any symlinks
457
void PreserveAttrCopyJob::skipSrc()
459
m_dest = m_globalDest;
460
destinationState = m_globalDestinationState;
462
skip(m_currentSrcURL);
466
void PreserveAttrCopyJob::statNextSrc()
468
/* Revert to the global destination, the one that applies to all source urls.
469
* Imagine you copy the items a b and c into /d, but /d/b exists so the user uses "Rename" to put it in /foo/b instead.
470
* m_dest is /foo/b for b, but we have to revert to /d for item c and following.
472
m_dest = m_globalDest;
473
destinationState = m_globalDestinationState;
478
void PreserveAttrCopyJob::statCurrentSrc()
480
if (m_currentStatSrc != m_srcList.end()) {
481
m_currentSrcURL = (*m_currentStatSrc);
483
if (m_mode == CopyJob::Link) {
484
// Skip the "stating the source" stage, we don't need it for linking
485
m_currentDest = m_dest;
486
struct CopyInfo info;
487
info.permissions = -1;
488
info.mtime = (time_t) - 1;
489
info.ctime = (time_t) - 1;
490
info.size = (KIO::filesize_t) - 1;
491
info.uSource = m_currentSrcURL;
492
info.uDest = m_currentDest;
493
// Append filename or dirname to destination URL, if allowed
494
if (destinationState == DEST_IS_DIR && !m_asMethod) {
496
(m_currentSrcURL.protocol() == info.uDest.protocol()) &&
497
(m_currentSrcURL.host() == info.uDest.host()) &&
498
(m_currentSrcURL.port() == info.uDest.port()) &&
499
(m_currentSrcURL.user() == info.uDest.user()) &&
500
(m_currentSrcURL.pass() == info.uDest.pass())) {
501
// This is the case of creating a real symlink
502
info.uDest.addPath(m_currentSrcURL.fileName());
504
// Different protocols, we'll create a .desktop file
505
// We have to change the extension anyway, so while we're at it,
506
// name the file like the URL
507
info.uDest.addPath(KIO::encodeFileName(m_currentSrcURL.prettyUrl()) + ".desktop");
510
files.append(info); // Files and any symlinks
511
statNextSrc(); // we could use a loop instead of a recursive call :)
513
} else if (m_mode == CopyJob::Move && (
514
// Don't go renaming right away if we need a stat() to find out the destination filename
515
KProtocolManager::fileNameUsedForCopying(m_currentSrcURL) == KProtocolInfo::FromUrl ||
516
destinationState != DEST_IS_DIR || m_asMethod)
518
// If moving, before going for the full stat+[list+]copy+del thing, try to rename
519
// The logic is pretty similar to FilePreserveAttrCopyJob::slotStart()
520
if ((m_currentSrcURL.protocol() == m_dest.protocol()) &&
521
(m_currentSrcURL.host() == m_dest.host()) &&
522
(m_currentSrcURL.port() == m_dest.port()) &&
523
(m_currentSrcURL.user() == m_dest.user()) &&
524
(m_currentSrcURL.pass() == m_dest.pass())) {
525
startRenameJob(m_currentSrcURL);
527
} else if (m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) {
528
startRenameJob(m_dest);
530
} else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_currentSrcURL)) {
531
startRenameJob(m_currentSrcURL);
536
// if the file system doesn't support deleting, we do not even stat
537
if (m_mode == CopyJob::Move && !KProtocolManager::supportsDeleting(m_currentSrcURL)) {
538
QPointer<PreserveAttrCopyJob> that = this;
539
emit warning(this, buildErrorString(ERR_CANNOT_DELETE, m_currentSrcURL.prettyUrl()));
541
statNextSrc(); // we could use a loop instead of a recursive call :)
545
// Stat the next src url
546
Job * job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo);
547
//kDebug(7007) << "KIO::stat on " << m_currentSrcURL;
548
state = STATE_STATING;
550
m_currentDestURL = m_dest;
551
m_bOnlyRenames = false;
554
// Finished the stat'ing phase
555
// First make sure that the totals were correctly emitted
556
state = STATE_STATING;
559
if (!dirs.isEmpty()) {
560
slotAboutToCreate(dirs);
561
emit aboutToCreate(this, dirs);
563
if (!files.isEmpty()) {
564
slotAboutToCreate(files);
565
emit aboutToCreate(this, files);
567
// Check if we are copying a single file
568
m_bSingleFileCopy = (files.count() == 1 && dirs.isEmpty());
569
// Then start copying things
570
state = STATE_CREATING_DIRS;
575
void PreserveAttrCopyJob::startRenameJob(const KUrl& slave_url)
578
// Append filename or dirname to destination URL, if allowed
579
if (destinationState == DEST_IS_DIR && !m_asMethod)
580
dest.addPath(m_currentSrcURL.fileName());
581
//kDebug(7007) << "This seems to be a suitable case for trying to rename before stat+[list+]copy+del";
582
state = STATE_RENAMING;
584
struct CopyInfo info;
585
info.permissions = -1;
586
info.mtime = (time_t) - 1;
587
info.ctime = (time_t) - 1;
588
info.size = (KIO::filesize_t) - 1;
589
info.uSource = m_currentSrcURL;
591
QList<CopyInfo> files;
593
slotAboutToCreate(files);
594
emit aboutToCreate(this, files);
596
SimpleJob * newJob = KIO::rename(m_currentSrcURL, dest, 0);
597
newJob->setUiDelegate(new JobUiDelegate());
598
Scheduler::scheduleJob(newJob);
600
if (m_currentSrcURL.directory() != dest.directory()) // For the user, moving isn't renaming. Only renaming is.
601
m_bOnlyRenames = false;
604
void PreserveAttrCopyJob::startListing(const KUrl & src)
606
state = STATE_LISTING;
608
ListJob * newjob = listRecursive(src, KIO::HideProgressInfo);
609
newjob->setUiDelegate(new JobUiDelegate());
610
newjob->setUnrestricted(true);
611
connect(newjob, SIGNAL(entries(KIO::Job *, const KIO::UDSEntryList&)),
612
SLOT(slotEntries(KIO::Job*, const KIO::UDSEntryList&)));
616
void PreserveAttrCopyJob::skip(const KUrl & sourceUrl)
618
// If this is one if toplevel sources,
619
// remove it from m_srcList, for a correct FilesRemoved() signal
620
//kDebug(7007) << "PreserveAttrCopyJob::skip: looking for " << sourceUrl;
621
m_srcList.removeAll(sourceUrl);
622
dirsToRemove.removeAll(sourceUrl);
625
bool PreserveAttrCopyJob::shouldOverwrite(const QString& path) const
629
QStringList::ConstIterator sit = m_overwriteList.begin();
630
for (; sit != m_overwriteList.end(); ++sit)
631
if (path.startsWith(*sit))
636
bool PreserveAttrCopyJob::shouldSkip(const QString& path) const
638
QStringList::ConstIterator sit = m_skipList.begin();
639
for (; sit != m_skipList.end(); ++sit)
640
if (path.startsWith(*sit))
645
void PreserveAttrCopyJob::slotResultCreatingDirs(KJob * job)
647
// The dir we are trying to create:
648
QList<CopyInfo>::Iterator it = dirs.begin();
649
// Was there an error creating a dir ?
651
m_conflictError = job->error();
652
if ((m_conflictError == ERR_DIR_ALREADY_EXIST)
653
|| (m_conflictError == ERR_FILE_ALREADY_EXIST)) { // can't happen?
654
KUrl oldURL = ((SimpleJob*)job)->url();
655
// Should we skip automatically ?
657
// We don't want to copy files in this directory, so we put it on the skip list
658
m_skipList.append(oldURL.path(KUrl::AddTrailingSlash));
660
dirs.erase(it); // Move on to next dir
662
// Did the user choose to overwrite already?
663
const QString destFile = (*it).uDest.path();
664
if (shouldOverwrite(destFile)) { // overwrite => just skip
665
slotCopyingDone((*it).uSource, (*it).uDest, true);
666
emit copyingDone(this, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */);
667
dirs.erase(it); // Move on to next dir
669
if (!isInteractive()) {
670
Job::slotResult(job); // will set the error and emit result(this)
674
assert(((SimpleJob*)job)->url().url() == (*it).uDest.url());
676
assert(!hasSubjobs()); // We should have only one job at a time ...
678
// We need to stat the existing dir, to get its last-modification time
679
KUrl existingDest((*it).uDest);
680
SimpleJob * newJob = KIO::stat(existingDest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
681
Scheduler::scheduleJob(newJob);
682
//kDebug(7007) << "KIO::stat for resolving conflict on " << existingDest;
683
state = STATE_CONFLICT_CREATING_DIRS;
685
return; // Don't move to next dir yet !
689
// Severe error, abort
690
Job::slotResult(job); // will set the error and emit result(this)
693
} else { // no error : remove from list, to move on to next dir
694
//this is required for the undo feature
695
slotCopyingDone((*it).uSource, (*it).uDest, true);
696
emit copyingDone(this, (*it).uSource, (*it).uDest, (*it).mtime, true, false);
697
m_directoriesCopied.append(*it);
702
//emit processedAmount( this, KJob::Directories, m_processedDirs );
704
assert(!hasSubjobs()); // We should have only one job at a time ...
708
void PreserveAttrCopyJob::slotResultConflictCreatingDirs(KJob * job)
710
// We come here after a conflict has been detected and we've stated the existing dir
712
// The dir we were trying to create:
713
QList<CopyInfo>::Iterator it = dirs.begin();
715
const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
717
// Its modification time:
718
const time_t destmtime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
719
const time_t destctime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
721
const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE);
722
const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
725
assert(!hasSubjobs()); // We should have only one job at a time ...
727
// Always multi and skip (since there are files after that)
728
RenameDialog_Mode mode = (RenameDialog_Mode)(M_MULTI | M_SKIP);
729
// Overwrite only if the existing thing is a dir (no chance with a file)
730
if (m_conflictError == ERR_DIR_ALREADY_EXIST) {
731
if ((*it).uSource == (*it).uDest ||
732
((*it).uSource.protocol() == (*it).uDest.protocol() &&
733
(*it).uSource.path(KUrl::RemoveTrailingSlash) == linkDest))
734
mode = (RenameDialog_Mode)(mode | M_OVERWRITE_ITSELF);
736
mode = (RenameDialog_Mode)(mode | M_OVERWRITE);
739
QString existingDest = (*it).uDest.path();
742
m_reportTimer->stop();
743
RenameDialog_Result r = ui()->askFileRename(this, i18n("Directory Already Exists"),
747
(*it).size, destsize,
748
(*it).ctime, destctime,
749
(*it).mtime, destmtime);
751
m_reportTimer->start(REPORT_TIMEOUT);
754
setError(ERR_USER_CANCELED);
758
QString oldPath = (*it).uDest.path(KUrl::AddTrailingSlash);
759
KUrl newUrl((*it).uDest);
760
newUrl.setPath(newPath);
761
emit renamed(this, (*it).uDest, newUrl); // for e.g. kpropsdlg
763
// Change the current one and strip the trailing '/'
764
(*it).uDest.setPath(newUrl.path(KUrl::RemoveTrailingSlash));
765
newPath = newUrl.path(KUrl::AddTrailingSlash); // With trailing slash
766
QList<CopyInfo>::Iterator renamedirit = it;
768
// Change the name of subdirectories inside the directory
769
for (; renamedirit != dirs.end() ; ++renamedirit) {
770
QString path = (*renamedirit).uDest.path();
771
if (path.startsWith(oldPath)) {
773
n.replace(0, oldPath.length(), newPath);
774
//kDebug(7007) << "dirs list: " << (*renamedirit).uSource.path() << " was going to be " << path << ", changed into " << n << endl;
775
(*renamedirit).uDest.setPath(n);
778
// Change filenames inside the directory
779
QList<CopyInfo>::Iterator renamefileit = files.begin();
780
for (; renamefileit != files.end() ; ++renamefileit) {
781
QString path = (*renamefileit).uDest.path();
782
if (path.startsWith(oldPath)) {
784
n.replace(0, oldPath.length(), newPath);
785
//kDebug(7007) << "files list: " << (*renamefileit).uSource.path() << " was going to be " << path << ", changed into " << n << endl;
786
(*renamefileit).uDest.setPath(n);
789
if (!dirs.isEmpty()) {
790
slotAboutToCreate(dirs);
791
emit aboutToCreate(this, dirs);
793
if (!files.isEmpty()) {
794
slotAboutToCreate(files);
795
emit aboutToCreate(this, files);
803
m_skipList.append(existingDest);
805
// Move on to next dir
810
m_overwriteList.append(existingDest);
811
slotCopyingDone((*it).uSource, (*it).uDest, true);
812
emit copyingDone(this, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */);
813
// Move on to next dir
817
case R_OVERWRITE_ALL:
818
m_bOverwriteAll = true;
819
slotCopyingDone((*it).uSource, (*it).uDest, true);
820
emit copyingDone(this, (*it).uSource, (*it).uDest, (*it).mtime, true /* directory */, false /* renamed */);
821
// Move on to next dir
828
state = STATE_CREATING_DIRS;
829
//emit processedAmount( this, KJob::Directories, m_processedDirs );
833
void PreserveAttrCopyJob::createNextDir()
836
if (!dirs.isEmpty()) {
837
// Take first dir to create out of list
838
QList<CopyInfo>::Iterator it = dirs.begin();
839
// Is this URL on the skip list or the overwrite list ?
840
while (it != dirs.end() && udir.isEmpty()) {
841
const QString dir = (*it).uDest.path();
842
if (shouldSkip(dir)) {
849
if (!udir.isEmpty()) { // any dir to create, finally ?
850
// Create the directory - with default permissions so that we can put files into it
851
// TODO : change permissions once all is finished; but for stuff coming from CDROM it sucks...
852
KIO::SimpleJob *newjob = KIO::mkdir(udir, -1);
853
Scheduler::scheduleJob(newjob);
855
m_currentDestURL = udir;
860
} else { // we have finished creating dirs
861
setProcessedAmount(KJob::Directories, m_processedDirs); // make sure final number appears
863
state = STATE_COPYING_FILES;
864
m_processedFiles++; // Ralf wants it to start at 1, not 0
869
void PreserveAttrCopyJob::slotResultCopyingFiles(KJob * job)
871
// The file we were trying to copy:
872
QList<CopyInfo>::Iterator it = files.begin();
874
// Should we skip automatically ?
877
m_fileProcessedSize = (*it).size;
878
files.erase(it); // Move on to next file
880
if (!isInteractive()) {
881
Job::slotResult(job); // will set the error and emit result(this)
885
m_conflictError = job->error(); // save for later
887
if ((m_conflictError == ERR_FILE_ALREADY_EXIST)
888
|| (m_conflictError == ERR_DIR_ALREADY_EXIST)
889
|| (m_conflictError == ERR_IDENTICAL_FILES)) {
891
assert(!hasSubjobs());
892
// We need to stat the existing file, to get its last-modification time
893
KUrl existingFile((*it).uDest);
894
SimpleJob * newJob = KIO::stat(existingFile, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
895
Scheduler::scheduleJob(newJob);
896
//kDebug(7007) << "KIO::stat for resolving conflict on " << existingFile;
897
state = STATE_CONFLICT_COPYING_FILES;
899
return; // Don't move to next file yet !
901
if (m_bCurrentOperationIsLink && qobject_cast<KIO::DeleteJob*>(job)) {
902
// Very special case, see a few lines below
903
// We are deleting the source of a symlink we successfully moved... ignore error
904
m_fileProcessedSize = (*it).size;
907
// Go directly to the conflict resolution, there is nothing to stat
908
slotResultConflictCopyingFiles(job);
914
// Special case for moving links. That operation needs two jobs, unlike others.
915
if (m_bCurrentOperationIsLink && m_mode == CopyJob::Move
916
&& !qobject_cast<KIO::DeleteJob *>(job) // Deleting source not already done
919
assert(!hasSubjobs());
920
// The only problem with this trick is that the error handling for this del operation
921
// is not going to be right... see 'Very special case' above.
922
KIO::Job * newjob = KIO::del((*it).uSource, HideProgressInfo);
924
return; // Don't move to next file yet !
927
if (m_bCurrentOperationIsLink) {
928
QString target = (m_mode == CopyJob::Link ? (*it).uSource.path() : (*it).linkDest);
929
//required for the undo feature
930
emit copyingLinkDone(this, (*it).uSource, target, (*it).uDest);
932
//required for the undo feature
933
slotCopyingDone((*it).uSource, (*it).uDest, false);
934
emit copyingDone(this, (*it).uSource, (*it).uDest, (*it).mtime, false, false);
936
// remove from list, to move on to next file
941
// clear processed size for last file and add it to overall processed size
942
m_processedSize += m_fileProcessedSize;
943
m_fileProcessedSize = 0;
945
//kDebug(7007) << files.count() << " files remaining";
947
// Merge metadata from subjob
948
KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
950
//m_incomingMetaData += kiojob->metaData();
952
assert(!hasSubjobs()); // We should have only one job at a time ...
956
void PreserveAttrCopyJob::slotResultConflictCopyingFiles(KJob * job)
958
// We come here after a conflict has been detected and we've stated the existing file
959
// The file we were trying to create:
960
QList<CopyInfo>::Iterator it = files.begin();
962
RenameDialog_Result res;
966
m_reportTimer->stop();
968
if ((m_conflictError == ERR_FILE_ALREADY_EXIST)
969
|| (m_conflictError == ERR_DIR_ALREADY_EXIST)
970
|| (m_conflictError == ERR_IDENTICAL_FILES)) {
971
// Its modification time:
972
const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
974
const time_t destmtime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1);
975
const time_t destctime = (time_t) entry.numberValue(KIO::UDSEntry::UDS_CREATION_TIME, -1);
976
const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE);
977
const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
979
// Offer overwrite only if the existing thing is a file
980
// If src==dest, use "overwrite-itself"
981
RenameDialog_Mode mode;
984
if (m_conflictError == ERR_DIR_ALREADY_EXIST)
985
mode = (RenameDialog_Mode) 0;
987
if ((*it).uSource == (*it).uDest ||
988
((*it).uSource.protocol() == (*it).uDest.protocol() &&
989
(*it).uSource.path(KUrl::RemoveTrailingSlash) == linkDest))
990
mode = M_OVERWRITE_ITSELF;
996
if (m_bSingleFileCopy)
997
mode = (RenameDialog_Mode)(mode | M_SINGLE);
999
mode = (RenameDialog_Mode)(mode | M_MULTI | M_SKIP);
1001
res = ui()->askFileRename(this, !isDir ?
1002
i18n("File Already Exists") : i18n("Already Exists as Directory"),
1003
(*it).uSource.url(),
1006
(*it).size, destsize,
1007
(*it).ctime, destctime,
1008
(*it).mtime, destmtime);
1011
if (job->error() == ERR_USER_CANCELED)
1013
else if (!isInteractive()) {
1014
Job::slotResult(job); // will set the error and emit result(this)
1017
SkipDialog_Result skipResult = ui()->askSkip(this, files.count() > 1,
1018
job->errorString());
1020
// Convert the return code from SkipDialog into a RenameDialog code
1021
res = (skipResult == S_SKIP) ? R_SKIP :
1022
(skipResult == S_AUTO_SKIP) ? R_AUTO_SKIP :
1028
m_reportTimer->start(REPORT_TIMEOUT);
1031
assert(!hasSubjobs());
1034
setError(ERR_USER_CANCELED);
1038
KUrl newUrl((*it).uDest);
1039
newUrl.setPath(newPath);
1040
emit renamed(this, (*it).uDest, newUrl); // for e.g. kpropsdlg
1041
(*it).uDest = newUrl;
1043
QList<CopyInfo> files;
1045
slotAboutToCreate(files);
1046
emit aboutToCreate(this, files);
1053
// Move on to next file
1054
skip((*it).uSource);
1055
m_processedSize += (*it).size;
1059
case R_OVERWRITE_ALL:
1060
m_bOverwriteAll = true;
1063
// Add to overwrite list, so that copyNextFile knows to overwrite
1064
m_overwriteList.append((*it).uDest.path());
1069
state = STATE_COPYING_FILES;
1070
//emit processedAmount( this, KJob::Files, m_processedFiles );
1074
KIO::Job* PreserveAttrCopyJob::linkNextFile(const KUrl& uSource, const KUrl& uDest, JobFlags flags)
1076
//kDebug(7007) << "Linking";
1078
(uSource.protocol() == uDest.protocol()) &&
1079
(uSource.host() == uDest.host()) &&
1080
(uSource.port() == uDest.port()) &&
1081
(uSource.user() == uDest.user()) &&
1082
(uSource.pass() == uDest.pass())) {
1083
// This is the case of creating a real symlink
1084
KIO::SimpleJob *newJob = KIO::symlink(uSource.path(), uDest, flags | HideProgressInfo /*no GUI*/);
1085
Scheduler::scheduleJob(newJob);
1086
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Linking target=" << uSource.path() << " link=" << uDest;
1087
//emit linking( this, uSource.path(), uDest );
1088
m_bCurrentOperationIsLink = true;
1089
m_currentSrcURL = uSource;
1090
m_currentDestURL = uDest;
1092
//Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps
1095
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Linking URL=" << uSource << " link=" << uDest;
1096
if (uDest.isLocalFile()) {
1097
// if the source is a devices url, handle it a littlebit special
1099
QString path = uDest.path();
1100
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile path=" << path;
1102
if (f.open(QIODevice::ReadWrite)) {
1104
KDesktopFile desktopFile(path);
1105
KConfigGroup config = desktopFile.desktopGroup();
1108
config.writePathEntry("URL", url.url());
1109
config.writeEntry("Name", url.url());
1110
config.writeEntry("Type", QString::fromLatin1("Link"));
1111
QString protocol = uSource.protocol();
1112
if (protocol == QLatin1String("ftp"))
1113
config.writeEntry("Icon", QString::fromLatin1("folder-remote"));
1114
else if (protocol == QLatin1String("http"))
1115
config.writeEntry("Icon", QString::fromLatin1("text-html"));
1116
else if (protocol == QLatin1String("document-properties"))
1117
config.writeEntry("Icon", QString::fromLatin1("text-x-texinfo"));
1118
else if (protocol == QLatin1String("mailto")) // sven:
1119
config.writeEntry("Icon", QString::fromLatin1("internet-mail")); // added mailto: support
1121
config.writeEntry("Icon", QString::fromLatin1("unknown"));
1123
files.erase(files.begin()); // done with this one, move on
1125
//emit processedAmount( this, KJob::Files, m_processedFiles );
1129
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile ERR_CANNOT_OPEN_FOR_WRITING";
1130
setError(ERR_CANNOT_OPEN_FOR_WRITING);
1131
setErrorText(uDest.path());
1136
// Todo: not show "link" on remote dirs if the src urls are not from the same protocol+host+...
1137
setError(ERR_CANNOT_SYMLINK);
1138
setErrorText(uDest.prettyUrl());
1145
void PreserveAttrCopyJob::copyNextFile()
1147
bool bCopyFile = false;
1148
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile()";
1149
// Take the first file in the list
1150
QList<CopyInfo>::Iterator it = files.begin();
1151
// Is this URL on the skip list ?
1152
while (it != files.end() && !bCopyFile) {
1153
const QString destFile = (*it).uDest.path();
1154
bCopyFile = !shouldSkip(destFile);
1161
if (bCopyFile) { // any file to create, finally ?
1162
const KUrl& uSource = (*it).uSource;
1163
const KUrl& uDest = (*it).uDest;
1164
// Do we set overwrite ?
1166
const QString destFile = uDest.path();
1167
//kDebug(7007) << "copying " << destFile;
1168
if (uDest == uSource)
1171
bOverwrite = shouldOverwrite(destFile);
1173
m_bCurrentOperationIsLink = false;
1174
KIO::Job * newjob = 0;
1175
if (m_mode == CopyJob::Link) {
1176
// User requested that a symlink be made
1177
JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1178
newjob = linkNextFile(uSource, uDest, flags);
1181
} else if (!(*it).linkDest.isEmpty() &&
1182
(uSource.protocol() == uDest.protocol()) &&
1183
(uSource.host() == uDest.host()) &&
1184
(uSource.port() == uDest.port()) &&
1185
(uSource.user() == uDest.user()) &&
1186
(uSource.pass() == uDest.pass()))
1187
// Copying a symlink - only on the same protocol/host/etc. (#5601, downloading an FTP file through its link),
1189
JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1190
KIO::SimpleJob *newJob = KIO::symlink((*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/);
1191
Scheduler::scheduleJob(newJob);
1193
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Linking target=" << (*it).linkDest << " link=" << uDest;
1194
m_currentSrcURL = KUrl((*it).linkDest);
1195
m_currentDestURL = uDest;
1197
//emit linking( this, (*it).linkDest, uDest );
1198
//Observer::self()->slotCopying( this, m_currentSrcURL, uDest ); // should be slotLinking perhaps
1199
m_bCurrentOperationIsLink = true;
1200
// NOTE: if we are moving stuff, the deletion of the source will be done in slotResultCopyingFiles
1201
} else if (m_mode == CopyJob::Move) { // Moving a file
1202
JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1203
KIO::FileCopyJob * moveJob = KIO::file_move(uSource, uDest, (*it).permissions, flags | HideProgressInfo/*no GUI*/);
1204
moveJob->setSourceSize((*it).size);
1206
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Moving " << uSource << " to " << uDest;
1207
//emit moving( this, uSource, uDest );
1208
m_currentSrcURL = uSource;
1209
m_currentDestURL = uDest;
1211
//Observer::self()->slotMoving( this, uSource, uDest );
1212
} else { // Copying a file
1213
// If source isn't local and target is local, we ignore the original permissions
1214
// Otherwise, files downloaded from HTTP end up with -r--r--r--
1215
bool remoteSource = !KProtocolManager::supportsListing(uSource);
1216
int permissions = (*it).permissions;
1217
if (m_defaultPermissions || (remoteSource && uDest.isLocalFile()))
1219
JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1220
KIO::FileCopyJob * copyJob = KIO::file_copy(uSource, uDest, permissions, flags | HideProgressInfo/*no GUI*/);
1221
copyJob->setParentJob(this); // in case of rename dialog
1222
copyJob->setSourceSize((*it).size);
1223
if ((*it).mtime != -1) {
1224
QDateTime dt; dt.setTime_t((*it).mtime);
1225
copyJob->setModificationTime(dt);
1228
//kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Copying " << uSource << " to " << uDest;
1229
m_currentSrcURL = uSource;
1230
m_currentDestURL = uDest;
1234
connect(newjob, SIGNAL(processedSize(KJob*, qulonglong)),
1235
SLOT(slotProcessedSize(KJob*, qulonglong)));
1236
connect(newjob, SIGNAL(totalSize(KJob*, qulonglong)),
1237
SLOT(slotTotalSize(KJob*, qulonglong)));
1240
//kDebug(7007) << "copyNextFile finished";
1245
void PreserveAttrCopyJob::deleteNextDir()
1247
if (m_mode == CopyJob::Move && !dirsToRemove.isEmpty()) { // some dirs to delete ?
1248
state = STATE_DELETING_DIRS;
1250
// Take first dir to delete out of list - last ones first !
1251
KUrl::List::Iterator it = --dirsToRemove.end();
1252
SimpleJob *job = KIO::rmdir(*it);
1253
Scheduler::scheduleJob(job);
1254
dirsToRemove.erase(it);
1257
// This step is done, move on
1258
state = STATE_SETTING_DIR_ATTRIBUTES;
1259
m_directoriesCopiedIterator = m_directoriesCopied.begin();
1260
setNextDirAttribute();
1264
void PreserveAttrCopyJob::setNextDirAttribute()
1266
while (m_directoriesCopiedIterator != m_directoriesCopied.end() &&
1267
(*m_directoriesCopiedIterator).mtime == -1) {
1268
++m_directoriesCopiedIterator;
1270
if (m_directoriesCopiedIterator != m_directoriesCopied.end()) {
1271
const KUrl url = (*m_directoriesCopiedIterator).uDest;
1272
const time_t mtime = (*m_directoriesCopiedIterator).mtime;
1273
const QDateTime dt = QDateTime::fromTime_t(mtime);
1274
++m_directoriesCopiedIterator;
1276
KIO::SimpleJob *job = KIO::setModificationTime(url, dt);
1277
Scheduler::scheduleJob(job);
1281
#if 0 // ifdef Q_OS_UNIX
1282
// TODO: can be removed now. Or reintroduced as a fast path for local files
1283
// if launching even more jobs as done above is a performance problem.
1285
QLinkedList<CopyInfo>::Iterator it = m_directoriesCopied.begin();
1286
for (; it != m_directoriesCopied.end() ; ++it) {
1287
const KUrl& url = (*it).uDest;
1288
if (url.isLocalFile() && (*it).mtime != (time_t) - 1) {
1289
const QByteArray path = QFile::encodeName(url.path());
1290
KDE_struct_stat statbuf;
1291
if (KDE_lstat(path, &statbuf) == 0) {
1292
struct utimbuf utbuf;
1293
utbuf.actime = statbuf.st_atime; // access time, unchanged
1294
utbuf.modtime = (*it).mtime; // modification time
1295
utime(path, &utbuf);
1300
m_directoriesCopied.clear();
1301
// but then we need to jump to the else part below. Maybe with a recursive call?
1304
// Finished - tell the world
1305
if (!m_bOnlyRenames) {
1306
KUrl url(m_globalDest);
1307
if (m_globalDestinationState != DEST_IS_DIR || m_asMethod)
1308
url.setPath(url.directory());
1309
//kDebug(7007) << "KDirNotify'ing FilesAdded " << url;
1310
org::kde::KDirNotify::emitFilesAdded(url.url());
1312
if (m_mode == CopyJob::Move && !m_srcList.isEmpty()) {
1313
//kDebug(7007) << "KDirNotify'ing FilesRemoved " << m_srcList.toStringList();
1314
org::kde::KDirNotify::emitFilesRemoved(m_srcList.toStringList());
1318
m_reportTimer->stop();
1319
--m_processedFiles; // undo the "start at 1" hack
1320
slotReport(); // display final numbers, important if progress dialog stays up
1326
void PreserveAttrCopyJob::slotProcessedSize(KJob*, qulonglong data_size)
1328
//kDebug(7007) << "PreserveAttrCopyJob::slotProcessedSize " << data_size;
1329
m_fileProcessedSize = data_size;
1330
setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
1332
if (m_processedSize + m_fileProcessedSize > m_totalSize) {
1333
m_totalSize = m_processedSize + m_fileProcessedSize;
1334
//kDebug(7007) << "Adjusting m_totalSize to " << m_totalSize;
1335
setTotalAmount(KJob::Bytes, m_totalSize); // safety
1337
//kDebug(7007) << "emit processedSize " << (unsigned long) (m_processedSize + m_fileProcessedSize);
1338
setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
1341
void PreserveAttrCopyJob::slotTotalSize(KJob*, qulonglong size)
1343
//kDebug(7007) << "slotTotalSize: " << size;
1344
// Special case for copying a single file
1345
// This is because some protocols don't implement stat properly
1346
// (e.g. HTTP), and don't give us a size in some cases (redirection)
1347
// so we'd rather rely on the size given for the transfer
1348
if (m_bSingleFileCopy && size > m_totalSize) {
1349
//kDebug(7007) << "slotTotalSize: updating totalsize to " << size;
1351
setTotalAmount(KJob::Bytes, size);
1355
void PreserveAttrCopyJob::slotResultDeletingDirs(KJob * job)
1358
// Couldn't remove directory. Well, perhaps it's not empty
1359
// because the user pressed Skip for a given file in it.
1360
// Let's not display "Could not remove dir ..." for each of those dir !
1363
assert(!hasSubjobs());
1367
void PreserveAttrCopyJob::slotResultSettingDirAttributes(KJob * job)
1370
// Couldn't set directory attributes. Ignore the error, it can happen
1371
// with inferior file systems like VFAT.
1372
// Let's not display warnings for each dir like "cp -a" does.
1375
assert(!hasSubjobs());
1376
setNextDirAttribute();
1379
// We were trying to do a direct renaming, before even stat'ing
1380
void PreserveAttrCopyJob::slotResultRenaming(KJob* job)
1382
int err = job->error();
1383
const QString errText = job->errorText();
1384
// Merge metadata from subjob
1385
KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
1387
//m_incomingMetaData += kiojob->metaData();
1389
assert(!hasSubjobs());
1390
// Determine dest again
1392
if (destinationState == DEST_IS_DIR && !m_asMethod)
1393
dest.addPath(m_currentSrcURL.fileName());
1395
// Direct renaming didn't work. Try renaming to a temp name,
1396
// this can help e.g. when renaming 'a' to 'A' on a VFAT partition.
1397
// In that case it's the _same_ dir, we don't want to copy+del (data loss!)
1398
if (m_currentSrcURL.isLocalFile() && m_currentSrcURL.url(KUrl::RemoveTrailingSlash) != dest.url(KUrl::RemoveTrailingSlash) &&
1399
m_currentSrcURL.url(KUrl::RemoveTrailingSlash).toLower() == dest.url(KUrl::RemoveTrailingSlash).toLower() &&
1400
(err == ERR_FILE_ALREADY_EXIST ||
1401
err == ERR_DIR_ALREADY_EXIST ||
1402
err == ERR_IDENTICAL_FILES)) {
1403
//kDebug(7007) << "Couldn't rename directly, dest already exists. Detected special case of lower/uppercase renaming in same dir, try with 2 rename calls";
1404
QByteArray _src(QFile::encodeName(m_currentSrcURL.path()));
1405
QByteArray _dest(QFile::encodeName(dest.path()));
1406
KTemporaryFile tmpFile;
1407
tmpFile.setPrefix(m_currentSrcURL.directory(KUrl::ObeyTrailingSlash));
1408
tmpFile.setAutoRemove(false);
1410
QByteArray _tmp(QFile::encodeName(tmpFile.fileName()));
1411
//kDebug(7007) << "PreserveAttrCopyJob::slotResult KTemporaryFile using " << _tmp << " as intermediary";
1412
if (KDE_rename(_src, _tmp) == 0) {
1413
if (!QFile::exists(_dest) && KDE_rename(_tmp, _dest) == 0) {
1414
//kDebug(7007) << "Success.";
1417
// Revert back to original name!
1418
if (KDE_rename(_tmp, _src) != 0) {
1419
kError(7007) << "Couldn't rename " << tmpFile.fileName() << " back to " << _src << " !" << endl;
1420
// Severe error, abort
1421
Job::slotResult(job); // will set the error and emit result(this)
1429
// This code is similar to PreserveAttrCopyJob::slotResultConflictCopyingFiles
1430
// but here it's about the base src url being moved/renamed
1431
// (*m_currentStatSrc) and its dest (m_dest), not about a single file.
1432
// It also means we already stated the dest, here.
1433
// On the other hand we haven't stated the src yet (we skipped doing it
1434
// to save time, since it's not necessary to rename directly!)...
1436
Q_ASSERT(m_currentSrcURL == *m_currentStatSrc);
1439
if ((err == ERR_DIR_ALREADY_EXIST ||
1440
err == ERR_FILE_ALREADY_EXIST ||
1441
err == ERR_IDENTICAL_FILES)
1442
&& isInteractive()) {
1444
m_reportTimer->stop();
1446
// Should we skip automatically ?
1448
// Move on to next file
1451
} else if (m_bOverwriteAll) {
1452
; // nothing to do, stat+copy+del will overwrite
1455
// If src==dest, use "overwrite-itself"
1456
RenameDialog_Mode mode = (RenameDialog_Mode)
1457
((m_currentSrcURL == dest) ? M_OVERWRITE_ITSELF : M_OVERWRITE);
1459
if (m_srcList.count() > 1)
1460
mode = (RenameDialog_Mode)(mode | M_MULTI | M_SKIP);
1462
mode = (RenameDialog_Mode)(mode | M_SINGLE);
1464
// we lack mtime info for both the src (not stated)
1465
// and the dest (stated but this info wasn't stored)
1466
// Let's do it for local files, at least
1467
KIO::filesize_t sizeSrc = (KIO::filesize_t) - 1;
1468
KIO::filesize_t sizeDest = (KIO::filesize_t) - 1;
1469
time_t ctimeSrc = (time_t) - 1;
1470
time_t ctimeDest = (time_t) - 1;
1471
time_t mtimeSrc = (time_t) - 1;
1472
time_t mtimeDest = (time_t) - 1;
1474
KDE_struct_stat stat_buf;
1475
if (m_currentSrcURL.isLocalFile() &&
1476
KDE_stat(QFile::encodeName(m_currentSrcURL.path()), &stat_buf) == 0) {
1477
sizeSrc = stat_buf.st_size;
1478
ctimeSrc = stat_buf.st_ctime;
1479
mtimeSrc = stat_buf.st_mtime;
1481
if (dest.isLocalFile() &&
1482
KDE_stat(QFile::encodeName(dest.path()), &stat_buf) == 0) {
1483
sizeDest = stat_buf.st_size;
1484
ctimeDest = stat_buf.st_ctime;
1485
mtimeDest = stat_buf.st_mtime;
1488
RenameDialog_Result r = ui()->askFileRename(
1490
err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"),
1491
m_currentSrcURL.url(),
1495
ctimeSrc, ctimeDest,
1496
mtimeSrc, mtimeDest);
1498
m_reportTimer->start(REPORT_TIMEOUT);
1502
setError(ERR_USER_CANCELED);
1507
// Set m_dest to the chosen destination
1508
// This is only for this src url; the next one will revert to m_globalDest
1509
m_dest.setPath(newPath);
1510
KIO::Job* job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
1511
state = STATE_STATING;
1512
destinationState = DEST_NOT_STATED;
1520
// Move on to next file
1523
case R_OVERWRITE_ALL:
1524
m_bOverwriteAll = true;
1527
// Add to overwrite list
1528
// Note that we add dest, not m_dest.
1529
// This ensures that when moving several urls into a dir (m_dest),
1530
// we only overwrite for the current one, not for all.
1531
// When renaming a single file (m_asMethod), it makes no difference.
1532
//kDebug(7007) << "adding to overwrite list: " << dest.path();
1533
m_overwriteList.append(dest.path());
1540
} else if (err != KIO::ERR_UNSUPPORTED_ACTION) {
1541
//kDebug(7007) << "Couldn't rename " << m_currentSrcURL << " to " << dest << ", aborting";
1543
setErrorText(errText);
1547
//kDebug(7007) << "Couldn't rename " << m_currentSrcURL << " to " << dest << ", reverting to normal way, starting with stat";
1548
//kDebug(7007) << "KIO::stat on " << m_currentSrcURL;
1549
KIO::Job* job = KIO::stat(m_currentSrcURL, StatJob::SourceSide, 2, KIO::HideProgressInfo);
1550
state = STATE_STATING;
1552
m_bOnlyRenames = false;
1554
//kDebug(7007) << "Renaming succeeded, move on";
1555
slotCopyingDone(*m_currentStatSrc, dest, true);
1556
emit copyingDone(this, *m_currentStatSrc, dest, -1 /*mtime unknown, and not needed*/, true, true);
1561
void PreserveAttrCopyJob::slotResult(KJob *job)
1563
//kDebug(7007) << "PreserveAttrCopyJob::slotResult() state=" << (int) state;
1564
// In each case, what we have to do is :
1565
// 1 - check for errors and treat them
1566
// 2 - removeSubjob(job);
1567
// 3 - decide what to do next
1570
case STATE_STATING: // We were trying to stat a src url or the dest
1571
slotResultStating(job);
1573
case STATE_RENAMING: { // We were trying to do a direct renaming, before even stat'ing
1574
slotResultRenaming(job);
1577
case STATE_LISTING: // recursive listing finished
1578
//kDebug(7007) << "totalSize: " << (unsigned int) m_totalSize << " files: " << files.count() << " dirs: " << dirs.count();
1579
// Was there an error ?
1581
Job::slotResult(job); // will set the error and emit result(this)
1586
assert(!hasSubjobs());
1590
case STATE_CREATING_DIRS:
1591
slotResultCreatingDirs(job);
1593
case STATE_CONFLICT_CREATING_DIRS:
1594
slotResultConflictCreatingDirs(job);
1596
case STATE_COPYING_FILES:
1597
slotResultCopyingFiles(job);
1599
case STATE_CONFLICT_COPYING_FILES:
1600
slotResultConflictCopyingFiles(job);
1602
case STATE_DELETING_DIRS:
1603
slotResultDeletingDirs(job);
1605
case STATE_SETTING_DIR_ATTRIBUTES:
1606
slotResultSettingDirAttributes(job);
1613
void PreserveAttrCopyJob::saveEntries(KIO::Job *job, const KIO::UDSEntryList &list)
1615
KIO::UDSEntryList::const_iterator it = list.begin();
1616
KIO::UDSEntryList::const_iterator end = list.end();
1617
for (; it != end; ++it) {
1618
KUrl url = ((KIO::SimpleJob *)job)->url();
1619
QString relName, user, group;
1620
time_t mtime = (time_t) - 1;
1624
if ((*it).contains(KIO::UDSEntry::UDS_URL))
1625
relName = KUrl((*it).stringValue(KIO::UDSEntry::UDS_URL)).fileName();
1626
else if ((*it).contains(KIO::UDSEntry::UDS_NAME))
1627
relName = (*it).stringValue(KIO::UDSEntry::UDS_NAME);
1629
if ((*it).contains(KIO::UDSEntry::UDS_MODIFICATION_TIME))
1630
mtime = (time_t)((*it).numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME));
1632
if ((*it).contains(KIO::UDSEntry::UDS_USER))
1633
user = (*it).stringValue(KIO::UDSEntry::UDS_USER);
1635
if ((*it).contains(KIO::UDSEntry::UDS_GROUP))
1636
group = (*it).stringValue(KIO::UDSEntry::UDS_GROUP);
1638
if ((*it).contains(KIO::UDSEntry::UDS_ACCESS))
1639
mode = ((*it).numberValue(KIO::UDSEntry::UDS_ACCESS));
1641
#ifdef HAVE_POSIX_ACL
1642
if ((*it).contains(KIO::UDSEntry::UDS_ACL_STRING))
1643
acl = (*it).stringValue(KIO::UDSEntry::UDS_ACL_STRING);
1646
url.addPath(relName);
1648
fileAttributes[ url ] = Attributes(mtime, user, group, mode, acl);
1652
void PreserveAttrCopyJob::slotAboutToCreate(const QList< KIO::CopyInfo > &files)
1654
for (QList< KIO::CopyInfo >::ConstIterator it = files.begin(); it != files.end(); ++it) {
1656
if ((*it).uSource.isLocalFile()) {
1657
KDE_struct_stat stat_p;
1658
KDE_lstat((*it).uSource.path(KUrl::RemoveTrailingSlash).toLocal8Bit(), &stat_p); /* getting the date information */
1661
#ifdef HAVE_POSIX_ACL
1662
acl_t acl = acl_get_file((*it).uSource.path(KUrl::RemoveTrailingSlash).toLocal8Bit(), ACL_TYPE_ACCESS);
1664
bool aclExtended = false;
1666
#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
1667
aclExtended = acl_equiv_mode(acl, 0);
1670
int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
1672
acl_tag_t currentTag;
1673
acl_get_tag_type(entry, ¤tTag);
1674
if (currentTag != ACL_USER_OBJ &&
1675
currentTag != ACL_GROUP_OBJ &&
1676
currentTag != ACL_OTHER) {
1680
ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
1686
if (acl && !aclExtended) {
1691
char *aclString = acl_to_text(acl, 0);
1692
aclStr = QString::fromLatin1(aclString);
1693
acl_free((void*)aclString);
1697
fileAttributes[(*it).uSource ] = Attributes(stat_p.st_mtime, stat_p.st_uid, stat_p.st_gid, stat_p.st_mode & 07777, aclStr);
1699
time_t mtime = (*it).mtime;
1701
if (mtime != 0 && mtime != ((time_t) - 1)) /* is it correct? */
1702
fileAttributes[(*it).uSource ].time = mtime;
1704
int permissions = (*it).permissions;
1705
fileAttributes[(*it).uSource ].mode = permissions;
1710
void PreserveAttrCopyJob::slotCopyingDone(const KUrl &from, const KUrl &to, bool postpone)
1712
if (m_mode == CopyJob::Link)
1715
if (postpone) { // the directories are stamped at the last step, so if it's a directory, we postpone
1717
QString path = to.path(KUrl::RemoveTrailingSlash);
1719
for (; i != directoriesToStamp.count(); i++) // sort the URL-s to avoid parent time stamp modification
1720
if (path >= directoriesToStamp[ i ].path(KUrl::RemoveTrailingSlash))
1723
if (i != directoriesToStamp.count()) {
1724
directoriesToStamp.insert(directoriesToStamp.begin() + i, to);
1725
originalDirectories.insert(originalDirectories.begin() + i, from);
1727
directoriesToStamp.append(to);
1728
originalDirectories.append(from);
1730
} else if (fileAttributes.count(from)) {
1731
Attributes attrs = fileAttributes[ from ];
1732
fileAttributes.remove(from);
1734
time_t mtime = attrs.time;
1736
if (to.isLocalFile()) {
1737
if (mtime != 0 && mtime != ((time_t) - 1)) {
1738
struct utimbuf timestamp;
1740
timestamp.actime = time(0);
1741
timestamp.modtime = mtime;
1743
::utime((const char *)(to.path(KUrl::RemoveTrailingSlash).toLocal8Bit()), ×tamp);
1746
if (attrs.uid != (uid_t) - 1)
1747
::chown((const char *)(to.path(KUrl::RemoveTrailingSlash).toLocal8Bit()), attrs.uid, (gid_t) - 1);
1748
if (attrs.gid != (gid_t) - 1)
1749
::chown((const char *)(to.path(KUrl::RemoveTrailingSlash).toLocal8Bit()), (uid_t) - 1, attrs.gid);
1751
if (attrs.mode != (mode_t) - 1)
1752
::chmod((const char *)(to.path(KUrl::RemoveTrailingSlash).toLocal8Bit()), attrs.mode);
1754
#ifdef HAVE_POSIX_ACL
1755
if (!attrs.acl.isNull()) {
1756
acl_t acl = acl_from_text(attrs.acl.toLatin1());
1757
if (acl && !acl_valid(acl))
1758
acl_set_file(to.path(KUrl::RemoveTrailingSlash).toLocal8Bit(), ACL_TYPE_ACCESS, acl);
1767
void PreserveAttrCopyJob::slotFinished()
1769
for (unsigned i = 0; i != directoriesToStamp.count(); i++) {
1770
KUrl from = originalDirectories[ i ];
1771
KUrl to = directoriesToStamp[ i ];
1773
slotCopyingDone(from, to, false);
1777
void KIO::PreserveAttrCopyJob::setDefaultPermissions(bool b)
1779
m_defaultPermissions = b;
1781
#include "preserveattrcopyjob.moc"