~ubuntu-branches/ubuntu/trusty/krusader/trusty

« back to all changes in this revision

Viewing changes to krusader/VFS/preserveattrcopyjob.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-08-08 13:47:36 UTC
  • mfrom: (1.2.19 upstream)
  • Revision ID: james.westby@ubuntu.com-20110808134736-8e630ivgd2c3sgg5
Tags: 1:2.4.0~beta1-0ubuntu1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
// Krusader modifications:
2
 
//
3
 
// Replace: CopyJob -> PreserveAttrCopyJob
4
 
// Replace: copyjob -> preserveattrcopyjob
5
 
// Compile fixes
6
 
 
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>
11
 
 
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.
16
 
 
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.
21
 
 
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.
26
 
*/
27
 
 
28
 
#include "preserveattrcopyjob.h"
29
 
 
30
 
#if defined(Q_OS_UNIX) || defined(Q_WS_WIN)
31
 
#include <utime.h>
32
 
#endif
33
 
#include <assert.h>
34
 
#include <sys/stat.h> // mode_t
35
 
#include <sys/types.h>
36
 
#include <pwd.h>
37
 
#include <grp.h>
38
 
 
39
 
#include <QtCore/QTimer>
40
 
#include <QtCore/QFile>
41
 
#include <QtCore/QPointer>
42
 
 
43
 
#include <kde_file.h>
44
 
#include <kuiserverjobtracker.h>
45
 
#include <OrgKdeKDirNotifyInterface>
46
 
#include <KLocale>
47
 
#include <KDesktopFile>
48
 
#include <KDebug>
49
 
#include <KDirWatch>
50
 
#include <KProtocolManager>
51
 
#include <KTemporaryFile>
52
 
#include <KIO/Slave>
53
 
#include <KIO/Scheduler>
54
 
#include <KIO/JobUiDelegate>
55
 
#include <KIO/DeleteJob>
56
 
 
57
 
Attributes::Attributes()
58
 
{
59
 
    time = (time_t) - 1;
60
 
    uid = (uid_t) - 1;
61
 
    gid = (gid_t) - 1;
62
 
    mode = (mode_t) - 1;
63
 
    acl.clear();
64
 
}
65
 
 
66
 
Attributes::Attributes(time_t tIn, uid_t uIn, gid_t gIn, mode_t modeIn, const QString & aclIn)
67
 
{
68
 
    time = tIn, uid = uIn, gid = gIn, mode = modeIn, acl = aclIn;
69
 
}
70
 
 
71
 
Attributes::Attributes(time_t tIn, QString user, QString group, mode_t modeIn, const QString & aclIn)
72
 
{
73
 
    time = tIn;
74
 
    uid = (uid_t) - 1;
75
 
    struct passwd* pw = getpwnam(QFile::encodeName(user));
76
 
    if (pw != 0L)
77
 
        uid = pw->pw_uid;
78
 
    gid = (gid_t) - 1;
79
 
    struct group* g = getgrnam(QFile::encodeName(group));
80
 
    if (g != 0L)
81
 
        gid = g->gr_gid;
82
 
    mode = modeIn;
83
 
    acl = aclIn;
84
 
}
85
 
 
86
 
using namespace KIO;
87
 
 
88
 
//this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
89
 
#define REPORT_TIMEOUT 200
90
 
 
91
 
#define KIO_ARGS QByteArray packedArgs; QDataStream stream( &packedArgs, QIODevice::WriteOnly ); stream
92
 
 
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)
98
 
        , m_bURLDirty(false)
99
 
        , m_mode(mode)
100
 
        , m_asMethod(asMethod)
101
 
        , destinationState(DEST_NOT_STATED)
102
 
        , state(STATE_STATING)
103
 
        , m_totalSize(0)
104
 
        , m_processedSize(0)
105
 
        , m_fileProcessedSize(0)
106
 
        , m_processedFiles(0)
107
 
        , m_processedDirs(0)
108
 
        , m_srcList(src)
109
 
        , m_currentStatSrc(m_srcList.begin())
110
 
        , m_bCurrentOperationIsLink(false)
111
 
        , m_bSingleFileCopy(false)
112
 
        , m_bOnlyRenames(mode == CopyJob::Move)
113
 
        , m_dest(dest)
114
 
        , m_bAutoSkip(false)
115
 
        , m_bOverwriteAll(false)
116
 
        , m_conflictError(0)
117
 
        , m_reportTimer(0)
118
 
 
119
 
{
120
 
    QTimer::singleShot(0, this, SLOT(slotStart()));
121
 
}
122
 
 
123
 
PreserveAttrCopyJob::~PreserveAttrCopyJob()
124
 
{
125
 
}
126
 
 
127
 
KUrl::List PreserveAttrCopyJob::srcUrls() const
128
 
{
129
 
    return m_srcList;
130
 
}
131
 
 
132
 
KUrl PreserveAttrCopyJob::destUrl() const
133
 
{
134
 
    return m_dest;
135
 
}
136
 
 
137
 
void PreserveAttrCopyJob::slotStart()
138
 
{
139
 
    /**
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
143
 
    */
144
 
    m_reportTimer = new QTimer(this);
145
 
 
146
 
    connect(m_reportTimer, SIGNAL(timeout()), this, SLOT(slotReport()));
147
 
    m_reportTimer->start(REPORT_TIMEOUT);
148
 
    connect(this, SIGNAL(result(KJob *)), this, SLOT(slotFinished()));
149
 
 
150
 
    // Stat the dest
151
 
    KIO::Job * job = KIO::stat(m_dest, StatJob::DestinationSide, 2, KIO::HideProgressInfo);
152
 
    //kDebug(7007) << "PreserveAttrCopyJob:stating the dest " << m_dest;
153
 
    addSubjob(job);
154
 
}
155
 
 
156
 
// For unit test purposes
157
 
static bool kio_resolve_local_urls = true;
158
 
 
159
 
void PreserveAttrCopyJob::slotResultStating(KJob *job)
160
 
{
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";
170
 
            removeSubjob(job);
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;
178
 
            info.uDest = m_dest;
179
 
            // Append filename or dirname to destination URL, if allowed
180
 
            if (destinationState == DEST_IS_DIR && !m_asMethod)
181
 
                info.uDest.addPath(srcurl.fileName());
182
 
 
183
 
            files.append(info);
184
 
            statNextSrc();
185
 
            return;
186
 
        }
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)
190
 
        return;
191
 
    }
192
 
 
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();
197
 
 
198
 
    if (destinationState == DEST_NOT_STATED)
