mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 19:02:48 +00:00
857 lines
23 KiB
C++
857 lines
23 KiB
C++
/* This file is part of the KDE libraries
|
|
Copyright (C) 2000-2005 David Faure <faure@kde.org>
|
|
Copyright (C) 2003 Leo Savernik <l.savernik@aon.at>
|
|
|
|
Moved from ktar.cpp by Roberto Teixeira <maragato@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License version 2 as published by the Free Software Foundation.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "karchive.h"
|
|
#include "klimitediodevice_p.h"
|
|
|
|
#include <config.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <ksavefile.h>
|
|
#include <kde_file.h>
|
|
|
|
#include <QStack>
|
|
#include <QtCore/QMap>
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QFile>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <assert.h>
|
|
#include <sys/stat.h>
|
|
#ifdef Q_OS_UNIX
|
|
#include <limits.h> // PATH_MAX
|
|
#endif
|
|
|
|
class KArchivePrivate
|
|
{
|
|
public:
|
|
KArchivePrivate()
|
|
: rootDir( 0 ),
|
|
saveFile( 0 ),
|
|
dev ( 0 ),
|
|
fileName(),
|
|
mode( QIODevice::NotOpen ),
|
|
deviceOwned( false )
|
|
{}
|
|
~KArchivePrivate()
|
|
{
|
|
delete saveFile;
|
|
delete rootDir;
|
|
}
|
|
void abortWriting();
|
|
|
|
KArchiveDirectory* rootDir;
|
|
KSaveFile* saveFile;
|
|
QIODevice * dev;
|
|
QString fileName;
|
|
QIODevice::OpenMode mode;
|
|
bool deviceOwned; // if true, we (KArchive) own dev and must delete it
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////// KArchive ///////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
KArchive::KArchive( const QString& fileName )
|
|
: d(new KArchivePrivate)
|
|
{
|
|
Q_ASSERT( !fileName.isEmpty() );
|
|
d->fileName = fileName;
|
|
// This constructor leaves the device set to 0.
|
|
// This is for the use of KSaveFile, see open().
|
|
}
|
|
|
|
KArchive::KArchive( QIODevice * dev )
|
|
: d(new KArchivePrivate)
|
|
{
|
|
d->dev = dev;
|
|
}
|
|
|
|
KArchive::~KArchive()
|
|
{
|
|
if ( isOpen() )
|
|
close(); // WARNING: won't call the virtual method close in the derived class!!!
|
|
|
|
delete d;
|
|
}
|
|
|
|
bool KArchive::open( QIODevice::OpenMode mode )
|
|
{
|
|
Q_ASSERT( mode != QIODevice::NotOpen );
|
|
|
|
if ( isOpen() )
|
|
close();
|
|
|
|
if ( !d->fileName.isEmpty() )
|
|
{
|
|
Q_ASSERT( !d->dev );
|
|
if ( !createDevice( mode ) )
|
|
return false;
|
|
}
|
|
|
|
Q_ASSERT( d->dev );
|
|
|
|
if ( !d->dev->isOpen() && !d->dev->open( mode ) )
|
|
return false;
|
|
|
|
d->mode = mode;
|
|
|
|
Q_ASSERT( !d->rootDir );
|
|
d->rootDir = 0;
|
|
|
|
return openArchive( mode );
|
|
}
|
|
|
|
bool KArchive::createDevice( QIODevice::OpenMode mode )
|
|
{
|
|
switch( mode ) {
|
|
case QIODevice::WriteOnly:
|
|
if ( !d->fileName.isEmpty() ) {
|
|
// The use of KSaveFile can't be done in the ctor (no mode known yet)
|
|
//kDebug() << "Writing to a file using KSaveFile";
|
|
d->saveFile = new KSaveFile( d->fileName );
|
|
if ( !d->saveFile->open() ) {
|
|
kWarning() << "KSaveFile creation for " << d->fileName << " failed, " << d->saveFile->errorString();
|
|
delete d->saveFile;
|
|
d->saveFile = 0;
|
|
return false;
|
|
}
|
|
d->dev = d->saveFile;
|
|
Q_ASSERT( d->dev );
|
|
}
|
|
break;
|
|
case QIODevice::ReadOnly:
|
|
case QIODevice::ReadWrite:
|
|
// ReadWrite mode still uses QFile for now; we'd need to copy to the tempfile, in fact.
|
|
if ( !d->fileName.isEmpty() ) {
|
|
d->dev = new QFile( d->fileName );
|
|
d->deviceOwned = true;
|
|
}
|
|
break; // continued below
|
|
default:
|
|
kWarning() << "Unsupported mode " << d->mode;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KArchive::close()
|
|
{
|
|
if ( !isOpen() )
|
|
return false; // already closed (return false or true? arguable...)
|
|
|
|
// moved by holger to allow kzip to write the zip central dir
|
|
// to the file in closeArchive()
|
|
// DF: added d->dev so that we skip closeArchive if saving aborted.
|
|
bool closeSucceeded = true;
|
|
if ( d->dev ) {
|
|
closeSucceeded = closeArchive();
|
|
if ( d->mode == QIODevice::WriteOnly && !closeSucceeded )
|
|
d->abortWriting();
|
|
}
|
|
|
|
if ( d->dev )
|
|
d->dev->close();
|
|
|
|
// if d->saveFile is not null then it is equal to d->dev.
|
|
if ( d->saveFile ) {
|
|
closeSucceeded = d->saveFile->finalize();
|
|
delete d->saveFile;
|
|
d->saveFile = 0;
|
|
} if ( d->deviceOwned ) {
|
|
delete d->dev; // we created it ourselves in open()
|
|
}
|
|
|
|
delete d->rootDir;
|
|
d->rootDir = 0;
|
|
d->mode = QIODevice::NotOpen;
|
|
d->dev = 0;
|
|
return closeSucceeded;
|
|
}
|
|
|
|
const KArchiveDirectory* KArchive::directory() const
|
|
{
|
|
// rootDir isn't const so that parsing-on-demand is possible
|
|
return const_cast<KArchive *>(this)->rootDir();
|
|
}
|
|
|
|
|
|
bool KArchive::addLocalFile( const QString& fileName, const QString& destName )
|
|
{
|
|
QFileInfo fileInfo( fileName );
|
|
if ( !fileInfo.isFile() && !fileInfo.isSymLink() )
|
|
{
|
|
kWarning() << fileName << "doesn't exist or is not a regular file.";
|
|
return false;
|
|
}
|
|
|
|
KDE_struct_stat fi;
|
|
if (KDE::lstat(fileName,&fi) == -1) {
|
|
kWarning() << "stat'ing" << fileName
|
|
<< "failed:" << strerror(errno);
|
|
return false;
|
|
}
|
|
|
|
if (fileInfo.isSymLink()) {
|
|
QString symLinkTarget;
|
|
// Do NOT use fileInfo.readLink() for unix symlinks!
|
|
// It returns the -full- path to the target, while we want the target string "as is".
|
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_OS2EMX)
|
|
const QByteArray encodedFileName = QFile::encodeName(fileName);
|
|
QByteArray s;
|
|
#if defined(PATH_MAX)
|
|
s.resize(PATH_MAX+1);
|
|
#else
|
|
int path_max = pathconf(encodedFileName.data(), _PC_PATH_MAX);
|
|
if (path_max <= 0) {
|
|
path_max = 4096;
|
|
}
|
|
s.resize(path_max);
|
|
#endif
|
|
int len = readlink(encodedFileName.data(), s.data(), s.size() - 1);
|
|
if ( len >= 0 ) {
|
|
s[len] = '\0';
|
|
symLinkTarget = QFile::decodeName(s);
|
|
}
|
|
#endif
|
|
if (symLinkTarget.isEmpty()) // Mac or Windows
|
|
symLinkTarget = fileInfo.readLink();
|
|
return writeSymLink(destName, symLinkTarget, fileInfo.owner(),
|
|
fileInfo.group(), fi.st_mode, fi.st_atime, fi.st_mtime,
|
|
fi.st_ctime);
|
|
}/*end if*/
|
|
|
|
qint64 size = fileInfo.size();
|
|
|
|
// the file must be opened before prepareWriting is called, otherwise
|
|
// if the opening fails, no content will follow the already written
|
|
// header and the tar file is effectively f*cked up
|
|
QFile file( fileName );
|
|
if ( !file.open( QIODevice::ReadOnly ) )
|
|
{
|
|
kWarning() << "couldn't open file " << fileName;
|
|
return false;
|
|
}
|
|
|
|
if ( !prepareWriting( destName, fileInfo.owner(), fileInfo.group(), size,
|
|
fi.st_mode, fi.st_atime, fi.st_mtime, fi.st_ctime ) )
|
|
{
|
|
kWarning() << " prepareWriting" << destName << "failed";
|
|
return false;
|
|
}
|
|
|
|
// Read and write data in chunks to minimize memory usage
|
|
QByteArray array;
|
|
array.resize( int( qMin( qint64( 1024 * 1024 ), size ) ) );
|
|
qint64 n;
|
|
qint64 total = 0;
|
|
while ( ( n = file.read( array.data(), array.size() ) ) > 0 )
|
|
{
|
|
if ( !writeData( array.data(), n ) )
|
|
{
|
|
kWarning() << "writeData failed";
|
|
return false;
|
|
}
|
|
total += n;
|
|
}
|
|
Q_ASSERT( total == size );
|
|
|
|
if ( !finishWriting( size ) )
|
|
{
|
|
kWarning() << "finishWriting failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KArchive::addLocalDirectory( const QString& path, const QString& destName )
|
|
{
|
|
QDir dir( path );
|
|
if ( !dir.exists() )
|
|
return false;
|
|
dir.setFilter(dir.filter() | QDir::Hidden);
|
|
const QStringList files = dir.entryList();
|
|
for ( QStringList::ConstIterator it = files.begin(); it != files.end(); ++it )
|
|
{
|
|
if ( *it != QLatin1String(".") && *it != QLatin1String("..") )
|
|
{
|
|
QString fileName = path + QLatin1Char('/') + *it;
|
|
// kDebug() << "storing " << fileName;
|
|
QString dest = destName.isEmpty() ? *it : (destName + QLatin1Char('/') + *it);
|
|
QFileInfo fileInfo( fileName );
|
|
|
|
if ( fileInfo.isFile() || fileInfo.isSymLink() )
|
|
addLocalFile( fileName, dest );
|
|
else if ( fileInfo.isDir() )
|
|
addLocalDirectory( fileName, dest );
|
|
// We omit sockets
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KArchive::writeFile( const QString& name, const QString& user,
|
|
const QString& group, const char* data, qint64 size,
|
|
mode_t perm, time_t atime, time_t mtime, time_t ctime )
|
|
{
|
|
if ( !prepareWriting( name, user, group, size, perm, atime, mtime, ctime ) )
|
|
{
|
|
kWarning() << "prepareWriting failed";
|
|
return false;
|
|
}
|
|
|
|
// Write data
|
|
// Note: if data is 0L, don't call write, it would terminate the KFilterDev
|
|
if ( data && size && !writeData( data, size ) )
|
|
{
|
|
kWarning() << "writeData failed";
|
|
return false;
|
|
}
|
|
|
|
if ( !finishWriting( size ) )
|
|
{
|
|
kWarning() << "finishWriting failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KArchive::writeData( const char* data, qint64 size )
|
|
{
|
|
bool ok = device()->write( data, size ) == size;
|
|
if ( !ok )
|
|
d->abortWriting();
|
|
return ok;
|
|
}
|
|
|
|
// The writeDir -> doWriteDir pattern allows to avoid propagating the default
|
|
// values into all virtual methods of subclasses, and it allows more extensibility:
|
|
// if a new argument is needed, we can add a writeDir overload which stores the
|
|
// additional argument in the d pointer, and doWriteDir reimplementations can fetch
|
|
// it from there.
|
|
|
|
bool KArchive::writeDir( const QString& name, const QString& user, const QString& group,
|
|
mode_t perm, time_t atime,
|
|
time_t mtime, time_t ctime )
|
|
{
|
|
return doWriteDir( name, user, group, perm | 040000, atime, mtime, ctime );
|
|
}
|
|
|
|
bool KArchive::writeSymLink(const QString &name, const QString &target,
|
|
const QString &user, const QString &group,
|
|
mode_t perm, time_t atime,
|
|
time_t mtime, time_t ctime )
|
|
{
|
|
return doWriteSymLink( name, target, user, group, perm, atime, mtime, ctime );
|
|
}
|
|
|
|
|
|
bool KArchive::prepareWriting( const QString& name, const QString& user,
|
|
const QString& group, qint64 size,
|
|
mode_t perm, time_t atime,
|
|
time_t mtime, time_t ctime )
|
|
{
|
|
bool ok = doPrepareWriting( name, user, group, size, perm, atime, mtime, ctime );
|
|
if ( !ok )
|
|
d->abortWriting();
|
|
return ok;
|
|
}
|
|
|
|
bool KArchive::finishWriting( qint64 size )
|
|
{
|
|
return doFinishWriting( size );
|
|
}
|
|
|
|
KArchiveDirectory * KArchive::rootDir()
|
|
{
|
|
if ( !d->rootDir )
|
|
{
|
|
//kDebug() << "Making root dir ";
|
|
struct passwd* pw = getpwuid( getuid() );
|
|
struct group* grp = getgrgid( getgid() );
|
|
QString username = pw ? QFile::decodeName(pw->pw_name) : QString::number( getuid() );
|
|
QString groupname = grp ? QFile::decodeName(grp->gr_name) : QString::number( getgid() );
|
|
|
|
d->rootDir = new KArchiveDirectory( this, QLatin1String("/"), (int)(0777 + S_IFDIR), 0, username, groupname, QString() );
|
|
}
|
|
return d->rootDir;
|
|
}
|
|
|
|
KArchiveDirectory * KArchive::findOrCreate( const QString & path )
|
|
{
|
|
//kDebug() << path;
|
|
if ( path.isEmpty() || path == QLatin1String("/") || path == QLatin1String(".") ) // root dir => found
|
|
{
|
|
//kDebug() << "returning rootdir";
|
|
return rootDir();
|
|
}
|
|
// Important note : for tar files containing absolute paths
|
|
// (i.e. beginning with "/"), this means the leading "/" will
|
|
// be removed (no KDirectory for it), which is exactly the way
|
|
// the "tar" program works (though it displays a warning about it)
|
|
// See also KArchiveDirectory::entry().
|
|
|
|
// Already created ? => found
|
|
const KArchiveEntry* ent = rootDir()->entry( path );
|
|
if ( ent )
|
|
{
|
|
if ( ent->isDirectory() )
|
|
//kDebug() << "found it";
|
|
return (KArchiveDirectory *) ent;
|
|
else
|
|
kWarning() << "Found" << path << "but it's not a directory";
|
|
}
|
|
|
|
// Otherwise go up and try again
|
|
int pos = path.lastIndexOf( QLatin1Char('/') );
|
|
KArchiveDirectory * parent;
|
|
QString dirname;
|
|
if ( pos == -1 ) // no more slash => create in root dir
|
|
{
|
|
parent = rootDir();
|
|
dirname = path;
|
|
}
|
|
else
|
|
{
|
|
QString left = path.left( pos );
|
|
dirname = path.mid( pos + 1 );
|
|
parent = findOrCreate( left ); // recursive call... until we find an existing dir.
|
|
}
|
|
|
|
//kDebug() << "found parent " << parent->name() << " adding " << dirname << " to ensure " << path;
|
|
// Found -> add the missing piece
|
|
KArchiveDirectory * e = new KArchiveDirectory( this, dirname, d->rootDir->permissions(),
|
|
d->rootDir->date(), d->rootDir->user(),
|
|
d->rootDir->group(), QString() );
|
|
parent->addEntry( e );
|
|
return e; // now a directory to <path> exists
|
|
}
|
|
|
|
void KArchive::setDevice( QIODevice * dev )
|
|
{
|
|
if ( d->deviceOwned )
|
|
delete d->dev;
|
|
d->dev = dev;
|
|
d->deviceOwned = false;
|
|
}
|
|
|
|
void KArchive::setRootDir( KArchiveDirectory *rootDir )
|
|
{
|
|
Q_ASSERT( !d->rootDir ); // Call setRootDir only once during parsing please ;)
|
|
d->rootDir = rootDir;
|
|
}
|
|
|
|
QIODevice::OpenMode KArchive::mode() const
|
|
{
|
|
return d->mode;
|
|
}
|
|
|
|
QIODevice * KArchive::device() const
|
|
{
|
|
return d->dev;
|
|
}
|
|
|
|
bool KArchive::isOpen() const
|
|
{
|
|
return d->mode != QIODevice::NotOpen;
|
|
}
|
|
|
|
QString KArchive::fileName() const
|
|
{
|
|
return d->fileName;
|
|
}
|
|
|
|
void KArchivePrivate::abortWriting()
|
|
{
|
|
if ( saveFile ) {
|
|
saveFile->abort();
|
|
delete saveFile;
|
|
saveFile = 0;
|
|
dev = 0;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
/////////////////////// KArchiveEntry //////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
class KArchiveEntryPrivate
|
|
{
|
|
public:
|
|
KArchiveEntryPrivate( KArchive* _archive, const QString& _name, int _access,
|
|
int _date, const QString& _user, const QString& _group,
|
|
const QString& _symlink) :
|
|
name(_name),
|
|
date(_date),
|
|
access(_access),
|
|
user(_user),
|
|
group(_group),
|
|
symlink(_symlink),
|
|
archive(_archive)
|
|
{}
|
|
QString name;
|
|
int date;
|
|
mode_t access;
|
|
QString user;
|
|
QString group;
|
|
QString symlink;
|
|
KArchive* archive;
|
|
};
|
|
|
|
KArchiveEntry::KArchiveEntry( KArchive* t, const QString& name, int access, int date,
|
|
const QString& user, const QString& group, const
|
|
QString& symlink) :
|
|
d(new KArchiveEntryPrivate(t,name,access,date,user,group,symlink))
|
|
{
|
|
}
|
|
|
|
KArchiveEntry::~KArchiveEntry()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
QDateTime KArchiveEntry::datetime() const
|
|
{
|
|
QDateTime datetimeobj;
|
|
datetimeobj.setTime_t( d->date );
|
|
return datetimeobj;
|
|
}
|
|
|
|
int KArchiveEntry::date() const
|
|
{
|
|
return d->date;
|
|
}
|
|
|
|
QString KArchiveEntry::name() const
|
|
{
|
|
return d->name;
|
|
}
|
|
|
|
mode_t KArchiveEntry::permissions() const
|
|
{
|
|
return d->access;
|
|
}
|
|
|
|
QString KArchiveEntry::user() const
|
|
{
|
|
return d->user;
|
|
}
|
|
|
|
QString KArchiveEntry::group() const
|
|
{
|
|
return d->group;
|
|
}
|
|
|
|
QString KArchiveEntry::symLinkTarget() const
|
|
{
|
|
return d->symlink;
|
|
}
|
|
|
|
bool KArchiveEntry::isFile() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool KArchiveEntry::isDirectory() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
KArchive* KArchiveEntry::archive() const
|
|
{
|
|
return d->archive;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
/////////////////////// KArchiveFile ///////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
class KArchiveFilePrivate
|
|
{
|
|
public:
|
|
KArchiveFilePrivate( qint64 _pos, qint64 _size ) :
|
|
pos(_pos),
|
|
size(_size)
|
|
{}
|
|
qint64 pos;
|
|
qint64 size;
|
|
};
|
|
|
|
KArchiveFile::KArchiveFile( KArchive* t, const QString& name, int access, int date,
|
|
const QString& user, const QString& group,
|
|
const QString & symlink,
|
|
qint64 pos, qint64 size )
|
|
: KArchiveEntry( t, name, access, date, user, group, symlink ),
|
|
d( new KArchiveFilePrivate(pos, size) )
|
|
{
|
|
}
|
|
|
|
KArchiveFile::~KArchiveFile()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
qint64 KArchiveFile::position() const
|
|
{
|
|
return d->pos;
|
|
}
|
|
|
|
qint64 KArchiveFile::size() const
|
|
{
|
|
return d->size;
|
|
}
|
|
|
|
void KArchiveFile::setSize( qint64 s )
|
|
{
|
|
d->size = s;
|
|
}
|
|
|
|
QByteArray KArchiveFile::data() const
|
|
{
|
|
bool ok = archive()->device()->seek( d->pos );
|
|
if (!ok) {
|
|
kWarning() << "Failed to sync to" << d->pos << "to read" << name();
|
|
}
|
|
|
|
// Read content
|
|
QByteArray arr;
|
|
if ( d->size )
|
|
{
|
|
arr = archive()->device()->read( d->size );
|
|
Q_ASSERT( arr.size() == d->size );
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
QIODevice * KArchiveFile::createDevice() const
|
|
{
|
|
return new KLimitedIODevice( archive()->device(), d->pos, d->size );
|
|
}
|
|
|
|
bool KArchiveFile::isFile() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void KArchiveFile::copyTo(const QString& dest) const
|
|
{
|
|
QFile f( dest + QLatin1Char('/') + name() );
|
|
if ( f.open( QIODevice::ReadWrite | QIODevice::Truncate ) )
|
|
{
|
|
QIODevice* inputDev = createDevice();
|
|
|
|
// Read and write data in chunks to minimize memory usage
|
|
const qint64 chunkSize = 1024 * 1024;
|
|
qint64 remainingSize = d->size;
|
|
QByteArray array;
|
|
array.resize( int( qMin( chunkSize, remainingSize ) ) );
|
|
|
|
while ( remainingSize > 0 ) {
|
|
const qint64 currentChunkSize = qMin( chunkSize, remainingSize );
|
|
const qint64 n = inputDev->read( array.data(), currentChunkSize );
|
|
Q_ASSERT( n == currentChunkSize );
|
|
Q_UNUSED( n );
|
|
f.write( array.data(), currentChunkSize );
|
|
remainingSize -= currentChunkSize;
|
|
}
|
|
f.close();
|
|
|
|
delete inputDev;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
//////////////////////// KArchiveDirectory /////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
class KArchiveDirectoryPrivate
|
|
{
|
|
public:
|
|
~KArchiveDirectoryPrivate()
|
|
{
|
|
qDeleteAll(entries);
|
|
}
|
|
QHash<QString, KArchiveEntry *> entries;
|
|
};
|
|
|
|
KArchiveDirectory::KArchiveDirectory( KArchive* t, const QString& name, int access,
|
|
int date,
|
|
const QString& user, const QString& group,
|
|
const QString &symlink)
|
|
: KArchiveEntry( t, name, access, date, user, group, symlink ),
|
|
d( new KArchiveDirectoryPrivate )
|
|
{
|
|
}
|
|
|
|
KArchiveDirectory::~KArchiveDirectory()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
QStringList KArchiveDirectory::entries() const
|
|
{
|
|
return d->entries.keys();
|
|
}
|
|
|
|
const KArchiveEntry* KArchiveDirectory::entry( const QString& _name ) const
|
|
{
|
|
QString name = QDir::cleanPath(_name);
|
|
int pos = name.indexOf( QLatin1Char('/') );
|
|
if ( pos == 0 ) // ouch absolute path (see also KArchive::findOrCreate)
|
|
{
|
|
if (name.length()>1)
|
|
{
|
|
name = name.mid( 1 ); // remove leading slash
|
|
pos = name.indexOf( QLatin1Char('/') ); // look again
|
|
}
|
|
else // "/"
|
|
return this;
|
|
}
|
|
// trailing slash ? -> remove
|
|
if ( pos != -1 && pos == name.length()-1 )
|
|
{
|
|
name = name.left( pos );
|
|
pos = name.indexOf( QLatin1Char('/') ); // look again
|
|
}
|
|
if ( pos != -1 )
|
|
{
|
|
const QString left = name.left(pos);
|
|
const QString right = name.mid(pos + 1);
|
|
|
|
//kDebug() << "left=" << left << "right=" << right;
|
|
|
|
const KArchiveEntry* e = d->entries.value( left );
|
|
if ( !e || !e->isDirectory() )
|
|
return 0;
|
|
return static_cast<const KArchiveDirectory*>(e)->entry( right );
|
|
}
|
|
|
|
return d->entries.value( name );
|
|
}
|
|
|
|
void KArchiveDirectory::addEntry( KArchiveEntry* entry )
|
|
{
|
|
if( entry->name().isEmpty() )
|
|
return;
|
|
|
|
if( d->entries.value( entry->name() ) ) {
|
|
kWarning() << "directory " << name()
|
|
<< "has entry" << entry->name() << "already";
|
|
return;
|
|
}
|
|
d->entries.insert( entry->name(), entry );
|
|
}
|
|
|
|
void KArchiveDirectory::removeEntry( KArchiveEntry* entry )
|
|
{
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
|
|
QHash<QString, KArchiveEntry*>::Iterator it = d->entries.find(entry->name());
|
|
// nothing removed?
|
|
if (it == d->entries.end()) {
|
|
kWarning() << "directory " << name()
|
|
<< "has no entry with name " << entry->name();
|
|
return;
|
|
}
|
|
if (it.value() != entry) {
|
|
kWarning() << "directory " << name()
|
|
<< "has another entry for name " << entry->name();
|
|
return;
|
|
}
|
|
d->entries.erase(it);
|
|
}
|
|
|
|
bool KArchiveDirectory::isDirectory() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static bool sortByPosition( const KArchiveFile* file1, const KArchiveFile* file2 ) {
|
|
return file1->position() < file2->position();
|
|
}
|
|
|
|
void KArchiveDirectory::copyTo(const QString& dest, bool recursiveCopy ) const
|
|
{
|
|
QDir root;
|
|
|
|
QList<const KArchiveFile*> fileList;
|
|
QMap<qint64, QString> fileToDir;
|
|
|
|
// placeholders for iterated items
|
|
QStack<const KArchiveDirectory *> dirStack;
|
|
QStack<QString> dirNameStack;
|
|
|
|
dirStack.push( this ); // init stack at current directory
|
|
dirNameStack.push( dest ); // ... with given path
|
|
do {
|
|
const KArchiveDirectory* curDir = dirStack.pop();
|
|
const QString curDirName = dirNameStack.pop();
|
|
root.mkdir(curDirName);
|
|
|
|
const QStringList dirEntries = curDir->entries();
|
|
foreach(const QString it, dirEntries) {
|
|
const KArchiveEntry* curEntry = curDir->entry(it);
|
|
if (!curEntry->symLinkTarget().isEmpty()) {
|
|
const QString linkName = curDirName+QLatin1Char('/')+curEntry->name();
|
|
#ifdef Q_OS_UNIX
|
|
if (!::symlink(curEntry->symLinkTarget().toLocal8Bit(), linkName.toLocal8Bit())) {
|
|
kDebug() << "symlink(" << curEntry->symLinkTarget() << ',' << linkName << ") failed:" << strerror(errno);
|
|
}
|
|
#else
|
|
// TODO - how to create symlinks on other platforms?
|
|
#endif
|
|
} else {
|
|
if ( curEntry->isFile() ) {
|
|
const KArchiveFile* curFile = dynamic_cast<const KArchiveFile*>( curEntry );
|
|
if (curFile) {
|
|
fileList.append( curFile );
|
|
fileToDir.insert( curFile->position(), curDirName );
|
|
}
|
|
}
|
|
|
|
if ( curEntry->isDirectory() && recursiveCopy ) {
|
|
const KArchiveDirectory *ad = dynamic_cast<const KArchiveDirectory*>( curEntry );
|
|
if (ad) {
|
|
dirStack.push( ad );
|
|
dirNameStack.push( curDirName + QLatin1Char('/') + curEntry->name() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (!dirStack.isEmpty());
|
|
|
|
qSort( fileList.begin(), fileList.end(), sortByPosition ); // sort on d->pos, so we have a linear access
|
|
|
|
foreach(const KArchiveFile *it, fileList) {
|
|
qint64 pos = it->position();
|
|
it->copyTo( fileToDir[pos] );
|
|
}
|
|
}
|
|
|