199
 
        // we were stating the dest
200
 
    {
201
 
        if (job->error())
202
 
            destinationState = DEST_DOESNT_EXIST;
203
 
        else {
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;
207
 
        }
208
 
        const bool isGlobalDest = m_dest == m_globalDest;
209
 
        if (isGlobalDest)
210
 
            m_globalDestinationState = destinationState;
211
 
 
212
 
        if (!sLocalPath.isEmpty() && kio_resolve_local_urls) {
213
 
            m_dest = KUrl();
214
 
            m_dest.setPath(sLocalPath);
215
 
            if (isGlobalDest)
216
 
                m_globalDest = m_dest;
217
 
        }
218
 
 
219
 
        removeSubjob(job);
220
 
        assert(!hasSubjobs());
221
 
 
222
 
        // After knowing what the dest is, we can start stat'ing the first src.
223
 
        statCurrentSrc();
224
 
        return;
225
 
    }
226
 
 
227
 
    // Is it a file or a dir ?
228
 
    const QString sName = entry.stringValue(KIO::UDSEntry::UDS_NAME);
229
 
 
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
233
 
    UDSEntryList lst;
234
 
    lst.append(entry);
235
 
 
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.
242
 
 
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);
250
 
 
251
 
    KUrl srcurl;
252
 
    if (!sLocalPath.isEmpty())
253
 
        srcurl.setPath(sLocalPath);
254
 
    else
255
 
        srcurl = ((SimpleJob*)job)->url();
256
 
 
257
 
    removeSubjob(job);
258
 
    assert(!hasSubjobs());    // We should have only one job at a time ...
259
 
 
260
 
    if (isDir
261
 
            // treat symlinks as files (no recursion)
262
 
            && !entry.isLink()
263
 
            && m_mode != CopyJob::Link) { // No recursion in Link mode either.
264
 
        //kDebug(7007) << " Source is a directory ";
265
 
 
266
 
        m_bCurrentSrcIsDir = true; // used by slotEntries
267
 
        if (destinationState == DEST_IS_DIR) { // (case 1)
268
 
            if (!m_asMethod) {
269
 
                // Use <desturl>/<directory_copied> as destination, from now on
270
 
                QString directory = srcurl.fileName();
271
 
                if (!sName.isEmpty() && KProtocolManager::fileNameUsedForCopying(srcurl) == KProtocolInfo::Name) {
272
 
                    directory = sName;
273
 
                }
274
 
                m_currentDest.addPath(directory);
275
 
            }
276
 
        } else if (destinationState == DEST_IS_FILE) { // (case 2)
277
 
            setError(ERR_IS_FILE);
278
 
            setErrorText(m_dest.prettyUrl());
279
 
            emitResult();
280
 
            return;
281
 
        } else { // (case 3)
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;
289
 
        }
290
 
 
291
 
        startListing(srcurl);
292
 
    } else {
293
 
        //kDebug(7007) << " Source is a file (or a symlink), or we are linking -> no recursive listing ";
294
 
        statNextSrc();
295
 
    }
296
 
}
297
 
 
298
 
bool PreserveAttrCopyJob::doSuspend()
299
 
{
300
 
    slotReport();
301
 
    return Job::doSuspend();
302
 
}
303
 
 
304
 
void PreserveAttrCopyJob::slotReport()
305
 
{
306
 
    if (isSuspended())
307
 
        return;
308
 
    // If showProgressInfo was set, progressId() is > 0.
309
 
    switch (state) {
310
 
    case STATE_COPYING_FILES:
311
 
        setProcessedAmount(KJob::Files, m_processedFiles);
312
 
        if (m_bURLDirty) {
313
 
            // Only emit urls when they changed. This saves time, and fixes #66281
314
 
            m_bURLDirty = false;
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);
325
 
            } else {
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);
330
 
            }
331
 
        }
332
 
        break;
333
 
 
334
 
    case STATE_CREATING_DIRS:
335
 
        setProcessedAmount(KJob::Directories, m_processedDirs);
336
 
        if (m_bURLDirty) {
337
 
            m_bURLDirty = false;
338
 
            emit description(this, i18nc("@title job", "Creating directory"),
339
 
                             qMakePair(i18n("Directory"), m_currentDestURL.prettyUrl()));
340
 
            emit creatingDir(this, m_currentDestURL);
341
 
        }
342
 
        break;
343
 
 
344
 
    case STATE_STATING:
345
 
    case STATE_LISTING:
346
 
        if (m_bURLDirty) {
347
 
            m_bURLDirty = false;
348
 
            emit description(this, i18nc("@title job", "Copying"),
349
 
                             qMakePair(i18n("Source"), m_currentSrcURL.prettyUrl()),
350
 
                             qMakePair(i18n("Destination"), m_currentDestURL.prettyUrl()));
351
 
        }
352
 
        setTotalAmount(KJob::Bytes, m_totalSize);
353
 
        setTotalAmount(KJob::Files, files.count());
354
 
        setTotalAmount(KJob::Directories, dirs.count());
355
 
        break;
356
 
 
357
 
    default:
358
 
        break;
359
 
    }
360
 
}
361
 
 
362
 
void PreserveAttrCopyJob::slotEntries(KIO::Job* job, const UDSEntryList& list)
363
 
{
364
 
    saveEntries(job, list);
365
 
 
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;
377
 
 
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);
381
 
        KUrl url;
382
 
        if (!urlStr.isEmpty())
383
 
            url = urlStr;
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);
387
 
 
388
 
        if (displayName != ".." && displayName != ".") {
389
 
            bool hasCustomURL = !url.isEmpty() || !localPath.isEmpty();
390
 
            if (!hasCustomURL) {
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);
396
 
                }
397
 
            }
398
 
            //kDebug(7007) << "displayName=" << displayName << " url=" << url;
399
 
            if (!localPath.isEmpty() && kio_resolve_local_urls) {
400
 
                url = KUrl();
401
 
                url.setPath(localPath);
402
 
            }
403
 
 
404
 
            info.uSource = url;
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;
413
 
                if (hasCustomURL &&
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();
419
 
                    int pos = 0;
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";
424
 
                            break;
425
 
                        }
426
 
                    }
427
 
                    if (pos >= 0) {
428
 
                        destFileName = path.mid(pos + 1);
429
 
                    }
430
 
 
431
 
                } else { // destination filename taken from UDS_NAME
432
 
                    destFileName = displayName;
433
 
                }
434
 
 
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());
440
 
 
441
 
                //kDebug(7007) << " adding destFileName=" << destFileName;
442
 
                info.uDest.addPath(destFileName);
443
 
            }
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);
450
 
            } else {
451
 
                files.append(info);   // Files and any symlinks
452
 
            }
453
 
        }
454
 
    }
455
 
}
456
 
 
457
 
void PreserveAttrCopyJob::skipSrc()
458
 
{
459
 
    m_dest = m_globalDest;
460
 
    destinationState = m_globalDestinationState;
461
 
    ++m_currentStatSrc;
462
 
    skip(m_currentSrcURL);
463
 
    statCurrentSrc();
464
 
}
465
 
 
466
 
void PreserveAttrCopyJob::statNextSrc()
467
 
{
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.
471
 
     */
472
 
    m_dest = m_globalDest;
473
 
    destinationState = m_globalDestinationState;
474
 
    ++m_currentStatSrc;
475
 
    statCurrentSrc();
476
 
}
477
 
 
478
 
void PreserveAttrCopyJob::statCurrentSrc()
479
 
{
480
 
    if (m_currentStatSrc != m_srcList.end()) {
481
 
        m_currentSrcURL = (*m_currentStatSrc);
482
 
        m_bURLDirty = true;
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) {
495
 
                if (
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());
503
 
                } else {
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");
508
 
                }
509
 
            }
510
 
            files.append(info);   // Files and any symlinks
511
 
            statNextSrc(); // we could use a loop instead of a recursive call :)
512
 
            return;
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)
517
 
                  ) {
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);
526
 
                return;
527
 
            } else if (m_currentSrcURL.isLocalFile() && KProtocolManager::canRenameFromFile(m_dest)) {
528
 
                startRenameJob(m_dest);
529
 
                return;
530
 
            } else if (m_dest.isLocalFile() && KProtocolManager::canRenameToFile(m_currentSrcURL)) {
531
 
                startRenameJob(m_currentSrcURL);
532
 
                return;
533
 
            }
534
 
        }
535
 
 
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()));
540
 
            if (that)
541
 
                statNextSrc(); // we could use a loop instead of a recursive call :)
542
 
            return;
543
 
        }
544
 
 
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;
549
 
        addSubjob(job);
550
 
        m_currentDestURL = m_dest;
551
 
        m_bOnlyRenames = false;
552
 
        m_bURLDirty = true;
553
 
    } else {
554
 
        // Finished the stat'ing phase
555
 
        // First make sure that the totals were correctly emitted
556
 
        state = STATE_STATING;
557
 
        m_bURLDirty = true;
558
 
        slotReport();
559
 
        if (!dirs.isEmpty()) {
560
 
            slotAboutToCreate(dirs);
561
 
            emit aboutToCreate(this, dirs);
562
 
        }
563
 
        if (!files.isEmpty()) {
564
 
            slotAboutToCreate(files);
565
 
            emit aboutToCreate(this, files);
566
 
        }
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;
571
 
        createNextDir();
572
 
    }
573
 
}
574
 
 
575
 
void PreserveAttrCopyJob::startRenameJob(const KUrl& slave_url)
576
 
{
577
 
    KUrl dest = m_dest;
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;
583
 
 
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;
590
 
    info.uDest = dest;
591
 
    QList<CopyInfo> files;
592
 
    files.append(info);
593
 
    slotAboutToCreate(files);
594
 
    emit aboutToCreate(this, files);
595
 
 
596
 
    SimpleJob * newJob = KIO::rename(m_currentSrcURL, dest, 0);
597
 
    newJob->setUiDelegate(new JobUiDelegate());
598
 
    Scheduler::scheduleJob(newJob);
599
 
    addSubjob(newJob);
600
 
    if (m_currentSrcURL.directory() != dest.directory())   // For the user, moving isn't renaming. Only renaming is.
601
 
        m_bOnlyRenames = false;
602
 
}
603
 
 
604
 
void PreserveAttrCopyJob::startListing(const KUrl & src)
605
 
{
606
 
    state = STATE_LISTING;
607
 
    m_bURLDirty = true;
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&)));
613
 
    addSubjob(newjob);
614
 
}
615
 
 
616
 
void PreserveAttrCopyJob::skip(const KUrl & sourceUrl)
617
 
{
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);
623
 
}
624
 
 
625
 
bool PreserveAttrCopyJob::shouldOverwrite(const QString& path) const
626
 
{
627
 
    if (m_bOverwriteAll)
628
 
        return true;
629
 
    QStringList::ConstIterator sit = m_overwriteList.begin();
630
 
    for (; sit != m_overwriteList.end(); ++sit)
631
 
        if (path.startsWith(*sit))
632
 
            return true;
633
 
    return false;
634
 
}
635
 
 
636
 
bool PreserveAttrCopyJob::shouldSkip(const QString& path) const
637
 
{
638
 
    QStringList::ConstIterator sit = m_skipList.begin();
639
 
    for (; sit != m_skipList.end(); ++sit)
640
 
        if (path.startsWith(*sit))
641
 
            return true;
642
 
    return false;
643
 
}
644
 
 
645
 
void PreserveAttrCopyJob::slotResultCreatingDirs(KJob * job)
646
 
{
647
 
    // The dir we are trying to create:
648
 
    QList<CopyInfo>::Iterator it = dirs.begin();
649
 
    // Was there an error creating a dir ?
650
 
    if (job->error()) {
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 ?
656
 
            if (m_bAutoSkip) {
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));
659
 
                skip(oldURL);
660
 
                dirs.erase(it);   // Move on to next dir
661
 
            } else {
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
668
 
                } else {
669
 
                    if (!isInteractive()) {
670
 
                        Job::slotResult(job);   // will set the error and emit result(this)
671
 
                        return;
672
 
                    }
673
 
 
674
 
                    assert(((SimpleJob*)job)->url().url() == (*it).uDest.url());
675
 
                    removeSubjob(job);
676
 
                    assert(!hasSubjobs());    // We should have only one job at a time ...
677
 
 
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;
684
 
                    addSubjob(newJob);
685
 
                    return; // Don't move to next dir yet !
686
 
                }
687
 
            }
688
 
        } else {
689
 
            // Severe error, abort
690
 
            Job::slotResult(job);   // will set the error and emit result(this)
691
 
            return;
692
 
        }
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);
698
 
        dirs.erase(it);
699
 
    }
700
 
 
701
 
    m_processedDirs++;
702
 
    //emit processedAmount( this, KJob::Directories, m_processedDirs );
703
 
    removeSubjob(job);
704
 
    assert(!hasSubjobs());   // We should have only one job at a time ...
705
 
    createNextDir();
706
 
}
707
 
 
708
 
void PreserveAttrCopyJob::slotResultConflictCreatingDirs(KJob * job)
709
 
{
710
 
    // We come here after a conflict has been detected and we've stated the existing dir
711
 
 
712
 
    // The dir we were trying to create:
713
 
    QList<CopyInfo>::Iterator it = dirs.begin();
714
 
 
715
 
    const UDSEntry entry = ((KIO::StatJob*)job)->statResult();
716
 
 
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);
720
 
 
721
 
    const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE);
722
 
    const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
723
 
 
724
 
    removeSubjob(job);
725
 
    assert(!hasSubjobs());    // We should have only one job at a time ...
726
 
 
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);
735
 
        else
736
 
            mode = (RenameDialog_Mode)(mode | M_OVERWRITE);
737
 
    }
738
 
 
739
 
    QString existingDest = (*it).uDest.path();
740
 
    QString newPath;
741
 
    if (m_reportTimer)
742
 
        m_reportTimer->stop();
743
 
    RenameDialog_Result r = ui()->askFileRename(this, i18n("Directory Already Exists"),
744
 
                            (*it).uSource.url(),
745
 
                            (*it).uDest.url(),
746
 
                            mode, newPath,
747
 
                            (*it).size, destsize,
748
 
                            (*it).ctime, destctime,
749
 
                            (*it).mtime, destmtime);
750
 
    if (m_reportTimer)
751
 
        m_reportTimer->start(REPORT_TIMEOUT);
752
 
    switch (r) {
753
 
    case R_CANCEL:
754
 
        setError(ERR_USER_CANCELED);
755
 
        emitResult();
756
 
        return;
757
 
    case R_RENAME: {
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
762
 
 
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;
767
 
        ++renamedirit;
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)) {
772
 
                QString n = path;
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);
776
 
            }
777
 
        }
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)) {
783
 
                QString n = path;
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);
787
 
            }
788
 
        }
789
 
        if (!dirs.isEmpty()) {
790
 
            slotAboutToCreate(dirs);
791
 
            emit aboutToCreate(this, dirs);
792
 
        }
793
 
        if (!files.isEmpty()) {
794
 
            slotAboutToCreate(files);
795
 
            emit aboutToCreate(this, files);
796
 
        }
797
 
    }
798
 
    break;
799
 
    case R_AUTO_SKIP:
800
 
        m_bAutoSkip = true;
801
 
        // fall through
802
 
    case R_SKIP:
803
 
        m_skipList.append(existingDest);
804
 
        skip((*it).uSource);
805
 
        // Move on to next dir
806
 
        dirs.erase(it);
807
 
        m_processedDirs++;
808
 
        break;
809
 
    case R_OVERWRITE:
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
814
 
        dirs.erase(it);
815
 
        m_processedDirs++;
816
 
        break;
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
822
 
        dirs.erase(it);
823
 
        m_processedDirs++;
824
 
        break;
825
 
    default:
826
 
        assert(0);
827
 
    }
828
 
    state = STATE_CREATING_DIRS;
829
 
    //emit processedAmount( this, KJob::Directories, m_processedDirs );
830
 
    createNextDir();
831
 
}
832
 
 
833
 
void PreserveAttrCopyJob::createNextDir()
834
 
{
835
 
    KUrl udir;
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)) {
843
 
                dirs.erase(it);
844
 
                it = dirs.begin();
845
 
            } else
846
 
                udir = (*it).uDest;
847
 
        }
848
 
    }
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);
854
 
 
855
 
        m_currentDestURL = udir;
856
 
        m_bURLDirty = true;
857
 
 
858
 
        addSubjob(newjob);
859
 
        return;
860
 
    } else { // we have finished creating dirs
861
 
        setProcessedAmount(KJob::Directories, m_processedDirs);   // make sure final number appears
862
 
 
863
 
        state = STATE_COPYING_FILES;
864
 
        m_processedFiles++; // Ralf wants it to start at 1, not 0
865
 
        copyNextFile();
866
 
    }
867
 
}
868
 
 
869
 
void PreserveAttrCopyJob::slotResultCopyingFiles(KJob * job)
870
 
{
871
 
    // The file we were trying to copy:
872
 
    QList<CopyInfo>::Iterator it = files.begin();
873
 
    if (job->error()) {
874
 
        // Should we skip automatically ?
875
 
        if (m_bAutoSkip) {
876
 
            skip((*it).uSource);
877
 
            m_fileProcessedSize = (*it).size;
878
 
            files.erase(it);   // Move on to next file
879
 
        } else {
880
 
            if (!isInteractive()) {
881
 
                Job::slotResult(job);   // will set the error and emit result(this)
882
 
                return;
883
 
            }
884
 
 
885
 
            m_conflictError = job->error(); // save for later
886
 
            // Existing dest ?
887
 
            if ((m_conflictError == ERR_FILE_ALREADY_EXIST)
888
 
                    || (m_conflictError == ERR_DIR_ALREADY_EXIST)
889
 
                    || (m_conflictError == ERR_IDENTICAL_FILES)) {
890
 
                removeSubjob(job);
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;
898
 
                addSubjob(newJob);
899
 
                return; // Don't move to next file yet !
900
 
            } else {
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;
905
 
                    files.erase(it);
906
 
                } else {
907
 
                    // Go directly to the conflict resolution, there is nothing to stat
908
 
                    slotResultConflictCopyingFiles(job);
909
 
                    return;
910
 
                }
911
 
            }
912
 
        }
913
 
    } else { // no error
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
917
 
           ) {
918
 
            removeSubjob(job);
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);
923
 
            addSubjob(newjob);
924
 
            return; // Don't move to next file yet !
925
 
        }
926
 
 
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);
931
 
        } else {
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);
935
 
        }
936
 
        // remove from list, to move on to next file
937
 
        files.erase(it);
938
 
    }
939
 
    m_processedFiles++;
940
 
 
941
 
    // clear processed size for last file and add it to overall processed size
942
 
    m_processedSize += m_fileProcessedSize;
943
 
    m_fileProcessedSize = 0;
944
 
 
945
 
    //kDebug(7007) << files.count() << " files remaining";
946
 
 
947
 
    // Merge metadata from subjob
948
 
    KIO::Job* kiojob = dynamic_cast<KIO::Job*>(job);
949
 
    Q_ASSERT(kiojob);
950
 
    //m_incomingMetaData += kiojob->metaData();
951
 
    removeSubjob(job);
952
 
    assert(!hasSubjobs());   // We should have only one job at a time ...
953
 
    copyNextFile();
954
 
}
955
 
 
956
 
void PreserveAttrCopyJob::slotResultConflictCopyingFiles(KJob * job)
957
 
{
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();
961
 
 
962
 
    RenameDialog_Result res;
963
 
    QString newPath;
964
 
 
965
 
    if (m_reportTimer)
966
 
        m_reportTimer->stop();
967
 
 
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();
973
 
 
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);
978
 
 
979
 
        // Offer overwrite only if the existing thing is a file
980
 
        // If src==dest, use "overwrite-itself"
981
 
        RenameDialog_Mode mode;
982
 
        bool isDir = true;
983
 
 
984
 
        if (m_conflictError == ERR_DIR_ALREADY_EXIST)
985
 
            mode = (RenameDialog_Mode) 0;
986
 
        else {
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;
991
 
            else
992
 
                mode = M_OVERWRITE;
993
 
            isDir = false;
994
 
        }
995
 
 
996
 
        if (m_bSingleFileCopy)
997
 
            mode = (RenameDialog_Mode)(mode | M_SINGLE);
998
 
        else
999
 
            mode = (RenameDialog_Mode)(mode | M_MULTI | M_SKIP);
1000
 
 
1001
 
        res = ui()->askFileRename(this, !isDir ?
1002
 
                                  i18n("File Already Exists") : i18n("Already Exists as Directory"),
1003
 
                                  (*it).uSource.url(),
1004
 
                                  (*it).uDest.url(),
1005
 
                                  mode, newPath,
1006
 
                                  (*it).size, destsize,
1007
 
                                  (*it).ctime, destctime,
1008
 
                                  (*it).mtime, destmtime);
1009
 
 
1010
 
    } else {
1011
 
        if (job->error() == ERR_USER_CANCELED)
1012
 
            res = R_CANCEL;
1013
 
        else if (!isInteractive()) {
1014
 
            Job::slotResult(job);   // will set the error and emit result(this)
1015
 
            return;
1016
 
        } else {
1017
 
            SkipDialog_Result skipResult = ui()->askSkip(this, files.count() > 1,
1018
 
                                           job->errorString());
1019
 
 
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 :
1023
 
                  R_CANCEL;
1024
 
        }
1025
 
    }
1026
 
 
1027
 
    if (m_reportTimer)
1028
 
        m_reportTimer->start(REPORT_TIMEOUT);
1029
 
 
1030
 
    removeSubjob(job);
1031
 
    assert(!hasSubjobs());
1032
 
    switch (res) {
1033
 
    case R_CANCEL:
1034
 
        setError(ERR_USER_CANCELED);
1035
 
        emitResult();
1036
 
        return;
1037
 
    case R_RENAME: {
1038
 
        KUrl newUrl((*it).uDest);
1039
 
        newUrl.setPath(newPath);
1040
 
        emit renamed(this, (*it).uDest, newUrl);   // for e.g. kpropsdlg
1041
 
        (*it).uDest = newUrl;
1042
 
 
1043
 
        QList<CopyInfo> files;
1044
 
        files.append(*it);
1045
 
        slotAboutToCreate(files);
1046
 
        emit aboutToCreate(this, files);
1047
 
    }
1048
 
    break;
1049
 
    case R_AUTO_SKIP:
1050
 
        m_bAutoSkip = true;
1051
 
        // fall through
1052
 
    case R_SKIP:
1053
 
        // Move on to next file
1054
 
        skip((*it).uSource);
1055
 
        m_processedSize += (*it).size;
1056
 
        files.erase(it);
1057
 
        m_processedFiles++;
1058
 
        break;
1059
 
    case R_OVERWRITE_ALL:
1060
 
        m_bOverwriteAll = true;
1061
 
        break;
1062
 
    case R_OVERWRITE:
1063
 
        // Add to overwrite list, so that copyNextFile knows to overwrite
1064
 
        m_overwriteList.append((*it).uDest.path());
1065
 
        break;
1066
 
    default:
1067
 
        assert(0);
1068
 
    }
1069
 
    state = STATE_COPYING_FILES;
1070
 
    //emit processedAmount( this, KJob::Files, m_processedFiles );
1071
 
    copyNextFile();
1072
 
}
1073
 
 
1074
 
KIO::Job* PreserveAttrCopyJob::linkNextFile(const KUrl& uSource, const KUrl& uDest, JobFlags flags)
1075
 
{
1076
 
    //kDebug(7007) << "Linking";
1077
 
    if (
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;
1091
 
        m_bURLDirty = true;
1092
 
        //Observer::self()->slotCopying( this, uSource, uDest ); // should be slotLinking perhaps
1093
 
        return newJob;
1094
 
    } else {
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
1098
 
 
1099
 
            QString path = uDest.path();
1100
 
            //kDebug(7007) << "PreserveAttrCopyJob::copyNextFile path=" << path;
1101
 
            QFile f(path);
1102
 
            if (f.open(QIODevice::ReadWrite)) {
1103
 
                f.close();
1104
 
                KDesktopFile desktopFile(path);
1105
 
                KConfigGroup config = desktopFile.desktopGroup();
1106
 
                KUrl url = uSource;
1107
 
                url.setPass("");
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
1120
 
                else
1121
 
                    config.writeEntry("Icon", QString::fromLatin1("unknown"));
1122
 
                config.sync();
1123
 
                files.erase(files.begin());   // done with this one, move on
1124
 
                m_processedFiles++;
1125
 
                //emit processedAmount( this, KJob::Files, m_processedFiles );
1126
 
                copyNextFile();
1127
 
                return 0;
1128
 
            } else {
1129
 
                //kDebug(7007) << "PreserveAttrCopyJob::copyNextFile ERR_CANNOT_OPEN_FOR_WRITING";
1130
 
                setError(ERR_CANNOT_OPEN_FOR_WRITING);
1131
 
                setErrorText(uDest.path());
1132
 
                emitResult();
1133
 
                return 0;
1134
 
            }
1135
 
        } else {
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());
1139
 
            emitResult();
1140
 
            return 0;
1141
 
        }
1142
 
    }
1143
 
}
1144
 
 
1145
 
void PreserveAttrCopyJob::copyNextFile()
1146
 
{
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);
1155
 
        if (!bCopyFile) {
1156
 
            files.erase(it);
1157
 
            it = files.begin();
1158
 
        }
1159
 
    }
1160
 
 
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 ?
1165
 
        bool bOverwrite;
1166
 
        const QString destFile = uDest.path();
1167
 
        //kDebug(7007) << "copying " << destFile;
1168
 
        if (uDest == uSource)
1169
 
            bOverwrite = false;
1170
 
        else
1171
 
            bOverwrite = shouldOverwrite(destFile);
1172
 
 
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);
1179
 
            if (!newjob)
1180
 
                return;
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),
1188
 
        {
1189
 
            JobFlags flags = bOverwrite ? Overwrite : DefaultFlags;
1190
 
            KIO::SimpleJob *newJob = KIO::symlink((*it).linkDest, uDest, flags | HideProgressInfo /*no GUI*/);
1191
 
            Scheduler::scheduleJob(newJob);
1192
 
            newjob = newJob;
1193
 
            //kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Linking target=" << (*it).linkDest << " link=" << uDest;
1194
 
            m_currentSrcURL = KUrl((*it).linkDest);
1195
 
            m_currentDestURL = uDest;
1196
 
            m_bURLDirty = true;
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);
1205
 
            newjob = moveJob;
1206
 
            //kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Moving " << uSource << " to " << uDest;
1207
 
            //emit moving( this, uSource, uDest );
1208
 
            m_currentSrcURL = uSource;
1209
 
            m_currentDestURL = uDest;
1210
 
            m_bURLDirty = true;
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()))
1218
 
                permissions = -1;
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);
1226
 
            }
1227
 
            newjob = copyJob;
1228
 
            //kDebug(7007) << "PreserveAttrCopyJob::copyNextFile : Copying " << uSource << " to " << uDest;
1229
 
            m_currentSrcURL = uSource;
1230
 
            m_currentDestURL = uDest;
1231
 
            m_bURLDirty = true;
1232
 
        }
1233
 
        addSubjob(newjob);
1234
 
        connect(newjob, SIGNAL(processedSize(KJob*, qulonglong)),
1235
 
                SLOT(slotProcessedSize(KJob*, qulonglong)));
1236
 
        connect(newjob, SIGNAL(totalSize(KJob*, qulonglong)),
1237
 
                SLOT(slotTotalSize(KJob*, qulonglong)));
1238
 
    } else {
1239
 
        // We're done
1240
 
        //kDebug(7007) << "copyNextFile finished";
1241
 
        deleteNextDir();
1242
 
    }
1243
 
}
1244
 
 
1245
 
void PreserveAttrCopyJob::deleteNextDir()
1246
 
{
1247
 
    if (m_mode == CopyJob::Move && !dirsToRemove.isEmpty()) { // some dirs to delete ?
1248
 
        state = STATE_DELETING_DIRS;
1249
 
        m_bURLDirty = true;
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);
1255
 
        addSubjob(job);
1256
 
    } else {
1257
 
        // This step is done, move on
1258
 
        state = STATE_SETTING_DIR_ATTRIBUTES;
1259
 
        m_directoriesCopiedIterator = m_directoriesCopied.begin();
1260
 
        setNextDirAttribute();
1261
 
    }
1262
 
}
1263
 
 
1264
 
void PreserveAttrCopyJob::setNextDirAttribute()
1265
 
{
1266
 
    while (m_directoriesCopiedIterator != m_directoriesCopied.end() &&
1267
 
            (*m_directoriesCopiedIterator).mtime == -1) {
1268
 
        ++m_directoriesCopiedIterator;
1269
 
    }
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;
1275
 
 
1276
 
        KIO::SimpleJob *job = KIO::setModificationTime(url, dt);
1277
 
        Scheduler::scheduleJob(job);
1278
 
        addSubjob(job);
1279
 
 
1280
 
 
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.
1284
 
        //
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);
1296
 
                }
1297
 
 
1298
 
            }
1299
 
        }
1300
 
        m_directoriesCopied.clear();
1301
 
        // but then we need to jump to the else part below. Maybe with a recursive call?
1302
 
#endif
1303
 
    } else {
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());
1311
 
 
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());
1315
 
            }
1316
 
        }
1317
 
        if (m_reportTimer)
1318
 
            m_reportTimer->stop();
1319
 
        --m_processedFiles; // undo the "start at 1" hack
1320
 
        slotReport(); // display final numbers, important if progress dialog stays up
1321
 
 
1322
 
        emitResult();
1323
 
    }
1324
 
}
1325
 
 
1326
 
void PreserveAttrCopyJob::slotProcessedSize(KJob*, qulonglong data_size)
1327
 
{
1328
 
    //kDebug(7007) << "PreserveAttrCopyJob::slotProcessedSize " << data_size;
1329
 
    m_fileProcessedSize = data_size;
1330
 
    setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
1331
 
 
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
1336
 
    }
1337
 
    //kDebug(7007) << "emit processedSize " << (unsigned long) (m_processedSize + m_fileProcessedSize);
1338
 
    setProcessedAmount(KJob::Bytes, m_processedSize + m_fileProcessedSize);
1339
 
}
1340
 
 
1341
 
void PreserveAttrCopyJob::slotTotalSize(KJob*, qulonglong size)
1342
 
{
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;
1350
 
        m_totalSize = size;
1351
 
        setTotalAmount(KJob::Bytes, size);
1352
 
    }
1353
 
}
1354
 
 
1355
 
void PreserveAttrCopyJob::slotResultDeletingDirs(KJob * job)
1356
 
{
1357
 
    if (job->error()) {
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 !
1361
 
    }
1362
 
    removeSubjob(job);
1363
 
    assert(!hasSubjobs());
1364
 
    deleteNextDir();
1365
 
}
1366
 
 
1367
 
void PreserveAttrCopyJob::slotResultSettingDirAttributes(KJob * job)
1368
 
{
1369
 
    if (job->error()) {
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.
1373
 
    }
1374
 
    removeSubjob(job);
1375
 
    assert(!hasSubjobs());
1376
 
    setNextDirAttribute();
1377
 
}
1378
 
 
1379
 
// We were trying to do a direct renaming, before even stat'ing
1380
 
void PreserveAttrCopyJob::slotResultRenaming(KJob* job)
1381
 
{
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);
1386
 
    Q_ASSERT(kiojob);
1387
 
    //m_incomingMetaData += kiojob->metaData();
1388
 
    removeSubjob(job);
1389
 
    assert(!hasSubjobs());
1390
 
    // Determine dest again
1391
 
    KUrl dest = m_dest;
1392
 
    if (destinationState == DEST_IS_DIR && !m_asMethod)
1393
 
        dest.addPath(m_currentSrcURL.fileName());
1394
 
    if (err) {
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);
1409
 
            tmpFile.open();
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.";
1415
 
                    err = 0;
1416
 
                } else {
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)
1422
 
                        return;
1423
 
                    }
1424
 
                }
1425
 
            }
1426
 
        }
1427
 
    }
1428
 
    if (err) {
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!)...
1435
 
 
1436
 
        Q_ASSERT(m_currentSrcURL == *m_currentStatSrc);
1437
 
 
1438
 
        // Existing dest?
1439
 
        if ((err == ERR_DIR_ALREADY_EXIST ||
1440
 
                err == ERR_FILE_ALREADY_EXIST ||
1441
 
                err == ERR_IDENTICAL_FILES)
1442
 
                && isInteractive()) {
1443
 
            if (m_reportTimer)
1444
 
                m_reportTimer->stop();
1445
 
 
1446
 
            // Should we skip automatically ?
1447
 
            if (m_bAutoSkip) {
1448
 
                // Move on to next file
1449
 
                skipSrc();
1450
 
                return;
1451
 
            } else if (m_bOverwriteAll) {
1452
 
                ; // nothing to do, stat+copy+del will overwrite
1453
 
            } else {
1454
 
                QString newPath;
1455
 
                // If src==dest, use "overwrite-itself"
1456
 
                RenameDialog_Mode mode = (RenameDialog_Mode)
1457
 
                                         ((m_currentSrcURL == dest) ? M_OVERWRITE_ITSELF : M_OVERWRITE);
1458
 
 
1459
 
                if (m_srcList.count() > 1)
1460
 
                    mode = (RenameDialog_Mode)(mode | M_MULTI | M_SKIP);
1461
 
                else
1462
 
                    mode = (RenameDialog_Mode)(mode | M_SINGLE);
1463
 
 
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;
1473
 
 
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;
1480
 
                }
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;
1486
 
                }
1487
 
 
1488
 
                RenameDialog_Result r = ui()->askFileRename(
1489
 
                                            this,
1490
 
                                            err != ERR_DIR_ALREADY_EXIST ? i18n("File Already Exists") : i18n("Already Exists as Folder"),
1491
 
                                            m_currentSrcURL.url(),
1492
 
                                            dest.url(),
1493
 
                                            mode, newPath,
1494
 
                                            sizeSrc, sizeDest,
1495
 
                                            ctimeSrc, ctimeDest,
1496
 
                                            mtimeSrc, mtimeDest);
1497
 
                if (m_reportTimer)
1498
 
                    m_reportTimer->start(REPORT_TIMEOUT);
1499
 
 
1500
 
                switch (r) {
1501
 
                case R_CANCEL: {
1502
 
                    setError(ERR_USER_CANCELED);
1503
 
                    emitResult();
1504
 
                    return;
1505
 
                }
1506
 
                case R_RENAME: {
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;
1513
 
                    addSubjob(job);
1514
 
                    return;
1515
 
                }
1516
 
                case R_AUTO_SKIP:
1517
 
                    m_bAutoSkip = true;
1518
 
                    // fall through
1519
 
                case R_SKIP:
1520
 
                    // Move on to next file
1521
 
                    skipSrc();
1522
 
                    return;
1523
 
                case R_OVERWRITE_ALL:
1524
 
                    m_bOverwriteAll = true;
1525
 
                    break;
1526
 
                case R_OVERWRITE:
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());
1534
 
                    break;
1535
 
                default:
1536
 
                    //assert( 0 );
1537
 
                    break;
1538
 
                }
1539
 
            }
1540
 
        } else if (err != KIO::ERR_UNSUPPORTED_ACTION) {
1541
 
            //kDebug(7007) << "Couldn't rename " << m_currentSrcURL << " to " << dest << ", aborting";
1542
 
            setError(err);
1543
 
            setErrorText(errText);
1544
 
            emitResult();
1545
 
            return;
1546
 
        }
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;
1551
 
        addSubjob(job);
1552
 
        m_bOnlyRenames = false;
1553
 
    } else {
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);
1557
 
        statNextSrc();
1558
 
    }
1559
 
}
1560
 
 
1561
 
void PreserveAttrCopyJob::slotResult(KJob *job)
1562
 
{
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
1568
 
 
1569
 
    switch (state) {
1570
 
    case STATE_STATING: // We were trying to stat a src url or the dest
1571
 
        slotResultStating(job);
1572
 
        break;
1573
 
    case STATE_RENAMING: { // We were trying to do a direct renaming, before even stat'ing
1574
 
        slotResultRenaming(job);
1575
 
        break;
1576
 
    }
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 ?
1580
 
        if (job->error()) {
1581
 
            Job::slotResult(job);   // will set the error and emit result(this)
1582
 
            return;
1583
 
        }
1584
 
 
1585
 
        removeSubjob(job);
1586
 
        assert(!hasSubjobs());
1587
 
 
1588
 
        statNextSrc();
1589
 
        break;
1590
 
    case STATE_CREATING_DIRS:
1591
 
        slotResultCreatingDirs(job);
1592
 
        break;
1593
 
    case STATE_CONFLICT_CREATING_DIRS:
1594
 
        slotResultConflictCreatingDirs(job);
1595
 
        break;
1596
 
    case STATE_COPYING_FILES:
1597
 
        slotResultCopyingFiles(job);
1598
 
        break;
1599
 
    case STATE_CONFLICT_COPYING_FILES:
1600
 
        slotResultConflictCopyingFiles(job);
1601
 
        break;
1602
 
    case STATE_DELETING_DIRS:
1603
 
        slotResultDeletingDirs(job);
1604
 
        break;
1605
 
    case STATE_SETTING_DIR_ATTRIBUTES:
1606
 
        slotResultSettingDirAttributes(job);
1607
 
        break;
1608
 
    default:
1609
 
        assert(0);
1610
 
    }
1611
 
}
1612
 
 
1613
 
void PreserveAttrCopyJob::saveEntries(KIO::Job *job, const KIO::UDSEntryList &list)
1614
 
{
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;
1621
 
        mode_t mode = 0755;
1622
 
        QString acl;
1623
 
 
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);
1628
 
 
1629
 
        if ((*it).contains(KIO::UDSEntry::UDS_MODIFICATION_TIME))
1630
 
            mtime = (time_t)((*it).numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME));
1631
 
 
1632
 
        if ((*it).contains(KIO::UDSEntry::UDS_USER))
1633
 
            user = (*it).stringValue(KIO::UDSEntry::UDS_USER);
1634
 
 
1635
 
        if ((*it).contains(KIO::UDSEntry::UDS_GROUP))
1636
 
            group = (*it).stringValue(KIO::UDSEntry::UDS_GROUP);
1637
 
 
1638
 
        if ((*it).contains(KIO::UDSEntry::UDS_ACCESS))
1639
 
            mode = ((*it).numberValue(KIO::UDSEntry::UDS_ACCESS));
1640
 
 
1641
 
#ifdef HAVE_POSIX_ACL
1642
 
        if ((*it).contains(KIO::UDSEntry::UDS_ACL_STRING))
1643
 
            acl = (*it).stringValue(KIO::UDSEntry::UDS_ACL_STRING);
1644
 
#endif
1645
 
 
1646
 
        url.addPath(relName);
1647
 
 
1648
 
        fileAttributes[ url ] = Attributes(mtime, user, group, mode, acl);
1649
 
    }
1650
 
}
1651
 
 
1652
 
void PreserveAttrCopyJob::slotAboutToCreate(const QList< KIO::CopyInfo > &files)
1653
 
{
1654
 
    for (QList< KIO::CopyInfo >::ConstIterator it = files.begin(); it != files.end(); ++it) {
1655
 
 
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 */
1659
 
 
1660
 
            QString aclStr;
1661
 
#ifdef HAVE_POSIX_ACL
1662
 
            acl_t acl = acl_get_file((*it).uSource.path(KUrl::RemoveTrailingSlash).toLocal8Bit(), ACL_TYPE_ACCESS);
1663
 
 
1664
 
            bool aclExtended = false;
1665
 
            if (acl) {
1666
 
#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
1667
 
                aclExtended = acl_equiv_mode(acl, 0);
1668
 
#else
1669
 
                acl_entry_t entry;
1670
 
                int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
1671
 
                while (ret == 1) {
1672
 
                    acl_tag_t currentTag;
1673
 
                    acl_get_tag_type(entry, &currentTag);
1674
 
                    if (currentTag != ACL_USER_OBJ &&
1675
 
                            currentTag != ACL_GROUP_OBJ &&
1676
 
                            currentTag != ACL_OTHER) {
1677
 
                        aclExtended = true;
1678
 
                        break;
1679
 
                    }
1680
 
                    ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
1681
 
                }
1682
 
#endif
1683
 
            }
1684
 
 
1685
 
 
1686
 
            if (acl && !aclExtended) {
1687
 
                acl_free(acl);
1688
 
                acl = NULL;
1689
 
            }
1690
 
            if (acl) {
1691
 
                char *aclString = acl_to_text(acl, 0);
1692
 
                aclStr = QString::fromLatin1(aclString);
1693
 
                acl_free((void*)aclString);
1694
 
                acl_free(acl);
1695
 
            }
1696
 
#endif
1697
 
            fileAttributes[(*it).uSource ] = Attributes(stat_p.st_mtime, stat_p.st_uid, stat_p.st_gid, stat_p.st_mode & 07777, aclStr);
1698
 
        } else {
1699
 
            time_t mtime = (*it).mtime;
1700
 
 
1701
 
            if (mtime != 0 && mtime != ((time_t) - 1))        /* is it correct? */
1702
 
                fileAttributes[(*it).uSource ].time = mtime;
1703
 
 
1704
 
            int permissions = (*it).permissions;
1705
 
            fileAttributes[(*it).uSource ].mode = permissions;
1706
 
        }
1707
 
    }
1708
 
}
1709
 
 
1710
 
void PreserveAttrCopyJob::slotCopyingDone(const KUrl &from, const KUrl &to, bool postpone)
1711
 
{
1712
 
    if (m_mode == CopyJob::Link)
1713
 
        return;
1714
 
 
1715
 
    if (postpone) {  // the directories are stamped at the last step, so if it's a directory, we postpone
1716
 
        int i = 0;
1717
 
        QString path = to.path(KUrl::RemoveTrailingSlash);
1718
 
 
1719
 
        for (; i != directoriesToStamp.count(); i++)  // sort the URL-s to avoid parent time stamp modification
1720
 
            if (path >= directoriesToStamp[ i ].path(KUrl::RemoveTrailingSlash))
1721
 
                break;
1722
 
 
1723
 
        if (i != directoriesToStamp.count()) {
1724
 
            directoriesToStamp.insert(directoriesToStamp.begin() + i, to);
1725
 
            originalDirectories.insert(originalDirectories.begin() + i, from);
1726
 
        } else {
1727
 
            directoriesToStamp.append(to);
1728
 
            originalDirectories.append(from);
1729
 
        }
1730
 
    } else if (fileAttributes.count(from)) {
1731
 
        Attributes attrs = fileAttributes[ from ];
1732
 
        fileAttributes.remove(from);
1733
 
 
1734
 
        time_t mtime = attrs.time;
1735
 
 
1736
 
        if (to.isLocalFile()) {
1737
 
            if (mtime != 0 && mtime != ((time_t) - 1)) {
1738
 
                struct utimbuf timestamp;
1739
 
 
1740
 
                timestamp.actime  = time(0);
1741
 
                timestamp.modtime = mtime;
1742
 
 
1743
 
                ::utime((const char *)(to.path(KUrl::RemoveTrailingSlash).toLocal8Bit()), &timestamp);
1744
 
            }
1745
 
 
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);
1750
 
 
1751
 
            if (attrs.mode != (mode_t) - 1)
1752
 
                ::chmod((const char *)(to.path(KUrl::RemoveTrailingSlash).toLocal8Bit()), attrs.mode);
1753
 
 
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);
1759
 
                if (acl)
1760
 
                    acl_free(acl);
1761
 
            }
1762
 
#endif
1763
 
        }
1764
 
    }
1765
 
}
1766
 
 
1767
 
void PreserveAttrCopyJob::slotFinished()
1768
 
{
1769
 
    for (unsigned i = 0; i != directoriesToStamp.count(); i++) {
1770
 
        KUrl from = originalDirectories[ i ];
1771
 
        KUrl to = directoriesToStamp[ i ];
1772
 
 
1773
 
        slotCopyingDone(from, to, false);
1774
 
    }
1775
 
}
1776
 
 
1777
 
void KIO::PreserveAttrCopyJob::setDefaultPermissions(bool b)
1778
 
{
1779
 
    m_defaultPermissions = b;
1780
 
}
1781
 
#include "preserveattrcopyjob.moc"