kutils: new karchive library to replace KZip and KTar

it has to solve a few problems like:
1. blocking the current thread while listing, extracting or adding to
   archive
2. lack of support for some archive formats such as 7-zip in the
   previously available classes for dealing with archive
3. progress and error reporting

a KArchiveJob class may have to be implemented to solve the above
issues. the API is just as frustrating to use as KTar and KZip right now
tho and I am not happy with that but the class itself is a 3-rd rewrite
of what was once Python module, plugin for another project and now used
as base in library.

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2022-10-06 15:50:40 +03:00
parent c27d7e7ee6
commit 3ab3f3b47f
48 changed files with 1557 additions and 5929 deletions

View file

@ -31,7 +31,7 @@
# git has its own built in compression methods
# You can enable these (for most people's uses),
# but keep in mind test file for KTar/KZip would
# but keep in mind test file for KArchive would
# obviously need these.
#*.7z
#*.dmg

View file

@ -270,7 +270,14 @@ set_package_properties(Speechd PROPERTIES
DESCRIPTION "Speech Dispatcher provides a high-level device independent layer for speech synthesis"
URL "https://freebsoft.org/speechd"
TYPE RECOMMENDED
PURPOSE "Text To Speech"
PURPOSE "Text-To-Speech"
)
kde4_optional_find_package(LibArchive 3.0.3)
set_package_properties(LibArchive PROPERTIES
DESCRIPTION "Multi-format archive and compression library"
URL "https://libarchive.org/"
PURPOSE "Archives in TAR, ZIP and other formats"
)
################# configure checks and create the configured files #################

View file

@ -42,6 +42,7 @@ kde4_bool_to_01(X11_XSync_FOUND HAVE_XSYNC) # kidletim
# check is to be added to get the proper set of headers.
check_symbol_exists(strtoll "stdlib.h" HAVE_STRTOLL) # kioslave
check_symbol_exists(getgrouplist "unistd.h;grp.h" HAVE_GETGROUPLIST) # kio
check_symbol_exists(strmode "string.h" HAVE_STRMODE) # karchive
check_function_exists(backtrace HAVE_BACKTRACE) # kdecore, kio
check_function_exists(fdatasync HAVE_FDATASYNC) # kdecore

View file

@ -63,6 +63,7 @@
# KDE4_KDNSSD_LIBS - the kdnssd library and all depending libraries
# KDE4_KHTTP_LIBS - the khttp library and all depending libraries
# KDE4_KSPEECH_LIBS - the kspeech library and all depending libraries
# KDE4_KARCHIVE_LIBS - the kspeech library and all depending libraries
#
# The variable INSTALL_TARGETS_DEFAULT_ARGS can be used when installing libraries
# or executables into the default locations.
@ -243,6 +244,7 @@ set(_kde_libraries
kdnssd
khttp
kspeech
karchive
kfile
kidletime
kio

View file

@ -22,6 +22,7 @@
#cmakedefine HAVE_SETMNTENT 1
#cmakedefine HAVE_STRTOLL 1
#cmakedefine HAVE_GETGROUPLIST 1
#cmakedefine HAVE_STRMODE 1
/* Define to 1 if you have the Xtest extension */
#cmakedefine HAVE_XTEST 1

View file

@ -11,11 +11,8 @@ install(
KActionMenu
KActionSelector
KApplication
KAr
KArchive
KArchiveDirectory
KArchiveEntry
KArchiveFile
KAssistantDialog
KAuthorization
KAutoMount
@ -292,7 +289,6 @@ install(
KSystemTrayIcon
KTabBar
KTabWidget
KTar
KTempDir
KTemporaryFile
KTextEdit
@ -342,8 +338,6 @@ install(
KXmlGuiWindow
KXMessages
KXYSelector
KZip
KZipFileEntry
NET
NETRootInfo
NETWinInfo

View file

@ -1 +0,0 @@
#include "../kar.h"

View file

@ -1 +0,0 @@
#include "../karchive.h"

View file

@ -1 +0,0 @@
#include "../karchive.h"

View file

@ -1 +0,0 @@
#include "../ktar.h"

View file

@ -1 +0,0 @@
#include "../kzip.h"

View file

@ -1 +0,0 @@
#include "../kzip.h"

View file

@ -122,21 +122,16 @@ set(kdecore_LIB_SRCS
date/klocalizeddate.cpp
date/ktimezone.cpp
date/ksystemtimezone.cpp
io/kar.cpp
io/karchive.cpp
io/kdebug.cpp
io/kdirwatch.cpp
io/kfilesystemtype_p.cpp
io/klimitediodevice.cpp
io/kmessage.cpp
io/kmountpoint.cpp
io/kprocess.cpp
io/ksavefile.cpp
io/ktar.cpp
io/ktempdir.cpp
io/ktemporaryfile.cpp
io/kurl.cpp
io/kzip.cpp
jobs/kcompositejob.cpp
jobs/kjob.cpp
jobs/kjobuidelegate.cpp
@ -295,8 +290,6 @@ install(
date/klocalizeddate.h
date/ksystemtimezone.h
date/ktimezone.h
io/kar.h
io/karchive.h
io/kdebug.h
io/kdirwatch.h
io/kmessage.h
@ -304,11 +297,9 @@ install(
io/klockfile.h
io/kmountpoint.h
io/ksavefile.h
io/ktar.h
io/ktempdir.h
io/ktemporaryfile.h
io/kurl.h
io/kzip.h
jobs/kcompositejob.h
jobs/kjob.h
jobs/kjobuidelegate.h

View file

@ -1,187 +0,0 @@
/* This file is part of the KDE libraries
Copyright (C) 2002 Laurence Anderson <l.d.anderson@warwick.ac.uk>
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 "kar.h"
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <kdebug.h>
#include <kmimetype.h>
#include <QtCore/QRegExp>
#include "kfilterdev.h"
//#include "klimitediodevice_p.h"
////////////////////////////////////////////////////////////////////////
/////////////////////////// KAr ///////////////////////////////////////
////////////////////////////////////////////////////////////////////////
class KAr::KArPrivate
{
public:
KArPrivate() {}
};
KAr::KAr( const QString& filename )
: KArchive( filename ), d(new KArPrivate)
{
}
KAr::KAr( QIODevice * dev )
: KArchive( dev ), d(new KArPrivate)
{
}
KAr::~KAr()
{
if( isOpen() )
close();
delete d;
}
bool KAr::doPrepareWriting( const QString&, const QString&, const QString&,
qint64, mode_t, time_t, time_t, time_t )
{
return false;
}
bool KAr::doFinishWriting( qint64 )
{
return false;
}
bool KAr::doWriteDir( const QString&, const QString&, const QString&,
mode_t, time_t, time_t, time_t )
{
return false;
}
bool KAr::doWriteSymLink( const QString&, const QString&, const QString&,
const QString&, mode_t, time_t, time_t, time_t )
{
return false;
}
bool KAr::openArchive( QIODevice::OpenMode mode )
{
// Open archive
if ( mode == QIODevice::WriteOnly )
return true;
if ( mode != QIODevice::ReadOnly && mode != QIODevice::ReadWrite )
{
kWarning(7042) << "Unsupported mode " << mode;
return false;
}
QIODevice* dev = device();
if ( !dev )
return false;
QByteArray magic = dev->read( 7 );
if ( magic != "!<arch>" ) {
kWarning(7042) << "Invalid main magic";
return false;
}
char *ar_longnames = 0;
while (! dev->atEnd()) {
QByteArray ar_header;
ar_header.resize(61);
QByteArray name;
int date, uid, gid, mode;
qint64 size;
QString suid, sgid;
dev->seek( dev->pos() + (2 - (dev->pos() % 2)) % 2 ); // Ar headers are padded to byte boundary
if ( dev->read(ar_header.data(), 60) != 60 ) { // Read ar header
kWarning(7042) << "Couldn't read header";
delete[] ar_longnames;
//return false;
return true; // Probably EOF / trailing junk
}
if (!ar_header.endsWith("`\n")) { // Check header magic // krazy:exclude=strings
kWarning(7042) << "Invalid magic";
delete[] ar_longnames;
return false;
}
name = ar_header.mid( 0, 16 ); // Process header
date = ar_header.mid( 16, 12 ).toInt();
uid = ar_header.mid( 28, 6 ).toInt();
gid = ar_header.mid( 34, 6 ).toInt();
mode = ar_header.mid( 40, 8 ).toInt();
size = ar_header.mid( 48, 10 ).toInt();
suid = QLatin1String(getpwuid(uid)->pw_name);
sgid = QLatin1String(getgrgid(gid)->gr_name);
bool skip_entry = false; // Deal with special entries
if (name.mid(0, 1) == "/") {
if (name.mid(1, 1) == "/") { // Longfilename table entry
delete[] ar_longnames;
ar_longnames = new char[size + 1];
ar_longnames[size] = '\0';
dev->read(ar_longnames, size);
skip_entry = true;
kDebug(7042) << "Read in longnames entry";
} else if (name.mid(1, 1) == " ") { // Symbol table entry
kDebug(7042) << "Skipped symbol entry";
dev->seek( dev->pos() + size );
skip_entry = true;
} else { // Longfilename
kDebug(7042) << "Longfilename #" << name.mid(1, 15).toInt();
if (! ar_longnames) {
kWarning(7042) << "Invalid longfilename reference";
delete[] ar_longnames;
return false;
}
name = &ar_longnames[name.mid(1, 15).toInt()];
name = name.left(name.indexOf("/"));
}
}
if (skip_entry) continue;
name = name.trimmed(); // Process filename
name.replace( '/', QByteArray() );
kDebug(7042) << "Filename: " << name << " Size: " << size;
KArchiveEntry* entry = new KArchiveFile(this, QString::fromLocal8Bit(name), mode, date,
suid, sgid, /*symlink*/ QString(),
dev->pos(), size);
rootDir()->addEntry(entry); // Ar files don't support directories, so everything in root
dev->seek( dev->pos() + size ); // Skip contents
}
delete[] ar_longnames;
return true;
}
bool KAr::closeArchive()
{
// Close the archive
return true;
}

View file

@ -1,91 +0,0 @@
/* This file is part of the KDE libraries
Copyright (C) 2002 Laurence Anderson <l.d.anderson@warwick.ac.uk>
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.
*/
#ifndef KAR_H
#define KAR_H
#include <karchive.h>
/**
* KAr is a class for reading archives in ar format. Writing
* is not supported.
* @short A class for reading ar archives.
* @author Laurence Anderson <l.d.anderson@warwick.ac.uk>
*/
class KDECORE_EXPORT KAr : public KArchive
{
public:
/**
* Creates an instance that operates on the given filename.
*
* @param filename is a local path (e.g. "/home/holger/myfile.ar")
*/
KAr( const QString& filename );
/**
* Creates an instance that operates on the given device.
* The device can be compressed (KFilterDev) or not (QFile, etc.).
* @param dev the device to read from
*/
KAr( QIODevice * dev );
/**
* If the ar file is still opened, then it will be
* closed automatically by the destructor.
*/
virtual ~KAr();
protected:
/*
* Writing not supported by this class, will always fail.
* @return always false
*/
virtual bool doPrepareWriting( const QString& name, const QString& user, const QString& group, qint64 size,
mode_t perm, time_t atime, time_t mtime, time_t ctime );
/*
* Writing not supported by this class, will always fail.
* @return always false
*/
virtual bool doFinishWriting( qint64 size );
/*
* Writing not supported by this class, will always fail.
* @return always false
*/
virtual bool doWriteDir( const QString& name, const QString& user, const QString& group,
mode_t perm, time_t atime, time_t mtime, time_t ctime );
virtual bool doWriteSymLink( const QString &name, const QString &target,
const QString &user, const QString &group, mode_t perm, time_t atime, time_t mtime, time_t ctime );
/**
* Opens the archive for reading.
* Parses the directory listing of the archive
* and creates the KArchiveDirectory/KArchiveFile entries.
*
*/
virtual bool openArchive( QIODevice::OpenMode mode );
virtual bool closeArchive();
private:
class KArPrivate;
KArPrivate* const d;
};
#endif

View file

@ -1,849 +0,0 @@
/* 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>
#include <limits.h> // PATH_MAX
#ifndef PATH_MAX
# define PATH_MAX _POSIX_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".
const QByteArray encodedFileName = QFile::encodeName(fileName);
QByteArray s(PATH_MAX, Qt::Uninitialized);
int len = readlink(encodedFileName.data(), s.data(), s.size() - 1);
if ( len >= 0 ) {
s[len] = '\0';
symLinkTarget = QFile::decodeName(s);
}
if (symLinkTarget.isEmpty())
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 );
Q_UNUSED( total );
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] );
}
}

View file

@ -1,597 +0,0 @@
/* 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.h 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.
*/
#ifndef KARCHIVE_H
#define KARCHIVE_H
#include <sys/types.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qstringlist.h>
#include <kdecore_export.h>
class KArchiveDirectory;
class KArchiveFile;
class KArchivePrivate;
/**
* KArchive is a base class for reading and writing archives.
* @short generic class for reading/writing archives
* @author David Faure <faure@kde.org>
*/
class KDECORE_EXPORT KArchive
{
protected:
/**
* Base constructor (protected since this is a pure virtual class).
* @param fileName is a local path (e.g. "/tmp/myfile.ext"),
* from which the archive will be read from, or into which the archive
* will be written, depending on the mode given to open().
*/
KArchive( const QString& fileName );
/**
* Base constructor (protected since this is a pure virtual class).
* @param dev the I/O device where the archive reads its data
* Note that this can be a file, but also a data buffer, a compression filter, etc.
* For a file in writing mode it is better to use the other constructor
* though, to benefit from the use of KSaveFile when saving.
*/
KArchive( QIODevice * dev );
public:
virtual ~KArchive();
/**
* Opens the archive for reading or writing.
* Inherited classes might want to reimplement openArchive instead.
* @param mode may be QIODevice::ReadOnly or QIODevice::WriteOnly
* @see close
*/
virtual bool open( QIODevice::OpenMode mode );
/**
* Closes the archive.
* Inherited classes might want to reimplement closeArchive instead.
*
* @return true if close succeeded without problems
* @see open
*/
virtual bool close();
/**
* Checks whether the archive is open.
* @return true if the archive is opened
*/
bool isOpen() const;
/**
* Returns the mode in which the archive was opened
* @return the mode in which the archive was opened (QIODevice::ReadOnly or QIODevice::WriteOnly)
* @see open()
*/
QIODevice::OpenMode mode() const;
/**
* The underlying device.
* @return the underlying device.
*/
QIODevice * device() const;
/**
* The name of the archive file, as passed to the constructor that takes a
* fileName, or an empty string if you used the QIODevice constructor.
* @return the name of the file, or QString() if unknown
*/
QString fileName() const;
/**
* If an archive is opened for reading, then the contents
* of the archive can be accessed via this function.
* @return the directory of the archive
*/
const KArchiveDirectory* directory() const;
/**
* Writes a local file into the archive. The main difference with writeFile,
* is that this method minimizes memory usage, by not loading the whole file
* into memory in one go.
*
* If @p fileName is a symbolic link, it will be written as is, i. e.
* it will not be resolved before.
* @param fileName full path to an existing local file, to be added to the archive.
* @param destName the resulting name (or relative path) of the file in the archive.
*/
bool addLocalFile( const QString& fileName, const QString& destName );
/**
* Writes a local directory into the archive, including all its contents, recursively.
* Calls addLocalFile for each file to be added.
*
* Since KDE 3.2 it will also add a @p path that is a symbolic link to a
* directory. The symbolic link will be dereferenced and the content of the
* directory it is pointing to added recursively. However, symbolic links
* *under* @p path will be stored as is.
* @param path full path to an existing local directory, to be added to the archive.
* @param destName the resulting name (or relative path) of the file in the archive.
*/
bool addLocalDirectory( const QString& path, const QString& destName );
enum { UnknownTime = static_cast<time_t>( -1 ) };
/**
* If an archive is opened for writing then you can add new directories
* using this function. KArchive won't write one directory twice.
*
* This method also allows some file metadata to be set.
* However, depending on the archive type not all metadata might be regarded.
*
* @param name the name of the directory
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
*/
virtual bool writeDir( const QString& name, const QString& user, const QString& group,
mode_t perm = 040755, time_t atime = UnknownTime,
time_t mtime = UnknownTime, time_t ctime = UnknownTime );
/**
* Writes a symbolic link to the archive if supported.
* The archive must be opened for writing.
*
* @param name name of symbolic link
* @param target target of symbolic link
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
*/
virtual bool writeSymLink(const QString &name, const QString &target,
const QString &user, const QString &group,
mode_t perm = 0120755, time_t atime = UnknownTime,
time_t mtime = UnknownTime, time_t ctime = UnknownTime );
/**
* If an archive is opened for writing then you can add a new file
* using this function. If the file name is for example "mydir/test1" then
* the directory "mydir" is automatically appended first if that did not
* happen yet.
*
* This method also allows some file metadata to be
* set. However, depending on the archive type not all metadata might be
* regarded.
* @param name the name of the file
* @param user the user that owns the file
* @param group the group that owns the file
* @param data the data to write (@p size bytes)
* @param size the size of the file
* @param perm permissions of the file
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
*/
virtual bool writeFile( const QString& name, const QString& user, const QString& group,
const char* data, qint64 size,
mode_t perm = 0100644, time_t atime = UnknownTime,
time_t mtime = UnknownTime, time_t ctime = UnknownTime );
/**
* Here's another way of writing a file into an archive:
* Call prepareWriting(), then call writeData()
* as many times as wanted then call finishWriting( totalSize ).
* For tar.gz files, you need to know the size before hand, it is needed in the header!
* For zip files, size isn't used.
*
* This method also allows some file metadata to be
* set. However, depending on the archive type not all metadata might be
* regarded.
* @param name the name of the file
* @param user the user that owns the file
* @param group the group that owns the file
* @param size the size of the file
* @param perm permissions of the file
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
*/
virtual bool prepareWriting( const QString& name, const QString& user,
const QString& group, qint64 size,
mode_t perm = 0100644, time_t atime = UnknownTime,
time_t mtime = UnknownTime, time_t ctime = UnknownTime );
/**
* Write data into the current file - to be called after calling prepareWriting
*/
virtual bool writeData( const char* data, qint64 size );
/**
* Call finishWriting after writing the data.
* @param size the size of the file
* @see prepareWriting()
*/
virtual bool finishWriting( qint64 size );
protected:
/**
* Opens an archive for reading or writing.
* Called by open.
* @param mode may be QIODevice::ReadOnly or QIODevice::WriteOnly
*/
virtual bool openArchive( QIODevice::OpenMode mode ) = 0;
/**
* Closes the archive.
* Called by close.
*/
virtual bool closeArchive() = 0;
/**
* Retrieves or create the root directory.
* The default implementation assumes that openArchive() did the parsing,
* so it creates a dummy rootdir if none was set (write mode, or no '/' in the archive).
* Reimplement this to provide parsing/listing on demand.
* @return the root directory
*/
virtual KArchiveDirectory* rootDir();
/**
* Write a directory to the archive.
* This virtual method must be implemented by subclasses.
*
* Depending on the archive type not all metadata might be used.
*
* @param name the name of the directory
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory. Use 040755 if you don't have any other information.
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
* @see writeDir
*/
virtual bool doWriteDir( const QString& name, const QString& user, const QString& group,
mode_t perm, time_t atime, time_t mtime, time_t ctime ) = 0;
/**
* Writes a symbolic link to the archive.
* This virtual method must be implemented by subclasses.
*
* @param name name of symbolic link
* @param target target of symbolic link
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
* @see writeSymLink
*/
virtual bool doWriteSymLink(const QString &name, const QString &target,
const QString &user, const QString &group,
mode_t perm, time_t atime, time_t mtime, time_t ctime) = 0;
/**
* This virtual method must be implemented by subclasses.
*
* Depending on the archive type not all metadata might be used.
*
* @param name the name of the file
* @param user the user that owns the file
* @param group the group that owns the file
* @param size the size of the file
* @param perm permissions of the file. Use 0100644 if you don't have any more specific permissions to set.
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
* @see prepareWriting
*/
virtual bool doPrepareWriting( const QString& name, const QString& user,
const QString& group, qint64 size, mode_t perm,
time_t atime, time_t mtime, time_t ctime ) = 0;
/**
* Called after writing the data.
* This virtual method must be implemented by subclasses.
*
* @param size the size of the file
* @see finishWriting()
*/
virtual bool doFinishWriting( qint64 size ) = 0;
/**
* Ensures that @p path exists, create otherwise.
* This handles e.g. tar files missing directory entries, like mico-2.3.0.tar.gz :)
* @param path the path of the directory
* @return the directory with the given @p path
*/
KArchiveDirectory * findOrCreate( const QString & path );
/**
* Can be reimplemented in order to change the creation of the device
* (when using the fileName constructor). By default this method uses
* KSaveFile when saving, and a simple QFile on reading.
* This method is called by open().
*/
virtual bool createDevice( QIODevice::OpenMode mode );
/**
* Can be called by derived classes in order to set the underlying device.
* Note that KArchive will -not- own the device, it must be deleted by the derived class.
*/
void setDevice( QIODevice *dev );
/**
* Derived classes call setRootDir from openArchive,
* to set the root directory after parsing an existing archive.
*/
void setRootDir( KArchiveDirectory *rootDir );
private:
KArchivePrivate* const d;
};
class KArchiveEntryPrivate;
/**
* A base class for entries in an KArchive.
* @short Base class for the archive-file's directory structure.
*
* @see KArchiveFile
* @see KArchiveDirectory
*/
class KDECORE_EXPORT KArchiveEntry
{
public:
/**
* Creates a new entry.
* @param archive the entries archive
* @param name the name of the entry
* @param access the permissions in unix format
* @param date the date (in seconds since 1970)
* @param user the user that owns the entry
* @param group the group that owns the entry
* @param symlink the symlink, or QString()
*/
KArchiveEntry( KArchive* archive, const QString& name, int access, int date,
const QString& user, const QString& group,
const QString& symlink );
virtual ~KArchiveEntry();
/**
* Creation date of the file.
* @return the creation date
*/
QDateTime datetime() const;
/**
* Creation date of the file.
* @return the creation date in seconds since 1970
*/
int date() const;
/**
* Name of the file without path.
* @return the file name without path
*/
QString name() const;
/**
* The permissions and mode flags as returned by the stat() function
* in st_mode.
* @return the permissions
*/
mode_t permissions() const;
/**
* User who created the file.
* @return the owner of the file
*/
QString user() const;
/**
* Group of the user who created the file.
* @return the group of the file
*/
QString group() const;
/**
* Symlink if there is one.
* @return the symlink, or QString()
*/
QString symLinkTarget() const;
/**
* Checks whether the entry is a file.
* @return true if this entry is a file
*/
virtual bool isFile() const;
/**
* Checks whether the entry is a directory.
* @return true if this entry is a directory
*/
virtual bool isDirectory() const;
protected:
KArchive* archive() const;
private:
KArchiveEntryPrivate* const d;
};
class KArchiveFilePrivate;
/**
* Represents a file entry in a KArchive.
* @short A file in an archive.
*
* @see KArchive
* @see KArchiveDirectory
*/
class KDECORE_EXPORT KArchiveFile : public KArchiveEntry
{
public:
/**
* Creates a new file entry. Do not call this, KArchive takes care of it.
* @param archive the entries archive
* @param name the name of the entry
* @param access the permissions in unix format
* @param date the date (in seconds since 1970)
* @param user the user that owns the entry
* @param group the group that owns the entry
* @param symlink the symlink, or QString()
* @param pos the position of the file in the directory
* @param size the size of the file
*/
KArchiveFile( KArchive* archive, const QString& name, int access, int date,
const QString& user, const QString& group, const QString &symlink,
qint64 pos, qint64 size );
/**
* Destructor. Do not call this, KArchive takes care of it.
*/
virtual ~KArchiveFile();
/**
* Position of the data in the [uncompressed] archive.
* @return the position of the file
*/
qint64 position() const;
/**
* Size of the data.
* @return the size of the file
*/
qint64 size() const;
/**
* Set size of data, usually after writing the file.
* @param s the new size of the file
*/
void setSize( qint64 s );
/**
* Returns the data of the file.
* Call data() with care (only once per file), this data isn't cached.
* @return the content of this file.
*/
virtual QByteArray data() const;
/**
* This method returns QIODevice (internal class: KLimitedIODevice)
* on top of the underlying QIODevice. This is obviously for reading only.
*
* WARNING: Note that the ownership of the device is being transferred to the caller,
* who will have to delete it.
*
* The returned device auto-opens (in readonly mode), no need to open it.
* @return the QIODevice of the file
*/
virtual QIODevice *createDevice() const;
/**
* Checks whether this entry is a file.
* @return true, since this entry is a file
*/
virtual bool isFile() const;
/**
* Extracts the file to the directory @p dest
* @param dest the directory to extract to
*/
void copyTo(const QString& dest) const;
private:
KArchiveFilePrivate* const d;
};
class KArchiveDirectoryPrivate;
/**
* Represents a directory entry in a KArchive.
* @short A directory in an archive.
*
* @see KArchive
* @see KArchiveFile
*/
class KDECORE_EXPORT KArchiveDirectory : public KArchiveEntry
{
public:
/**
* Creates a new directory entry.
* @param archive the entries archive
* @param name the name of the entry
* @param access the permissions in unix format
* @param date the date (in seconds since 1970)
* @param user the user that owns the entry
* @param group the group that owns the entry
* @param symlink the symlink, or QString()
*/
KArchiveDirectory( KArchive* archive, const QString& name, int access, int date,
const QString& user, const QString& group,
const QString& symlink);
virtual ~KArchiveDirectory();
/**
* Returns a list of sub-entries.
* Note that the list is not sorted, it's even in random order (due to using a hashtable).
* Use sort() on the result to sort the list by filename.
*
* @return the names of all entries in this directory (filenames, no path).
*/
QStringList entries() const;
/**
* Returns the entry with the given name.
* @param name may be "test1", "mydir/test3", "mydir/mysubdir/test3", etc.
* @return a pointer to the entry in the directory.
*/
const KArchiveEntry* entry( const QString& name ) const;
/**
* @internal
* Adds a new entry to the directory.
*/
void addEntry( KArchiveEntry* );
/**
* @internal
* Adds a new entry to the directory.
*/
void removeEntry( KArchiveEntry* );
/**
* Checks whether this entry is a directory.
* @return true, since this entry is a directory
*/
virtual bool isDirectory() const;
/**
* Extracts all entries in this archive directory to the directory
* @p dest.
* @param dest the directory to extract to
* @param recursive if set to true, subdirectories are extracted as well
*/
void copyTo(const QString& dest, bool recursive = true) const;
private:
KArchiveDirectoryPrivate* const d;
};
#endif

View file

@ -1,81 +0,0 @@
/* This file is part of the KDE libraries
Copyright (C) 2001, 2002, 2007 David Faure <faure@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 <kdebug.h>
#include "klimitediodevice_p.h"
KLimitedIODevice::KLimitedIODevice( QIODevice *dev, qint64 start, qint64 length )
: m_dev( dev ), m_start( start ), m_length( length )
{
//kDebug(7005) << "start=" << start << "length=" << length;
open( QIODevice::ReadOnly ); //krazy:exclude=syscalls
}
bool KLimitedIODevice::open( QIODevice::OpenMode m )
{
//kDebug(7005) << "m=" << m;
if ( m & QIODevice::ReadOnly ) {
/*bool ok = false;
if ( m_dev->isOpen() )
ok = ( m_dev->mode() == QIODevice::ReadOnly );
else
ok = m_dev->open( m );
if ( ok )*/
m_dev->seek( m_start ); // No concurrent access !
}
else
kWarning(7005) << "KLimitedIODevice::open only supports QIODevice::ReadOnly!";
setOpenMode( QIODevice::ReadOnly );
return true;
}
void KLimitedIODevice::close()
{
}
qint64 KLimitedIODevice::size() const
{
return m_length;
}
qint64 KLimitedIODevice::readData( char * data, qint64 maxlen )
{
maxlen = qMin( maxlen, m_length - pos() ); // Apply upper limit
return m_dev->read( data, maxlen );
}
bool KLimitedIODevice::seek( qint64 pos )
{
Q_ASSERT( pos <= m_length );
pos = qMin( pos, m_length ); // Apply upper limit
bool ret = m_dev->seek( m_start + pos );
if ( ret ) {
QIODevice::seek( pos );
}
return ret;
}
qint64 KLimitedIODevice::bytesAvailable() const
{
return QIODevice::bytesAvailable();
}
bool KLimitedIODevice::isSequential() const
{
return m_dev->isSequential();
}

View file

@ -1,63 +0,0 @@
/* This file is part of the KDE libraries
Copyright (C) 2001, 2002, 2007 David Faure <faure@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.
*/
#ifndef KLIMITEDIODEVICE_P_H
#define KLIMITEDIODEVICE_P_H
#include <QtCore/qiodevice.h>
/**
* A readonly device that reads from an underlying device
* from a given point to another (e.g. to give access to a single
* file inside an archive).
* @author David Faure <faure@kde.org>
* @internal - used by KArchive
*/
class KLimitedIODevice : public QIODevice
{
public:
/**
* Creates a new KLimitedIODevice.
* @param dev the underlying device, opened or not
* This device itself auto-opens (in readonly mode), no need to open it.
* @param start where to start reading (position in bytes)
* @param length the length of the data to read (in bytes)
*/
KLimitedIODevice( QIODevice *dev, qint64 start, qint64 length );
virtual ~KLimitedIODevice() {}
virtual bool isSequential() const;
virtual bool open( QIODevice::OpenMode m );
virtual void close();
virtual qint64 size() const;
virtual qint64 readData ( char * data, qint64 maxlen );
virtual qint64 writeData ( const char *, qint64 ) { return -1; } // unsupported
virtual int putChar( int ) { return -1; } // unsupported
//virtual qint64 pos() const { return m_dev->pos() - m_start; }
virtual bool seek( qint64 pos );
virtual qint64 bytesAvailable() const;
private:
QIODevice* m_dev;
qint64 m_start;
qint64 m_length;
};
#endif

View file

@ -1,860 +0,0 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 David Faure <faure@kde.org>
Copyright (C) 2003 Leo Savernik <l.savernik@aon.at>
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 "ktar.h"
#include <stdlib.h> // strtol
#include <time.h> // time()
#include <assert.h>
#include <sys/stat.h> // S_IFDIR
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <kdebug.h>
#include <kmimetype.h>
#include <ktemporaryfile.h>
#include <kfilterdev.h>
#include <kfilterbase.h>
////////////////////////////////////////////////////////////////////////
/////////////////////////// KTar ///////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Mime types of known filters
static const char application_gzip[] = "application/x-gzip";
static const char application_bzip[] = "application/x-bzip";
static const char application_lzma[] = "application/x-lzma";
static const char application_xz[] = "application/x-xz";
class KTar::KTarPrivate
{
public:
KTarPrivate(KTar *parent)
: q(parent),
tarEnd( 0 ),
tmpFile( 0 )
{
}
KTar *q;
QStringList dirList;
qint64 tarEnd;
KTemporaryFile* tmpFile;
QString mimetype;
QByteArray origFileName;
bool fillTempFile(const QString & fileName);
bool writeBackTempFile( const QString & fileName );
void fillBuffer( char * buffer, const char * mode, qint64 size, time_t mtime,
char typeflag, const char * uname, const char * gname );
void writeLonglink(char *buffer, const QByteArray &name, char typeflag,
const char *uname, const char *gname);
qint64 readRawHeader(char *buffer);
bool readLonglink(char *buffer, QByteArray &longlink);
qint64 readHeader(char *buffer, QString &name, QString &symlink);
};
KTar::KTar( const QString& fileName, const QString & _mimetype )
: KArchive( fileName ), d(new KTarPrivate(this))
{
d->mimetype = _mimetype;
}
KTar::KTar( QIODevice * dev )
: KArchive( dev ), d(new KTarPrivate(this))
{
Q_ASSERT( dev );
}
// Only called when a filename was given
bool KTar::createDevice(QIODevice::OpenMode mode)
{
if (d->mimetype.isEmpty()) {
// Find out mimetype manually
KMimeType::Ptr mime;
if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) {
// Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz,
// we can still do the right thing here.
mime = KMimeType::findByFileContent(fileName());
if (mime == KMimeType::defaultMimeTypePtr()) {
// Unable to determine mimetype from contents, get it from file name
mime = KMimeType::findByPath(fileName(), 0, true);
}
} else {
mime = KMimeType::findByPath(fileName(), 0, true);
}
//kDebug(7041) << mode << mime->name();
if (mime->is(QString::fromLatin1("application/x-compressed-tar")) || mime->is(QString::fromLatin1(application_gzip))) {
// gzipped tar file (with possibly invalid file name), ask for gzip filter
d->mimetype = QString::fromLatin1(application_gzip);
} else if (mime->is(QString::fromLatin1("application/x-bzip-compressed-tar")) || mime->is(QString::fromLatin1(application_bzip))) {
// bzipped2 tar file (with possibly invalid file name), ask for bz2 filter
d->mimetype = QString::fromLatin1(application_bzip);
} else if (mime->is(QString::fromLatin1("application/x-lzma-compressed-tar")) || mime->is(QString::fromLatin1(application_lzma))) {
// lzma compressed tar file (with possibly invalid file name), ask for xz filter
d->mimetype = QString::fromLatin1(application_lzma);
} else if (mime->is(QString::fromLatin1("application/x-xz-compressed-tar")) || mime->is(QString::fromLatin1(application_xz))) {
// xz compressed tar file (with possibly invalid name), ask for xz filter
d->mimetype = QString::fromLatin1(application_xz);
}
}
if (d->mimetype == QLatin1String("application/x-tar")) {
return KArchive::createDevice(mode);
} else if (mode == QIODevice::WriteOnly) {
if (!KArchive::createDevice(mode))
return false;
if (!d->mimetype.isEmpty()) {
// Create a compression filter on top of the KSaveFile device that KArchive created.
//kDebug(7041) << "creating KFilterDev for" << d->mimetype;
QIODevice *filterDev = KFilterDev::device(device(), d->mimetype);
Q_ASSERT(filterDev);
setDevice(filterDev);
}
return true;
} else {
// The compression filters are very slow with random access.
// So instead of applying the filter to the device,
// the file is completely extracted instead,
// and we work on the extracted tar file.
// This improves the extraction speed by the tar ioslave dramatically,
// if the archive file contains many files.
// This is because the tar ioslave extracts one file after the other and normally
// has to walk through the decompression filter each time.
// Which is in fact nearly as slow as a complete decompression for each file.
Q_ASSERT(!d->tmpFile);
d->tmpFile = new KTemporaryFile();
d->tmpFile->setPrefix(QLatin1String("ktar-"));
d->tmpFile->setSuffix(QLatin1String(".tar"));
d->tmpFile->open();
//kDebug(7041) << "creating tempfile:" << d->tmpFile->fileName();
setDevice(d->tmpFile);
return true;
}
}
KTar::~KTar()
{
// mjarrett: Closes to prevent ~KArchive from aborting w/o device
if( isOpen() )
close();
delete d->tmpFile;
delete d;
}
void KTar::setOrigFileName( const QByteArray & fileName ) {
if ( !isOpen() || !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
return;
}
d->origFileName = fileName;
}
qint64 KTar::KTarPrivate::readRawHeader( char *buffer ) {
// Read header
qint64 n = q->device()->read( buffer, 0x200 );
// we need to test if there is a prefix value because the file name can be null
// and the prefix can have a value and in this case we don't reset n.
if ( n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 0) ) {
// Make sure this is actually a tar header
if (strncmp(buffer + 257, "ustar", 5)) {
// The magic isn't there (broken/old tars), but maybe a correct checksum?
int check = 0;
for( uint j = 0; j < 0x200; ++j )
check += buffer[j];
// adjust checksum to count the checksum fields as blanks
for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ )
check -= buffer[148 + j];
check += 8 * ' ';
QByteArray s = QByteArray::number( check, 8 ); // octal
// only compare those of the 6 checksum digits that mean something,
// because the other digits are filled with all sorts of different chars by different tars ...
// Some tars right-justify the checksum so it could start in one of three places - we have to check each.
if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() )
&& strncmp( buffer + 148 + 7 - s.length(), s.data(), s.length() )
&& strncmp( buffer + 148 + 8 - s.length(), s.data(), s.length() ) ) {
kWarning(7041) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
<< "instead of ustar. Reading from wrong pos in file?"
<< "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );
return -1;
}
}/*end if*/
} else {
// reset to 0 if 0x200 because logical end of archive has been reached
if (n == 0x200) n = 0;
}/*end if*/
return n;
}
bool KTar::KTarPrivate::readLonglink(char *buffer,QByteArray &longlink) {
qint64 n = 0;
//kDebug() << "reading longlink from pos " << q->device()->pos();
QIODevice *dev = q->device();
// read size of longlink from size field in header
// size is in bytes including the trailing null (which we ignore)
qint64 size = QByteArray( buffer + 0x7c, 12 ).trimmed().toLongLong( 0, 8 /*octal*/ );
size--; // ignore trailing null
longlink.resize(size);
qint64 offset = 0;
while (size > 0) {
int chunksize = qMin(size, 0x200LL);
n = dev->read( longlink.data() + offset, chunksize );
if (n == -1) return false;
size -= chunksize;
offset += 0x200;
}/*wend*/
// jump over the rest
const int skip = 0x200 - (n % 0x200);
if (skip <= 0x200) {
if (dev->read(buffer,skip) != skip)
return false;
}
return true;
}
qint64 KTar::KTarPrivate::readHeader( char *buffer, QString &name, QString &symlink ) {
name.truncate(0);
symlink.truncate(0);
while (true) {
qint64 n = readRawHeader(buffer);
if (n != 0x200) return n;
// is it a longlink?
if (strcmp(buffer,"././@LongLink") == 0) {
char typeflag = buffer[0x9c];
QByteArray longlink;
readLonglink(buffer,longlink);
switch (typeflag) {
case 'L': name = QFile::decodeName(longlink); break;
case 'K': symlink = QFile::decodeName(longlink); break;
}/*end switch*/
} else {
break;
}/*end if*/
}/*wend*/
// if not result of longlink, read names directly from the header
if (name.isEmpty())
// there are names that are exactly 100 bytes long
// and neither longlink nor \0 terminated (bug:101472)
name = QFile::decodeName(QByteArray(buffer, 100));
if (symlink.isEmpty())
symlink = QFile::decodeName(QByteArray(buffer + 0x9d /*?*/, 100));
return 0x200;
}
/*
* If we have created a temporary file, we have
* to decompress the original file now and write
* the contents to the temporary file.
*/
bool KTar::KTarPrivate::fillTempFile( const QString & fileName) {
if ( ! tmpFile )
return true;
//kDebug(7041) << "filling tmpFile of mimetype" << mimetype;
bool forced = false;
if ( QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype )
forced = true;
QIODevice *filterDev = KFilterDev::deviceForFile( fileName, mimetype, forced );
if( filterDev ) {
QFile* file = tmpFile;
Q_ASSERT(file->isOpen());
Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
file->seek(0);
QByteArray buffer;
buffer.resize(8*1024);
if ( ! filterDev->open( QIODevice::ReadOnly ) )
{
delete filterDev;
return false;
}
qint64 len = -1;
while ( !filterDev->atEnd() && len != 0 ) {
len = filterDev->read(buffer.data(),buffer.size());
if ( len < 0 ) { // corrupted archive
delete filterDev;
return false;
}
if ( file->write(buffer.data(), len) != len ) { // disk full
delete filterDev;
return false;
}
}
filterDev->close();
delete filterDev;
file->flush();
file->seek(0);
Q_ASSERT(file->isOpen());
Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
} else {
kDebug(7041) << "no filterdevice found!";
}
//kDebug( 7041 ) << "filling tmpFile finished.";
return true;
}
bool KTar::openArchive( QIODevice::OpenMode mode ) {
if ( !(mode & QIODevice::ReadOnly) )
return true;
if ( !d->fillTempFile( fileName() ) )
return false;
// We'll use the permission and user/group of d->rootDir
// for any directory we emulate (see findOrCreate)
//KDE_struct_stat buf;
//KDE_stat( fileName(), &buf );
d->dirList.clear();
QIODevice* dev = device();
if ( !dev )
return false;
// read dir information
char buffer[ 0x200 ];
bool ende = false;
do
{
QString name;
QString symlink;
// Read header
qint64 n = d->readHeader( buffer, name, symlink );
if (n < 0) return false;
if (n == 0x200)
{
bool isdir = false;
if ( name.endsWith( QLatin1Char( '/' ) ) )
{
isdir = true;
name.truncate( name.length() - 1 );
}
QByteArray prefix = QByteArray(buffer + 0x159, 155);
if (prefix[0] != '\0') {
name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name);
}
int pos = name.lastIndexOf( QLatin1Char('/') );
QString nm = ( pos == -1 ) ? name : name.mid( pos + 1 );
// read access
buffer[ 0x6b ] = 0;
char *dummy;
const char* p = buffer + 0x64;
while( *p == ' ' ) ++p;
int access = (int)strtol( p, &dummy, 8 );
// read user and group
QString user = QString::fromLocal8Bit( buffer + 0x109 );
QString group = QString::fromLocal8Bit( buffer + 0x129 );
// read time
buffer[ 0x93 ] = 0;
p = buffer + 0x88;
while( *p == ' ' ) ++p;
int time = (int)strtol( p, &dummy, 8 );
// read type flag
char typeflag = buffer[ 0x9c ];
// '0' for files, '1' hard link, '2' symlink, '5' for directory
// (and 'L' for longlink fileNames, 'K' for longlink symlink targets)
// 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring
// to the next file in the archive and 'g' for Global extended header
if ( typeflag == '5' )
isdir = true;
bool isDumpDir = false;
if ( typeflag == 'D' )
{
isdir = false;
isDumpDir = true;
}
//kDebug(7041) << nm << "isdir=" << isdir << "pos=" << dev->pos() << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' );
if (typeflag == 'x' || typeflag == 'g') { // pax extended header, or pax global extended header
// Skip it for now. TODO: implement reading of extended header, as per http://pubs.opengroup.org/onlinepubs/009695399/utilities/pax.html
(void)dev->read( buffer, 0x200 );
continue;
}
if (isdir)
access |= S_IFDIR; // f*cking broken tar files
KArchiveEntry* e;
if ( isdir )
{
//kDebug(7041) << "directory" << nm;
e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
}
else
{
// read size
QByteArray sizeBuffer( buffer + 0x7c, 12 );
qint64 size = sizeBuffer.trimmed().toLongLong( 0, 8 /*octal*/ );
//kDebug(7041) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
// for isDumpDir we will skip the additional info about that dirs contents
if ( isDumpDir )
{
//kDebug(7041) << nm << "isDumpDir";
e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
}
else
{
// Let's hack around hard links. Our classes don't support that, so make them symlinks
if ( typeflag == '1' )
{
kDebug(7041) << "Hard link, setting size to 0 instead of" << size;
size = 0; // no contents
}
//kDebug(7041) << "file" << nm << "size=" << size;
e = new KArchiveFile( this, nm, access, time, user, group, symlink,
dev->pos(), size );
}
// Skip contents + align bytes
qint64 rest = size % 0x200;
qint64 skip = size + (rest ? 0x200 - rest : 0);
//kDebug(7041) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
if (! dev->seek( dev->pos() + skip ) )
kWarning(7041) << "skipping" << skip << "failed";
}
if ( pos == -1 )
{
if (nm == QLatin1String(".")) { // special case
Q_ASSERT( isdir );
if (isdir) {
setRootDir( static_cast<KArchiveDirectory *>( e ) );
}
} else {
rootDir()->addEntry( e );
}
}
else
{
// In some tar files we can find dir/./file => call cleanPath
QString path = QDir::cleanPath( name.left( pos ) );
// Ensure container directory exists, create otherwise
KArchiveDirectory * d = findOrCreate( path );
d->addEntry( e );
}
}
else
{
//qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
d->tarEnd = dev->pos() - n; // Remember end of archive
ende = true;
}
} while( !ende );
return true;
}
/*
* Writes back the changes of the temporary file
* to the original file.
* Must only be called if in write mode, not in read mode
*/
bool KTar::KTarPrivate::writeBackTempFile( const QString & fileName )
{
if ( !tmpFile )
return true;
//kDebug(7041) << "Write temporary file to compressed file" << fileName << mimetype;
bool forced = false;
if (QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype ||
QLatin1String(application_lzma) == mimetype || QLatin1String(application_xz) == mimetype)
forced = true;
// #### TODO this should use KSaveFile to avoid problems on disk full
// (KArchive uses KSaveFile by default, but the temp-uncompressed-file trick
// circumvents that).
QIODevice *dev = KFilterDev::deviceForFile( fileName, mimetype, forced );
if( dev ) {
QFile* file = tmpFile;
if ( !dev->open(QIODevice::WriteOnly) )
{
file->close();
delete dev;
return false;
}
if ( forced )
static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName );
file->seek(0);
QByteArray buffer;
buffer.resize(8*1024);
qint64 len;
while ( !file->atEnd()) {
len = file->read(buffer.data(), buffer.size());
dev->write(buffer.data(),len); // TODO error checking
}
file->close();
dev->close();
delete dev;
}
//kDebug(7041) << "Write temporary file to compressed file done.";
return true;
}
bool KTar::closeArchive() {
d->dirList.clear();
bool ok = true;
// If we are in readwrite mode and had created
// a temporary tar file, we have to write
// back the changes to the original file
if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
ok = d->writeBackTempFile( fileName() );
delete d->tmpFile;
d->tmpFile = 0;
setDevice(0);
}
return ok;
}
bool KTar::doFinishWriting( qint64 size ) {
// Write alignment
int rest = size % 0x200;
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive
if ( rest )
{
char buffer[ 0x201 ];
for( uint i = 0; i < 0x200; ++i )
buffer[i] = 0;
qint64 nwritten = device()->write( buffer, 0x200 - rest );
return nwritten == 0x200 - rest;
}
return true;
}
/*** Some help from the tar sources
struct posix_header
{ byte offset
char name[100]; * 0 * 0x0
char mode[8]; * 100 * 0x64
char uid[8]; * 108 * 0x6c
char gid[8]; * 116 * 0x74
char size[12]; * 124 * 0x7c
char mtime[12]; * 136 * 0x88
char chksum[8]; * 148 * 0x94
char typeflag; * 156 * 0x9c
char linkname[100]; * 157 * 0x9d
char magic[6]; * 257 * 0x101
char version[2]; * 263 * 0x107
char uname[32]; * 265 * 0x109
char gname[32]; * 297 * 0x129
char devmajor[8]; * 329 * 0x149
char devminor[8]; * 337 * ...
char prefix[155]; * 345 *
* 500 *
};
*/
void KTar::KTarPrivate::fillBuffer( char * buffer,
const char * mode, qint64 size, time_t mtime, char typeflag,
const char * uname, const char * gname ) {
// mode (as in stpos())
assert( strlen(mode) == 6 );
memcpy( buffer+0x64, mode, 6 );
buffer[ 0x6a ] = ' ';
buffer[ 0x6b ] = '\0';
// dummy uid
strcpy( buffer + 0x6c, " 765 ");
// dummy gid
strcpy( buffer + 0x74, " 144 ");
// size
QByteArray s = QByteArray::number( size, 8 ); // octal
s = s.rightJustified( 11, '0' );
memcpy( buffer + 0x7c, s.data(), 11 );
buffer[ 0x87 ] = ' '; // space-terminate (no null after)
// modification time
s = QByteArray::number( static_cast<qulonglong>(mtime), 8 ); // octal
s = s.rightJustified( 11, '0' );
memcpy( buffer + 0x88, s.data(), 11 );
buffer[ 0x93 ] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
// spaces, replaced by the check sum later
buffer[ 0x94 ] = 0x20;
buffer[ 0x95 ] = 0x20;
buffer[ 0x96 ] = 0x20;
buffer[ 0x97 ] = 0x20;
buffer[ 0x98 ] = 0x20;
buffer[ 0x99 ] = 0x20;
/* From the tar sources :
Fill in the checksum field. It's formatted differently from the
other fields: it has [6] digits, a null, then a space -- rather than
digits, a space, then a null. */
buffer[ 0x9a ] = '\0';
buffer[ 0x9b ] = ' ';
// type flag (dir, file, link)
buffer[ 0x9c ] = typeflag;
// magic + version
strcpy( buffer + 0x101, "ustar");
strcpy( buffer + 0x107, "00" );
// user
strcpy( buffer + 0x109, uname );
// group
strcpy( buffer + 0x129, gname );
// Header check sum
int check = 32;
for( uint j = 0; j < 0x200; ++j )
check += buffer[j];
s = QByteArray::number( check, 8 ); // octal
s = s.rightJustified( 6, '0' );
memcpy( buffer + 0x94, s.constData(), 6 );
}
void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag,
const char *uname, const char *gname) {
strcpy( buffer, "././@LongLink" );
qint64 namelen = name.length() + 1;
fillBuffer( buffer, " 0", namelen, 0, typeflag, uname, gname );
q->device()->write( buffer, 0x200 ); // TODO error checking
qint64 offset = 0;
while (namelen > 0) {
int chunksize = qMin(namelen, 0x200LL);
memcpy(buffer, name.data()+offset, chunksize);
// write long name
q->device()->write( buffer, 0x200 ); // TODO error checking
// not even needed to reclear the buffer, tar doesn't do it
namelen -= chunksize;
offset += 0x200;
}/*wend*/
}
bool KTar::doPrepareWriting(const QString &name, const QString &user,
const QString &group, qint64 size, mode_t perm,
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
if ( !isOpen() )
{
kWarning(7041) << "You must open the tar file before writing to it\n";
return false;
}
if ( !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "You must open the tar file for writing\n";
return false;
}
// In some tar files we can find dir/./file => call cleanPath
QString fileName ( QDir::cleanPath( name ) );
/*
// Create toplevel dirs
// Commented out by David since it's not necessary, and if anybody thinks it is,
// he needs to implement a findOrCreate equivalent in writeDir.
// But as KTar and the "tar" program both handle tar files without
// dir entries, there's really no need for that
QString tmp ( fileName );
int i = tmp.lastIndexOf( '/' );
if ( i != -1 )
{
QString d = tmp.left( i + 1 ); // contains trailing slash
if ( !m_dirList.contains( d ) )
{
tmp = tmp.mid( i + 1 );
writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
}
}
*/
char buffer[ 0x201 ];
memset( buffer, 0, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
// provide converted stuff we need later on
const QByteArray encodedFileName = QFile::encodeName(fileName);
const QByteArray uname = user.toLocal8Bit();
const QByteArray gname = group.toLocal8Bit();
// If more than 100 chars, we need to use the LongLink trick
if ( fileName.length() > 99 )
d->writeLonglink(buffer,encodedFileName,'L',uname,gname);
// Write (potentially truncated) name
strncpy( buffer, encodedFileName, 99 );
buffer[99] = 0;
// zero out the rest (except for what gets filled anyways)
memset(buffer+0x9d, 0, 0x200 - 0x9d);
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
permstr = permstr.rightJustified(6, '0');
d->fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname);
// Write header
return device()->write( buffer, 0x200 ) == 0x200;
}
bool KTar::doWriteDir(const QString &name, const QString &user,
const QString &group, mode_t perm,
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
if ( !isOpen() )
{
kWarning(7041) << "You must open the tar file before writing to it\n";
return false;
}
if ( !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "You must open the tar file for writing\n";
return false;
}
// In some tar files we can find dir/./ => call cleanPath
QString dirName ( QDir::cleanPath( name ) );
// Need trailing '/'
if ( !dirName.endsWith( QLatin1Char( '/' ) ) )
dirName += QLatin1Char( '/' );
if ( d->dirList.contains( dirName ) )
return true; // already there
char buffer[ 0x201 ];
memset( buffer, 0, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
// provide converted stuff we need lateron
QByteArray encodedDirname = QFile::encodeName(dirName);
QByteArray uname = user.toLocal8Bit();
QByteArray gname = group.toLocal8Bit();
// If more than 100 chars, we need to use the LongLink trick
if ( dirName.length() > 99 )
d->writeLonglink(buffer,encodedDirname,'L',uname,gname);
// Write (potentially truncated) name
strncpy( buffer, encodedDirname, 99 );
buffer[99] = 0;
// zero out the rest (except for what gets filled anyways)
memset(buffer+0x9d, 0, 0x200 - 0x9d);
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
permstr = permstr.rightJustified(6, ' ');
d->fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname);
// Write header
device()->write( buffer, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
d->tarEnd = device()->pos();
d->dirList.append( dirName ); // contains trailing slash
return true; // TODO if wanted, better error control
}
bool KTar::doWriteSymLink(const QString &name, const QString &target,
const QString &user, const QString &group,
mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
if ( !isOpen() )
{
kWarning(7041) << "You must open the tar file before writing to it\n";
return false;
}
if ( !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "You must open the tar file for writing\n";
return false;
}
// In some tar files we can find dir/./file => call cleanPath
QString fileName ( QDir::cleanPath( name ) );
char buffer[ 0x201 ];
memset( buffer, 0, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
// provide converted stuff we need lateron
QByteArray encodedFileName = QFile::encodeName(fileName);
QByteArray encodedTarget = QFile::encodeName(target);
QByteArray uname = user.toLocal8Bit();
QByteArray gname = group.toLocal8Bit();
// If more than 100 chars, we need to use the LongLink trick
if (target.length() > 99)
d->writeLonglink(buffer,encodedTarget,'K',uname,gname);
if ( fileName.length() > 99 )
d->writeLonglink(buffer,encodedFileName,'L',uname,gname);
// Write (potentially truncated) name
strncpy( buffer, encodedFileName, 99 );
buffer[99] = 0;
// Write (potentially truncated) symlink target
strncpy(buffer+0x9d, encodedTarget, 99);
buffer[0x9d+99] = 0;
// zero out the rest
memset(buffer+0x9d+100, 0, 0x200 - 100 - 0x9d);
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
permstr = permstr.rightJustified(6, ' ');
d->fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname);
// Write header
bool retval = device()->write( buffer, 0x200 ) == 0x200;
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
d->tarEnd = device()->pos();
return retval;
}

View file

@ -1,105 +0,0 @@
/* 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>
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.
*/
#ifndef KTAR_H
#define KTAR_H
#include <karchive.h>
/**
* A class for reading / writing (optionally compressed) tar archives.
*
* KTar allows you to read and write tar archives, including those
* that are compressed using gzip, bzip2 or xz.
*
* @author Torben Weis <weis@kde.org>, David Faure <faure@kde.org>
*/
class KDECORE_EXPORT KTar : public KArchive
{
public:
/**
* Creates an instance that operates on the given filename
* using the compression filter associated to given mimetype.
*
* @param filename is a local path (e.g. "/home/weis/myfile.tgz")
* @param mimetype "application/x-gzip", "application/x-bzip" or
* "application/x-xz"
* Do not use application/x-compressed-tar or similar - you only need to
* specify the compression layer ! If the mimetype is omitted, it
* will be determined from the filename.
*/
explicit KTar( const QString& filename,
const QString& mimetype = QString() );
/**
* Creates an instance that operates on the given device.
* The device can be compressed (KFilterDev) or not (QFile, etc.).
* @warning Do not assume that giving a QFile here will decompress the file,
* in case it's compressed!
* @param dev the device to read from. If the source is compressed, the
* QIODevice must take care of decompression
*/
explicit KTar( QIODevice * dev );
/**
* If the tar ball is still opened, then it will be
* closed automatically by the destructor.
*/
virtual ~KTar();
/**
* Special function for setting the "original file name" in the gzip header,
* when writing a tar.gz file. It appears when using in the "file" command,
* for instance. Should only be called if the underlying device is a KFilterDev!
* @param fileName the original file name
*/
void setOrigFileName( const QByteArray & fileName );
protected:
/// Reimplemented from KArchive
virtual bool doWriteSymLink(const QString &name, const QString &target,
const QString &user, const QString &group,
mode_t perm, time_t atime, time_t mtime, time_t ctime);
/// Reimplemented from KArchive
virtual bool doWriteDir( const QString& name, const QString& user, const QString& group,
mode_t perm, time_t atime, time_t mtime, time_t ctime );
/// Reimplemented from KArchive
virtual bool doPrepareWriting( const QString& name, const QString& user,
const QString& group, qint64 size, mode_t perm,
time_t atime, time_t mtime, time_t ctime );
/// Reimplemented from KArchive
virtual bool doFinishWriting( qint64 size );
/**
* Opens the archive for reading.
* Parses the directory listing of the archive
* and creates the KArchiveDirectory/KArchiveFile entries.
* @param mode the mode of the file
*/
virtual bool openArchive( QIODevice::OpenMode mode );
virtual bool closeArchive();
virtual bool createDevice( QIODevice::OpenMode mode );
private:
class KTarPrivate;
KTarPrivate* const d;
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,220 +0,0 @@
/* This file is part of the KDE libraries
Copyright (C) 2002 Holger Schroeder <holger-kde@holgis.net>
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.
*/
#ifndef KZIP_H
#define KZIP_H
#include <karchive.h>
class KZipFileEntry;
/**
* A class for reading / writing zip archives.
*
* You can use it in QIODevice::ReadOnly or in QIODevice::WriteOnly mode, and it
* behaves just as expected.
* It can also be used in QIODevice::ReadWrite mode, in this case one can
* append files to an existing zip archive. When you append new files, which
* are not yet in the zip, it works as expected, i.e. the files are appended at the end.
* When you append a file, which is already in the file, the reference to the
* old file is dropped and the new one is added to the zip - but the
* old data from the file itself is not deleted, it is still in the
* zipfile. So when you want to have a small and garbage-free zipfile,
* just read the contents of the appended zip file and write it to a new one
* in QIODevice::WriteOnly mode. This is especially important when you don't want
* to leak information of how intermediate versions of files in the zip
* were looking.
*
* For more information on the zip fileformat go to
* http://www.pkware.com/products/enterprise/white_papers/appnote.html
* @author Holger Schroeder <holger-kde@holgis.net>
*/
class KDECORE_EXPORT KZip : public KArchive
{
public:
/**
* Creates an instance that operates on the given filename.
* using the compression filter associated to given mimetype.
*
* @param filename is a local path (e.g. "/home/holger/myfile.zip")
*/
KZip( const QString& filename );
/**
* Creates an instance that operates on the given device.
* The device can be compressed (KFilterDev) or not (QFile, etc.).
* @warning Do not assume that giving a QFile here will decompress the file,
* in case it's compressed!
* @param dev the device to access
*/
KZip( QIODevice * dev );
/**
* If the zip file is still opened, then it will be
* closed automatically by the destructor.
*/
virtual ~KZip();
/**
* Describes the contents of the "extra field" for a given file in the Zip archive.
*/
enum ExtraField { NoExtraField = 0, ///< No extra field
ModificationTime = 1, ///< Modification time ("extended timestamp" header)
DefaultExtraField = 1
};
/**
* Call this before writeFile or prepareWriting, to define what the next
* file to be written should have in its extra field.
* @param ef the type of "extra field"
* @see extraField()
*/
void setExtraField( ExtraField ef );
/**
* The current type of "extra field" that will be used for new files.
* @return the current type of "extra field"
* @see setExtraField()
*/
ExtraField extraField() const;
/**
* Describes the compression type for a given file in the Zip archive.
*/
enum Compression { NoCompression = 0, ///< Uncompressed.
DeflateCompression = 1 ///< Deflate compression method.
};
/**
* Call this before writeFile or prepareWriting, to define whether the next
* files to be written should be compressed or not.
* @param c the new compression mode
* @see compression()
*/
void setCompression( Compression c );
/**
* The current compression mode that will be used for new files.
* @return the current compression mode
* @see setCompression()
*/
Compression compression() const;
/**
* Write data to a file that has been created using prepareWriting().
* @param data a pointer to the data
* @param size the size of the chunk
* @return true if successful, false otherwise
*/
virtual bool writeData( const char* data, qint64 size );
protected:
/// Reimplemented from KArchive
virtual bool doWriteSymLink(const QString &name, const QString &target,
const QString &user, const QString &group,
mode_t perm, time_t atime, time_t mtime, time_t ctime);
/// Reimplemented from KArchive
virtual bool doPrepareWriting( const QString& name, const QString& user,
const QString& group, qint64 size, mode_t perm,
time_t atime, time_t mtime, time_t ctime );
/**
* Write data to a file that has been created using prepareWriting().
* @param size the size of the file
* @return true if successful, false otherwise
*/
virtual bool doFinishWriting( qint64 size );
/**
* Opens the archive for reading.
* Parses the directory listing of the archive
* and creates the KArchiveDirectory/KArchiveFile entries.
* @param mode the mode of the file
*/
virtual bool openArchive( QIODevice::OpenMode mode );
/// Closes the archive
virtual bool closeArchive();
/// Reimplemented from KArchive
virtual bool doWriteDir( const QString& name, const QString& user,
const QString& group, mode_t perm, time_t atime,
time_t mtime, time_t ctime );
private:
class KZipPrivate;
KZipPrivate * const d;
};
/**
* A KZipFileEntry represents an file in a zip archive.
*/
class KDECORE_EXPORT KZipFileEntry : public KArchiveFile
{
public:
/**
* Creates a new zip file entry. Do not call this, KZip takes care of it.
*/
KZipFileEntry( KZip* zip, const QString& name, int access, int date,
const QString& user, const QString& group, const QString& symlink,
const QString& path, qint64 start, qint64 uncompressedSize,
int encoding, qint64 compressedSize);
/**
* Destructor. Do not call this.
*/
~KZipFileEntry();
int encoding() const;
qint64 compressedSize() const;
/// Only used when writing
void setCompressedSize(qint64 compressedSize);
/// Header start: only used when writing
void setHeaderStart(qint64 headerstart);
qint64 headerStart() const;
/// CRC: only used when writing
unsigned long crc32() const;
void setCRC32(unsigned long crc32);
/// Name with complete path - KArchiveFile::name() is the filename only (no path)
const QString &path() const;
/**
* @return the content of this file.
* Call data() with care (only once per file), this data isn't cached.
*/
virtual QByteArray data() const;
/**
* This method returns a QIODevice to read the file contents.
* This is obviously for reading only.
* Note that the ownership of the device is being transferred to the caller,
* who will have to delete it.
* The returned device auto-opens (in readonly mode), no need to open it.
*/
virtual QIODevice* createDevice() const;
private:
class KZipFileEntryPrivate;
KZipFileEntryPrivate * const d;
};
#endif

View file

@ -138,9 +138,6 @@
7031 KImageIO
7032 kio (KUrlCompletion)
7033 KFileMetaInfo
7040 KZip
7041 KTar
7042 KAr
7043 kio (bookmarks)
7044 kio (AccessManager)
@ -193,6 +190,9 @@
# kspeech
51008 kspeech
# karchive
51009 karchive
# kdemultimedia
67100 kmix

View file

@ -18,9 +18,6 @@ ENDMACRO(KDECORE_EXECUTABLE_TESTS)
########### next target ###############
KDECORE_UNIT_TESTS(
# FIXME: due to issues with kgzipfilter the test bellow is disabled as it
# causes a hang
# karchivetest
klocaletimeformattest
klocalizedstringtest
kmountpointtest
@ -65,8 +62,6 @@ KDECORE_UNIT_TESTS(
)
KDECORE_EXECUTABLE_TESTS(
ktartest
kziptest
kdebugtest
kcmdlineargstest
dbuscalltest
@ -95,11 +90,6 @@ set(kdatetimeformattertest_SRCS kdatetimeformattertest.cpp ../date/kdatetimeform
kde4_add_test(kdecore-kdatetimeformattertest ${kdatetimeformattertest_SRCS})
target_link_libraries(kdecore-kdatetimeformattertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### klimitediodevicetest ###############
kde4_add_test(kdecore-klimitediodevicetest klimitediodevicetest.cpp ../io/klimitediodevice.cpp)
target_link_libraries(kdecore-klimitediodevicetest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
########### kmimetypetest ###############
# compile kmimemagicrule.cpp into the test since it's not exported and we call match().

View file

@ -1,933 +0,0 @@
/* This file is part of the KDE project
Copyright (C) 2006, 2010 David Faure <faure@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 as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <config-compression.h>
#include "karchivetest.h"
#include <kmimetype.h>
#include "moc_karchivetest.cpp"
#include <ktar.h>
#include <kzip.h>
#include <qtest_kde.h>
#include <QtCore/QFileInfo>
#include <kdebug.h>
#include <kfilterdev.h>
#include <ktempdir.h>
#include <unistd.h> // symlink
#include <errno.h>
QTEST_KDEMAIN_CORE( KArchiveTest )
static const int SIZE1 = 100;
/**
* Writes test fileset specified archive
* @param archive archive
*/
static void writeTestFilesToArchive( KArchive* archive )
{
QVERIFY( archive->writeFile( "empty", "weis", "users", "", 0 ) );
QVERIFY( archive->writeFile( "test1", "weis", "users", "Hallo", 5, 0100440 ) );
// Now let's try with the prepareWriting/writeData/finishWriting API
QVERIFY( archive->prepareWriting( "test2", "weis", "users", 8 ) );
QVERIFY( archive->writeData( "Hallo ", 6 ) );
QVERIFY( archive->writeData( "Du", 2 ) );
QVERIFY( archive->finishWriting( 8 ) );
// Add local file
QFile localFile( "test3" );
QVERIFY( localFile.open( QIODevice::WriteOnly ) );
QVERIFY( localFile.write( "Noch so einer", 13 ) == 13 );
localFile.close();
QVERIFY( archive->addLocalFile( "test3", "z/test3" ) );
// writeFile API
QVERIFY( archive->writeFile( "my/dir/test3", "dfaure", "hackers", "I do not speak German\nDavid.", 29 ) );
// Now a medium file : 100 null bytes
char medium[ SIZE1 ];
memset( medium, 0, SIZE1 );
QVERIFY( archive->writeFile( "mediumfile", "user", "group", medium, SIZE1 ) );
// Another one, with an absolute path
QVERIFY( archive->writeFile( "/dir/subdir/mediumfile2", "user", "group", medium, SIZE1 ) );
// Now a huge file : 20000 null bytes
int n = 20000;
char * huge = new char[ n ];
memset( huge, 0, n );
QVERIFY( archive->writeFile( "hugefile", "user", "group", huge, n ) );
delete [] huge;
// Now an empty directory
QVERIFY( archive->writeDir( "aaaemptydir", "user", "group" ) );
// Add local symlink
QVERIFY( archive->addLocalFile( "test3_symlink", "z/test3_symlink") );
}
enum { WithUserGroup = 1 }; // ListingFlags
static QStringList recursiveListEntries( const KArchiveDirectory * dir, const QString & path, int listingFlags )
{
QStringList ret;
QStringList l = dir->entries();
l.sort();
Q_FOREACH(const QString& it, l) {
const KArchiveEntry* entry = dir->entry(it);
QString descr;
descr += QString("mode=") + QString::number( entry->permissions(), 8 ) + ' ';
if ( listingFlags & WithUserGroup )
{
descr += QString("user=") + entry->user() + ' ';
descr += QString("group=") + entry->group() + ' ';
}
descr += QString("path=") + path+(it) + ' ';
descr += QString("type=") + ( entry->isDirectory() ? "dir" : "file" );
if ( entry->isFile() )
descr += QString(" size=") + QString::number( static_cast<const KArchiveFile *>(entry)->size() );
if (!entry->symLinkTarget().isEmpty())
descr += QString(" symlink=") + entry->symLinkTarget();
// TODO add date and time
//kDebug() << descr;
ret.append( descr );
if (entry->isDirectory())
ret += recursiveListEntries( (KArchiveDirectory *)entry, path+it+'/', listingFlags );
}
return ret;
}
/**
* Verifies contents of specified archive against test fileset
* @param archive archive
*/
static void testFileData( KArchive* archive )
{
const KArchiveDirectory* dir = archive->directory();
const KArchiveEntry* e = dir->entry( "z/test3" );
QVERIFY( e );
QVERIFY( e->isFile() );
const KArchiveFile* f = static_cast<const KArchiveFile*>( e );
QByteArray arr( f->data() );
QCOMPARE( arr.size(), 13 );
QCOMPARE( arr, QByteArray( "Noch so einer" ) );
// Now test using createDevice()
QIODevice *dev = f->createDevice();
QByteArray contents = dev->readAll();
QCOMPARE( contents, arr );
delete dev;
dev = f->createDevice();
contents = dev->read(5); // test reading in two chunks
QCOMPARE(contents.size(), 5);
contents += dev->read(50);
QCOMPARE(contents.size(), 13);
QCOMPARE( QString::fromLatin1(contents), QString::fromLatin1(arr) );
delete dev;
e = dir->entry( "mediumfile" );
QVERIFY( e && e->isFile() );
f = (KArchiveFile*)e;
QCOMPARE( f->data().size(), SIZE1 );
e = dir->entry( "hugefile" );
QVERIFY( e && e->isFile() );
f = (KArchiveFile*)e;
QCOMPARE( f->data().size(), 20000 );
e = dir->entry( "aaaemptydir" );
QVERIFY( e && e->isDirectory() );
e = dir->entry( "my/dir/test3" );
QVERIFY( e && e->isFile() );
f = (KArchiveFile*)e;
dev = f->createDevice();
QByteArray firstLine = dev->readLine();
QCOMPARE(QString::fromLatin1(firstLine), QString::fromLatin1("I do not speak German\n"));
QByteArray secondLine = dev->read(100);
QCOMPARE(QString::fromLatin1(secondLine), QString::fromLatin1("David."));
delete dev;
e = dir->entry( "z/test3_symlink" );
QVERIFY(e);
QVERIFY(e->isFile());
QCOMPARE(e->symLinkTarget(), QString("test3"));
// Test "./" prefix for KOffice (xlink:href="./ObjectReplacements/Object 1")
e = dir->entry( "./hugefile" );
QVERIFY( e && e->isFile() );
e = dir->entry( "./my/dir/test3" );
QVERIFY( e && e->isFile() );
// Test directory entries
e = dir->entry( "my" );
QVERIFY(e && e->isDirectory());
e = dir->entry( "my/" );
QVERIFY(e && e->isDirectory());
e = dir->entry( "./my/" );
QVERIFY(e && e->isDirectory());
}
static void testReadWrite( KArchive* archive )
{
QVERIFY(archive->writeFile("newfile", "dfaure", "users", "New File", 8, 0100440));
}
static void testCopyTo( KArchive* archive )
{
const KArchiveDirectory* dir = archive->directory();
KTempDir tmpDir;
const QString dirName = tmpDir.name();
dir->copyTo( dirName );
QVERIFY(QFile::exists(dirName+"dir"));
QVERIFY(QFileInfo(dirName+"dir").isDir());
QFileInfo fileInfo1(dirName+"dir/subdir/mediumfile2");
QVERIFY(fileInfo1.exists());
QVERIFY(fileInfo1.isFile());
QCOMPARE(fileInfo1.size(), Q_INT64_C(100));
QFileInfo fileInfo2(dirName+"hugefile");
QVERIFY(fileInfo2.exists());
QVERIFY(fileInfo2.isFile());
QCOMPARE(fileInfo2.size(), Q_INT64_C(20000));
QFileInfo fileInfo3(dirName+"mediumfile");
QVERIFY(fileInfo3.exists());
QVERIFY(fileInfo3.isFile());
QCOMPARE(fileInfo3.size(), Q_INT64_C(100));
QFileInfo fileInfo4(dirName+"my/dir/test3");
QVERIFY(fileInfo4.exists());
QVERIFY(fileInfo4.isFile());
QCOMPARE(fileInfo4.size(), Q_INT64_C(29));
const QString fileName = dirName+"z/test3_symlink";
const QFileInfo fileInfo5(fileName);
QVERIFY(fileInfo5.exists());
QVERIFY(fileInfo5.isFile());
// Do not use fileInfo.readLink() for unix symlinks
// It returns the -full- path to the target, while we want the target string "as is".
QString symLinkTarget;
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);
}
QCOMPARE(symLinkTarget, QString("test3"));
}
/**
* Prepares dataset for archive filter tests
*/
void KArchiveTest::setupData()
{
QTest::addColumn<QString>("fileName");
QTest::addColumn<QString>("mimeType");
QTest::newRow(".tar.gz") << "karchivetest.tar.gz" << "application/x-gzip";
#if HAVE_BZIP2_SUPPORT
QTest::newRow(".tar.bz2") << "karchivetest.tar.bz2" << "application/x-bzip";
#endif
#if HAVE_XZ_SUPPORT
QTest::newRow(".tar.lzma") << "karchivetest.tar.lzma" << "application/x-lzma";
QTest::newRow(".tar.xz") << "karchivetest.tar.xz" << "application/x-xz";
#endif
}
/**
* @see QTest::initTestCase()
*/
void KArchiveTest::initTestCase()
{
// Prepare local symlink
QFile::remove("test3_symlink");
if (::symlink("test3", "test3_symlink") != 0) {
qDebug() << errno;
QVERIFY(false);
}
// For better benchmarks: initialize KMimeTypeFactory magic here
KMimeType::findByContent(QByteArray("hello"));
}
void KArchiveTest::testCreateTar_data()
{
QTest::addColumn<QString>("fileName");
QTest::newRow(".tar") << "karchivetest.tar";
}
/**
* @dataProvider testCreateTar_data
*/
void KArchiveTest::testCreateTar()
{
QFETCH(QString, fileName);
// With tempfile: 0.7-0.8 ms, 994236 instr. loads
// Without tempfile: 0.81 ms, 992541 instr. loads
// Note: use ./karchivetest 2>&1 | grep ms
// to avoid being slowed down by the kDebugs.
QBENCHMARK {
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::WriteOnly));
writeTestFilesToArchive(&tar);
QVERIFY(tar.close());
QFileInfo fileInfo(QFile::encodeName(fileName));
QVERIFY(fileInfo.exists());
// We can't check for an exact size because of the addLocalFile, whose data is system-dependent
QVERIFY(fileInfo.size() > 450);
}
// NOTE The only .tar test, cleanup here
QFile::remove(fileName);
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testCreateTarXXX()
{
QFETCH(QString, fileName);
// With tempfile: 1.3-1.7 ms, 2555089 instr. loads
// Without tempfile: 0.87 ms, 987915 instr. loads
QBENCHMARK {
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::WriteOnly));
writeTestFilesToArchive(&tar);
QVERIFY(tar.close());
QFileInfo fileInfo(QFile::encodeName(fileName));
QVERIFY(fileInfo.exists());
// We can't check for an exact size because of the addLocalFile, whose data is system-dependent
QVERIFY(fileInfo.size() > 350);
}
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testReadTar() // testCreateTarGz must have been run first.
{
QFETCH( QString, fileName );
// 1.6-1.7 ms per interaction, 2908428 instruction loads
// After the "no tempfile when writing fix" this went down
// to 0.9-1.0 ms, 1689059 instruction loads.
// I guess it finds the data in the kernel cache now that no KTempFile is
// used when writing.
QBENCHMARK {
KTar tar( fileName );
QVERIFY( tar.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
QFileInfo localFileData("test3");
const QString owner = localFileData.owner();
const QString group = localFileData.group();
QCOMPARE( listing.count(), 15 );
QCOMPARE( listing[ 0], QString("mode=40755 user=user group=group path=aaaemptydir type=dir") );
QCOMPARE( listing[ 1], QString("mode=40777 user=%1 group=%2 path=dir type=dir").arg(owner, group) );
QCOMPARE( listing[ 2], QString("mode=40777 user=%1 group=%2 path=dir/subdir type=dir").arg(owner,group) );
QCOMPARE( listing[ 3], QString("mode=100644 user=user group=group path=dir/subdir/mediumfile2 type=file size=100") );
QCOMPARE( listing[ 4], QString("mode=100644 user=weis group=users path=empty type=file size=0") );
QCOMPARE( listing[ 5], QString("mode=100644 user=user group=group path=hugefile type=file size=20000") );
QCOMPARE( listing[ 6], QString("mode=100644 user=user group=group path=mediumfile type=file size=100") );
QCOMPARE( listing[ 7], QString("mode=40777 user=%1 group=%2 path=my type=dir").arg(owner, group) );
QCOMPARE( listing[ 8], QString("mode=40777 user=%1 group=%2 path=my/dir type=dir").arg(owner, group) );
QCOMPARE( listing[ 9], QString("mode=100644 user=dfaure group=hackers path=my/dir/test3 type=file size=29") );
QCOMPARE( listing[10], QString("mode=100440 user=weis group=users path=test1 type=file size=5") );
QCOMPARE( listing[11], QString("mode=100644 user=weis group=users path=test2 type=file size=8") );
QCOMPARE( listing[12], QString("mode=40777 user=%1 group=%2 path=z type=dir").arg(owner, group) );
// This one was added with addLocalFile, so ignore mode/user/group.
QString str = listing[13];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3 type=file size=13") );
str = listing[14];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3_symlink type=file size=0 symlink=test3") );
QVERIFY( tar.close() );
}
}
/**
* This tests the decompression using kfilterdev, basically.
* To debug KTarPrivate::fillTempFile().
*
* @dataProvider setupData
*/
void KArchiveTest::testUncompress()
{
QFETCH(QString, fileName);
QFETCH(QString, mimeType);
// testCreateTar must have been run first.
QVERIFY(QFile::exists(fileName));
QIODevice *filterDev = KFilterDev::deviceForFile(fileName, mimeType, true);
QVERIFY(filterDev);
QByteArray buffer;
buffer.resize(8*1024);
kDebug() << "buffer.size()=" << buffer.size();
QVERIFY(filterDev->open(QIODevice::ReadOnly));
qint64 totalSize = 0;
qint64 len = -1;
while (!filterDev->atEnd() && len != 0) {
len = filterDev->read(buffer.data(), buffer.size());
QVERIFY(len >= 0);
totalSize += len;
// kDebug() << "read len=" << len << " totalSize=" << totalSize;
}
filterDev->close();
delete filterDev;
// kDebug() << "totalSize=" << totalSize;
QVERIFY(totalSize > 26000); // 27648 here when using gunzip
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testTarFileData()
{
QFETCH(QString, fileName);
// testCreateTar must have been run first.
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadOnly));
testFileData(&tar);
QVERIFY(tar.close());
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testTarCopyTo()
{
QFETCH(QString, fileName);
// testCreateTar must have been run first.
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadOnly));
testCopyTo(&tar);
QVERIFY(tar.close());
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testTarReadWrite()
{
QFETCH(QString, fileName);
// testCreateTar must have been run first.
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadWrite));
testReadWrite(&tar);
testFileData(&tar);
QVERIFY(tar.close());
// Reopen it and check it
{
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadOnly));
testFileData( &tar );
const KArchiveDirectory* dir = tar.directory();
const KArchiveEntry* e = dir->entry("newfile");
QVERIFY(e && e->isFile());
const KArchiveFile* f = (KArchiveFile*)e;
QCOMPARE(f->data().size(), 8);
}
// NOTE This is the last test for this dataset. so cleanup here
QFile::remove(fileName);
}
void KArchiveTest::testTarMaxLength_data()
{
QTest::addColumn<QString>("fileName");
QTest::newRow("maxlength.tar.gz") << "karchivetest-maxlength.tar.gz";
}
/**
* @dataProvider testTarMaxLength_data
*/
void KArchiveTest::testTarMaxLength()
{
QFETCH( QString, fileName );
KTar tar( fileName );
QVERIFY( tar.open( QIODevice::WriteOnly ) );
// Generate long filenames of each possible length bigger than 98...
// Also exceed 512 byte block size limit to see how well the ././@LongLink
// implementation fares
for (int i = 98; i < 514 ; i++ )
{
QString str, num;
str.fill( 'a', i-10 );
num.setNum( i );
num = num.rightJustified( 10, '0' );
tar.writeFile( str+num, "testu", "testg", "hum", 3 );
}
// Result of this test : works perfectly now (failed at 482 formerly and
// before that at 154).
QVERIFY( tar.close() );
QVERIFY( tar.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
QCOMPARE( listing[ 0], QString("mode=100644 user=testu group=testg path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3") );
QCOMPARE( listing[ 3], QString("mode=100644 user=testu group=testg path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3") );
QCOMPARE( listing[ 4], QString("mode=100644 user=testu group=testg path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3") );
QCOMPARE( listing.count(), 416 );
QVERIFY( tar.close() );
// NOTE Cleanup here
QFile::remove( fileName );
}
void KArchiveTest::testTarGlobalHeader()
{
KTar tar( QString::fromLatin1(KDESRCDIR) + QLatin1String( "global_header_test.tar.bz2" ) );
QVERIFY( tar.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
QCOMPARE( listing[ 0], QString("mode=40775 user=root group=root path=Test type=dir") );
QCOMPARE( listing[ 1], QString("mode=664 user=root group=root path=Test/test.txt type=file size=0") );
QCOMPARE( listing.count(), 2 );
QVERIFY( tar.close() );
}
void KArchiveTest::testTarPrefix()
{
KTar tar( QString::fromLatin1(KDESRCDIR) + QLatin1String( "tar_prefix_test.tar.bz2" ) );
QVERIFY( tar.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
QCOMPARE( listing[ 0], QString("mode=40775 user=root group=root path=Test type=dir") );
QCOMPARE( listing[ 1], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7 type=dir") );
QCOMPARE( listing[ 2], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples type=dir") );
QCOMPARE( listing[ 3], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator type=dir") );
QCOMPARE( listing[ 4], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original type=dir") );
QCOMPARE( listing[ 5], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java type=dir") );
QCOMPARE( listing[ 6], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com type=dir") );
QCOMPARE( listing[ 7], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech type=dir") );
QCOMPARE( listing[ 8], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples type=dir") );
QCOMPARE( listing[ 9], QString("mode=664 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples/GeneratorExample.html type=file size=43086") );
QCOMPARE( listing.count(), 10 );
QVERIFY( tar.close() );
}
void KArchiveTest::testTarDirectoryForgotten()
{
KTar tar( QString::fromLatin1(KDESRCDIR) + QLatin1String( "tar_directory_forgotten.tar.bz2" ) );
QVERIFY( tar.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
QVERIFY( listing[9].contains("trolltech/examples/generator") );
QVERIFY( listing[10].contains("trolltech/examples/generator/GeneratorExample.html") );
QCOMPARE( listing.count(), 11 );
QVERIFY( tar.close() );
}
void KArchiveTest::testTarRootDir() // bug 309463
{
KTar tar(QString::fromLatin1(KDESRCDIR) + QLatin1String("tar_rootdir.tar.bz2"));
QVERIFY(tar.open(QIODevice::ReadOnly));
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries(dir, "", WithUserGroup);
qDebug() << listing.join("\n");
QVERIFY(listing[0].contains("%{APPNAME}.cpp"));
QVERIFY(listing[1].contains("%{APPNAME}.h"));
QVERIFY(listing[5].contains("main.cpp"));
QCOMPARE(listing.count(), 10);
}
void KArchiveTest::testTarDirectoryTwice() // bug 206994
{
KTar tar(QString::fromLatin1(KDESRCDIR) + QLatin1String("tar_directory_twice.tar.bz2"));
QVERIFY(tar.open(QIODevice::ReadOnly));
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries(dir, "", WithUserGroup);
qDebug() << listing.join("\n");
QVERIFY(listing[0].contains("path=d"));
QVERIFY(listing[1].contains("path=d/f1.txt"));
QVERIFY(listing[2].contains("path=d/f2.txt"));
QCOMPARE(listing.count(), 3);
}
///
static const char s_zipFileName[] = "karchivetest.zip";
static const char s_zipMaxLengthFileName[] = "karchivetest-maxlength.zip";
static const char s_zipLocaleFileName[] = "karchivetest-locale.zip";
static const char s_zipMimeType[] = "application/vnd.oasis.opendocument.text";
void KArchiveTest::testCreateZip()
{
KZip zip( s_zipFileName );
QVERIFY( zip.open( QIODevice::WriteOnly ) );
zip.setExtraField( KZip::NoExtraField );
zip.setCompression( KZip::NoCompression );
QByteArray zipMimeType( s_zipMimeType );
zip.writeFile( "mimetype", "", "", zipMimeType.data(), zipMimeType.size() );
zip.setCompression( KZip::DeflateCompression );
writeTestFilesToArchive( &zip );
QVERIFY( zip.close() );
QFile zipFile( QFile::encodeName( s_zipFileName ) );
QFileInfo fileInfo( zipFile );
QVERIFY( fileInfo.exists() );
QVERIFY( fileInfo.size() > 300 );
// Check that the header with no-compression and no-extrafield worked.
// (This is for the "magic" for koffice documents)
QVERIFY( zipFile.open( QIODevice::ReadOnly ) );
QByteArray arr = zipFile.read( 4 );
QCOMPARE( arr, QByteArray( "PK\003\004" ) );
QVERIFY( zipFile.seek( 30 ) );
arr = zipFile.read( 8 );
QCOMPARE( arr, QByteArray( "mimetype" ) );
arr = zipFile.read( zipMimeType.size() );
QCOMPARE( arr, zipMimeType );
}
void KArchiveTest::testCreateZipError()
{
// Giving a directory name to kzip must give an error case in close(), see #136630.
// Otherwise we just lose data.
KZip zip(QDir::currentPath());
QVERIFY(zip.open(QIODevice::WriteOnly));
writeTestFilesToArchive(&zip);
// try to add something as a file that is no file
QVERIFY( !zip.addLocalFile( QDir::currentPath(), "bogusdir" ) );
QVERIFY(!zip.close());
}
void KArchiveTest::testReadZipError()
{
QFile brokenZip( "broken.zip" );
QVERIFY( brokenZip.open( QIODevice::WriteOnly ) );
// incomplete magic
brokenZip.write( QByteArray( "PK\003" ) );
brokenZip.close();
KZip zip( "broken.zip" );
QVERIFY( !zip.open(QIODevice::ReadOnly) );
QVERIFY( brokenZip.open( QIODevice::WriteOnly | QIODevice::Append ) );
// add rest of magic, but still incomplete header
brokenZip.write( QByteArray( "\004\000\000\000\000" ) );
brokenZip.close();
QVERIFY( !zip.open(QIODevice::ReadOnly) );
QVERIFY( brokenZip.remove() );
}
void KArchiveTest::testReadZip()
{
// testCreateZip must have been run first.
KZip zip( s_zipFileName );
QVERIFY( zip.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = zip.directory();
QVERIFY( dir != 0 );
// ZIP has no support for per-file user/group, so omit them from the listing
const QStringList listing = recursiveListEntries( dir, "", 0 );
QCOMPARE( listing.count(), 16 );
QCOMPARE( listing[ 0], QString("mode=40755 path=aaaemptydir type=dir") );
QCOMPARE( listing[ 1], QString("mode=40777 path=dir type=dir") );
QCOMPARE( listing[ 2], QString("mode=40777 path=dir/subdir type=dir") );
QCOMPARE( listing[ 3], QString("mode=100644 path=dir/subdir/mediumfile2 type=file size=100") );
QCOMPARE( listing[ 4], QString("mode=100644 path=empty type=file size=0") );
QCOMPARE( listing[ 5], QString("mode=100644 path=hugefile type=file size=20000") );
QCOMPARE( listing[ 6], QString("mode=100644 path=mediumfile type=file size=100") );
QCOMPARE( listing[ 7], QString("mode=100644 path=mimetype type=file size=%1").arg(strlen(s_zipMimeType)) );
QCOMPARE( listing[ 8], QString("mode=40777 path=my type=dir") );
QCOMPARE( listing[ 9], QString("mode=40777 path=my/dir type=dir") );
QCOMPARE( listing[10], QString("mode=100644 path=my/dir/test3 type=file size=29") );
QCOMPARE( listing[11], QString("mode=100440 path=test1 type=file size=5") );
QCOMPARE( listing[12], QString("mode=100644 path=test2 type=file size=8") );
QCOMPARE( listing[13], QString("mode=40777 path=z type=dir") );
// This one was added with addLocalFile, so ignore mode
QString str = listing[14];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3 type=file size=13") );
str = listing[15];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3_symlink type=file size=5 symlink=test3") );
QVERIFY( zip.close() );
}
void KArchiveTest::testZipFileData()
{
// testCreateZip must have been run first.
KZip zip(s_zipFileName);
QVERIFY(zip.open( QIODevice::ReadOnly));
testFileData(&zip);
QVERIFY(zip.close());
}
void KArchiveTest::testZipCopyTo()
{
// testCreateZip must have been run first.
KZip zip(s_zipFileName);
QVERIFY(zip.open(QIODevice::ReadOnly));
testCopyTo(&zip);
QVERIFY(zip.close());
}
void KArchiveTest::testZipMaxLength()
{
KZip zip( s_zipMaxLengthFileName );
QVERIFY( zip.open( QIODevice::WriteOnly ) );
// Similar to testTarMaxLength just to make sure, but of course zip doesn't have
// those limitations in the first place.
for (int i = 98; i < 514 ; i++ )
{
QString str, num;
str.fill( 'a', i-10 );
num.setNum( i );
num = num.rightJustified( 10, '0' );
zip.writeFile( str+num, "testu", "testg", "hum", 3 );
}
QVERIFY( zip.close() );
QVERIFY( zip.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = zip.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", 0 );
QCOMPARE( listing[ 0], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3") );
QCOMPARE( listing[ 3], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3") );
QCOMPARE( listing[ 4], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3") );
QCOMPARE( listing.count(), 514 - 98 );
QVERIFY( zip.close() );
}
void KArchiveTest::testZipWithNonLatinFileNames()
{
KZip zip( s_zipLocaleFileName );
QVERIFY( zip.open( QIODevice::WriteOnly ) );
const QByteArray fileData("Test of data with a russian file name");
const QString fileName = QString::fromUtf8( "Архитектура.okular" );
const QString recodedFileName = QFile::decodeName( QFile::encodeName( fileName ) );
QVERIFY( zip.writeFile( fileName, "pino", "users", fileData.constData(), fileData.size() ) );
QVERIFY( zip.close() );
QVERIFY( zip.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = zip.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", 0 );
QCOMPARE( listing.count(), 1 );
QCOMPARE( listing[0], QString::fromUtf8("mode=100644 path=%1 type=file size=%2").arg(recodedFileName).arg(fileData.size()) );
const KArchiveFile* fileEntry = static_cast< const KArchiveFile* >( dir->entry( dir->entries()[0] ) );
QCOMPARE( fileEntry->data(), fileData );
}
void KArchiveTest::testZipWithOverwrittenFileName()
{
KZip zip( s_zipFileName );
QVERIFY( zip.open( QIODevice::WriteOnly ) );
const QByteArray fileData1("There could be a fire, if there is smoke.");
const QString fileName = QLatin1String("wisdom");
QVERIFY( zip.writeFile( fileName, "konqi", "dragons", fileData1.constData(), fileData1.size() ) );
// now overwrite it
const QByteArray fileData2("If there is smoke, there could be a fire.");
QVERIFY( zip.writeFile( fileName, "konqi", "dragons", fileData2.constData(), fileData2.size() ) );
QVERIFY( zip.close() );
QVERIFY( zip.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = zip.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", 0 );
QCOMPARE( listing.count(), 1 );
QCOMPARE( listing[0], QString::fromUtf8("mode=100644 path=%1 type=file size=%2").arg(fileName).arg(fileData2.size()) );
const KArchiveFile* fileEntry = static_cast< const KArchiveFile* >( dir->entry( dir->entries()[0] ) );
QCOMPARE( fileEntry->data(), fileData2 );
}
static bool writeFile(const QString& dirName, const QString& fileName, const QByteArray& data)
{
Q_ASSERT(dirName.endsWith('/'));
QFile file(dirName + fileName);
if (!file.open(QIODevice::WriteOnly))
return false;
file.write(data);
return true;
}
void KArchiveTest::testZipAddLocalDirectory()
{
// Prepare local dir
KTempDir tmpDir;
const QString dirName = tmpDir.name();
const QByteArray file1Data = "Hello Shantanu";
const QString file1 = QLatin1String("file1");
QVERIFY(writeFile(dirName, file1, file1Data));
{
KZip zip(s_zipFileName);
QVERIFY(zip.open(QIODevice::WriteOnly));
QVERIFY(zip.addLocalDirectory(dirName, "."));
QVERIFY(zip.close());
}
{
KZip zip(s_zipFileName);
QVERIFY(zip.open(QIODevice::ReadOnly));
const KArchiveDirectory* dir = zip.directory();
QVERIFY(dir != 0);
const KArchiveEntry* e = dir->entry(file1);
QVERIFY(e && e->isFile());
const KArchiveFile* f = (KArchiveFile*)e;
QCOMPARE(f->data(), file1Data);
}
}
/**
* @see QTest::cleanupTestCase()
*/
void KArchiveTest::cleanupTestCase()
{
QFile::remove(s_zipMaxLengthFileName);
QFile::remove(s_zipFileName);
QFile::remove(s_zipLocaleFileName);
QFile::remove("test3_symlink");
}

View file

@ -1,70 +0,0 @@
/* This file is part of the KDE project
Copyright (C) 2006 David Faure <faure@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 as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KARCHIVETEST_H
#define KARCHIVETEST_H
#include <QtCore/QObject>
class KArchiveTest : public QObject
{
Q_OBJECT
void setupData();
private Q_SLOTS:
void initTestCase();
void testCreateTar_data();
void testCreateTar();
void testCreateTarXXX_data(){ setupData(); };
void testCreateTarXXX();
void testReadTar_data(){ setupData(); };
void testReadTar();
void testUncompress_data(){ setupData(); };
void testUncompress();
void testTarFileData_data(){ setupData(); };
void testTarFileData();
void testTarCopyTo_data(){ setupData(); };
void testTarCopyTo();
void testTarReadWrite_data(){ setupData(); };
void testTarReadWrite();
void testTarMaxLength_data();
void testTarMaxLength();
void testTarGlobalHeader();
void testTarPrefix();
void testTarDirectoryForgotten();
void testTarRootDir();
void testTarDirectoryTwice();
void testCreateZip();
void testCreateZipError();
void testReadZipError();
void testReadZip();
void testZipFileData();
void testZipCopyTo();
void testZipMaxLength();
void testZipWithNonLatinFileNames();
void testZipWithOverwrittenFileName();
void testZipAddLocalDirectory();
void cleanupTestCase();
};
#endif

View file

@ -1,79 +0,0 @@
/* This file is part of the KDE project
Copyright (C) 2009 Pino Toscano <pino@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 as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "klimitediodevicetest.h"
#include "klimitediodevice_p.h"
#include <qtest_kde.h>
QTEST_KDEMAIN_CORE(KLimitedIODeviceTest)
void KLimitedIODeviceTest::addChunk(const QByteArray &chunk)
{
ChunkData cd;
cd.data = chunk;
cd.offset = m_chunks.isEmpty() ? 0 : m_chunks.last().offset + m_chunks.last().data.size();
m_chunks.append(cd);
m_data.append(chunk);
}
void KLimitedIODeviceTest::initTestCase()
{
addChunk("Test of string");
addChunk("second part of the large buffer");
addChunk("... which will be used to test the KLimitedIODevice");
m_buffer.setBuffer(&m_data);
m_buffer.open(QIODevice::ReadOnly);
}
void KLimitedIODeviceTest::testReadChunks_data()
{
QTest::addColumn<int>("index");
for (int i = 0; i < m_chunks.count(); ++i) {
const ChunkData &d = m_chunks.at(i);
QTest::newRow(d.data.constData()) << i;
}
}
void KLimitedIODeviceTest::testReadChunks()
{
QFETCH(int, index);
const ChunkData &chunk = m_chunks.at(index);
KLimitedIODevice dev(&m_buffer, chunk.offset, chunk.data.size());
QVERIFY(dev.isOpen());
QCOMPARE(dev.readAll(), chunk.data);
}
void KLimitedIODeviceTest::testSeeking()
{
const ChunkData &chunk = m_chunks.at(2);
KLimitedIODevice dev(&m_buffer, chunk.offset, chunk.data.size());
QVERIFY(dev.seek(dev.size() - 16));
QCOMPARE(dev.readAll(), chunk.data.right(16));
QVERIFY(dev.seek(0));
QCOMPARE(dev.readAll(), chunk.data);
}
#include "moc_klimitediodevicetest.cpp"

View file

@ -1,52 +0,0 @@
/* This file is part of the KDE project
Copyright (C) 2009 Pino Toscano <pino@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 as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KLIMITEDIODEVICETEST_H
#define KLIMITEDIODEVICETEST_H
#include <QByteArray>
#include <QBuffer>
#include <QList>
#include <QObject>
struct ChunkData
{
QByteArray data;
int offset;
};
class KLimitedIODeviceTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testReadChunks_data();
void testReadChunks();
void testSeeking();
private:
void addChunk(const QByteArray &chunk);
QByteArray m_data;
QBuffer m_buffer;
QList<ChunkData> m_chunks;
};
#endif

View file

@ -1,73 +0,0 @@
/*
* Copyright (C) 2002 - 2005 David Faure <faure@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 "ktar.h"
#include <stdio.h>
#include <kcomponentdata.h>
#include <kdebug.h>
void recursive_print( const KArchiveDirectory * dir, const QString & path )
{
QStringList l = dir->entries();
l.sort();
QStringList::ConstIterator it = l.constBegin();
for( ; it != l.constEnd(); ++it )
{
const KArchiveEntry* entry = dir->entry( (*it) );
printf("mode=%07o %s %s %s%s %lld isdir=%d\n", entry->permissions(), entry->user().toLatin1().constData(), entry->group().toLatin1().constData(), path.toLatin1().constData(), (*it).toLatin1().constData(),
entry->isFile() ? static_cast<const KArchiveFile *>(entry)->size() : 0,
entry->isDirectory());
if (!entry->symLinkTarget().isEmpty()) {
printf(" (symlink to %s)\n",qPrintable(entry->symLinkTarget()));
}
if (entry->isDirectory())
recursive_print( (KArchiveDirectory *)entry, path+(*it)+'/' );
}
}
// See karchivetest.cpp for the unittest that covers KTar.
int main( int argc, char** argv )
{
if (argc != 2)
{
printf("\n"
" Usage :\n"
" ./ktartest /path/to/existing_file.tar.gz tests listing an existing tar.gz\n" );
return 1;
}
KComponentData componentData(QByteArray("ktartest"));
KTar tar( argv[1] );
if ( !tar.open( QIODevice::ReadOnly ) )
{
printf("Could not open %s for reading\n", argv[1] );
return 1;
}
const KArchiveDirectory* dir = tar.directory();
//printf("calling recursive_print\n");
recursive_print( dir, "" );
//printf("recursive_print called\n");
tar.close();
return 0;
}

View file

@ -1,246 +0,0 @@
/*
* Copyright (C) 2002-2006 David Faure <faure@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 "kzip.h"
#include <stdio.h>
#include <kcomponentdata.h>
#include <kdebug.h>
#include <QtCore/QFile>
#include <assert.h>
void recursive_print( const KArchiveDirectory * dir, const QString & path )
{
QStringList l = dir->entries();
QStringList::Iterator it = l.begin();
for( ; it != l.end(); ++it )
{
const KArchiveEntry* entry = dir->entry( (*it) );
printf("mode=%07o %s %s size: %lld pos: %lld %s%s isdir=%d%s", entry->permissions(),
entry->user().toLatin1().constData(), entry->group().toLatin1().constData(),
entry->isDirectory() ? 0 : ((KArchiveFile*)entry)->size(),
entry->isDirectory() ? 0 : ((KArchiveFile*)entry)->position(),
path.toLatin1().constData(), (*it).toLatin1().constData(), entry->isDirectory(),
entry->symLinkTarget().isEmpty() ? "" : QString(" symlink: %1").arg(entry->symLinkTarget()).toLatin1().constData() );
// if (!entry->isDirectory()) printf("%d", ((KArchiveFile*)entry)->size());
printf("\n");
if (entry->isDirectory())
recursive_print( (KArchiveDirectory *)entry, path+(*it)+'/' );
}
}
void recursive_transfer(const KArchiveDirectory * dir,
const QString & path, KZip * zip)
{
QStringList l = dir->entries();
QStringList::Iterator it = l.begin();
for( ; it != l.end(); ++it )
{
const KArchiveEntry* e = dir->entry( (*it) );
kDebug() << "actual file: " << e->name();
if (e->isFile())
{
Q_ASSERT( e && e->isFile() );
const KArchiveFile* f = (KArchiveFile*)e;
printf("FILE=%s\n", e->name().toLatin1().constData());
QByteArray arr( f->data() );
printf("SIZE=%i\n",arr.size() );
QString str( arr );
printf("DATA=%s\n", str.toLatin1().constData());
if (e->symLinkTarget().isEmpty()) {
zip->writeFile( path+e->name().toLatin1().constData(),
"holgi", "holgrp",
f->data(), arr.size() );
} else
zip->writeSymLink(path+e->name(), e->symLinkTarget(), "leo", "leo",
0120777, 1000000000l, 1000000000l, 1000000000l);
}
else if (e->isDirectory())
{
recursive_transfer((KArchiveDirectory *)e ,
path+e->name()+'/', zip);
}
}
}
int main( int argc, char** argv )
{
if (argc < 3)
{
// ###### Note: please consider adding new tests to karchivetest (so that they can be automated)
// rather than here (interactive)
printf("\n"
" Usage :\n"
" ./kziptest list /path/to/existing_file.zip tests listing an existing zip\n"
" ./kziptest print file.zip prints contents of all files.\n"
" ./kziptest print2 file.zip filename prints contents of one file.\n"
" ./kziptest update file.zip filename updates contents of one file.\n"
" ./kziptest transfer file.zip newfile.zip complete transfer.\n" );
return 1;
}
KComponentData componentData(QByteArray("kziptest"));
QString command = argv[1];
if ( command == "list" )
{
KZip zip( argv[2] );
if ( !zip.open( QIODevice::ReadOnly ) )
{
printf("Could not open %s for reading\n", argv[2] );
return 1;
}
const KArchiveDirectory* dir = zip.directory();
//printf("calling recursive_print\n");
recursive_print( dir, "" );
//printf("recursive_print called\n");
zip.close();
return 0;
}
else if (command == "print" )
{
KZip zip( argv[2] );
kDebug() << "Opening zip file";
if ( !zip.open( QIODevice::ReadOnly ) )
{
printf("Could not open %s for reading\n", argv[2] );
return 1;
}
const KArchiveDirectory* dir = zip.directory();
kDebug() << "Listing toplevel of zip file";
QStringList l = dir->entries();
QStringList::Iterator it = l.begin();
for( ; it != l.end(); ++it )
{
const KArchiveEntry* e = dir->entry( (*it) );
kDebug() << "Printing " << (*it);
if (e->isFile())
{
Q_ASSERT( e && e->isFile() );
const KArchiveFile* f = (KArchiveFile*)e;
QByteArray arr( f->data() );
printf("SIZE=%i\n",arr.size() );
QString str( arr );
printf("DATA=%s\n", str.toLatin1().constData());
}
}
zip.close();
return 0;
}
else if (command == "print2" )
{
if (argc != 4)
{
printf("usage: kziptest print2 archivename filename");
return 1;
}
KZip zip( argv[2] );
if ( !zip.open( QIODevice::ReadOnly ) )
{
printf("Could not open %s for reading\n", argv[2] );
return 1;
}
const KArchiveDirectory* dir = zip.directory();
const KArchiveEntry* e = dir->entry( argv[3] );
Q_ASSERT( e && e->isFile() );
const KArchiveFile* f = (KArchiveFile*)e;
QByteArray arr( f->data() );
printf("SIZE=%i\n",arr.size() );
QString str( arr );
// printf("DATA=%s\n", str.toLatin1().constData());
printf("%s", str.toLatin1().constData());
zip.close();
return 0;
}
else if (command == "update" )
{
if (argc != 4)
{
printf("usage: kziptest update archivename filename");
return 1;
}
KZip zip( argv[2] );
if ( !zip.open( QIODevice::ReadWrite ) )
{
printf("Could not open %s for read/write\n", argv[2] );
return 1;
}
// const KArchiveEntry* e = zip.directory()->entry( argv[3] );
// Q_ASSERT( e && e->isFile() );
// const KArchiveFile* f = (KArchiveFile*)e;
// QCString data( "This is some new data that goes into " );
// data += argv[3];
QFile f ( argv[3] );
if (!f.open( QIODevice::ReadOnly ))
{
printf("Could not open %s for reading\n", argv[2] );
return 1;
}
QDataStream s( &f );
zip.writeFile( argv[3], "", "", f.readAll(), f.size() );
zip.close();
return 0;
}
else if (command == "transfer" )
{
if (argc != 4)
{
printf("usage: kziptest transfer sourcefile destfile");
return 1;
}
KZip zip1( argv[2] );
KZip zip2( argv[3] );
if ( !zip1.open( QIODevice::ReadOnly ) )
{
printf("Could not open %s for reading\n", argv[2] );
return 1;
}
if ( !zip2.open( QIODevice::WriteOnly ) )
{
printf("Could not open %s for writing\n", argv[3] );
return 1;
}
const KArchiveDirectory* dir1 = zip1.directory();
recursive_transfer(dir1, "", &zip2 );
zip1.close();
zip2.close();
return 0;
}
else
printf("Unknown command\n");
}

Binary file not shown.

View file

@ -14,6 +14,7 @@ add_subdirectory(kpowermanager)
add_subdirectory(kdnssd)
add_subdirectory(khttp)
add_subdirectory(kspeech)
add_subdirectory(karchive)
######## kidletime ####################

View file

@ -0,0 +1,45 @@
if(LibArchive_FOUND)
include_directories(${LibArchive_INCLUDE_DIRS})
add_definitions(-DHAVE_LIBARCHIVE)
endif()
add_definitions(-DKDE_DEFAULT_DEBUG_AREA=51009)
set(karchive_LIB_SRCS
karchive.cpp
)
add_library(karchive ${LIBRARY_TYPE} ${karchive_LIB_SRCS})
target_link_libraries(karchive PUBLIC
${KDE4_KDECORE_LIBS}
)
if(LibArchive_FOUND)
target_link_libraries(karchive PRIVATE ${LibArchive_LIBRARIES})
endif()
set_target_properties(karchive PROPERTIES
VERSION ${GENERIC_LIB_VERSION}
SOVERSION ${GENERIC_LIB_SOVERSION}
)
generate_export_header(karchive)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/karchive_export.h
karchive.h
DESTINATION ${KDE4_INCLUDE_INSTALL_DIR}
COMPONENT Devel
)
install(
TARGETS karchive
EXPORT kdelibsLibraryTargets
${INSTALL_TARGETS_DEFAULT_ARGS}
)
if(ENABLE_TESTING)
add_subdirectory(tests)
endif()

1116
kutils/karchive/karchive.cpp Normal file

File diff suppressed because it is too large Load diff

163
kutils/karchive/karchive.h Normal file
View file

@ -0,0 +1,163 @@
/* This file is part of the KDE libraries
Copyright (C) 2018 Ivailo Monev <xakepa10@gmail.com>
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.
*/
#ifndef KARCHIVE_H
#define KARCHIVE_H
#include "karchive_export.h"
#include <QStringList>
#include <sys/types.h>
/*!
Archive entry information holder, valid object is obtained via @p KArchive::entry
@note It is up to the programmer to keep the integrity of the structure
@ingroup Types
@see KArchive
@since 4.22
*/
class KARCHIVE_EXPORT KArchiveEntry
{
public:
KArchiveEntry();
bool encrypted;
qint64 size;
qint64 gid;
qint64 uid;
mode_t mode;
time_t atime;
time_t ctime;
time_t mtime;
QByteArray hardlink;
QByteArray symlink;
QByteArray pathname;
QByteArray groupname;
QByteArray username;
//! @brief Returns if the entry is valid or not
bool isNull() const;
//! @brief Fancy encrypted for the purpose of widgets
QString fancyEncrypted() const;
//! @brief Fancy size for the purpose of widgets
QString fancySize() const;
//! @brief Fancy mode for the purpose of widgets
QString fancyMode() const;
//! @brief Fancy access time for the purpose of widgets
QString fancyATime() const;
//! @brief Fancy creation time for the purpose of widgets
QString fancyCTime() const;
//! @brief Fancy modification time for the purpose of widgets
QString fancyMTime() const;
//! @brief Fancy type for the purpose of widgets
QString fancyType() const;
};
#ifndef QT_NO_DEBUG_STREAM
KARCHIVE_EXPORT QDebug operator<<(QDebug, const KArchiveEntry &entry);
#endif
QT_BEGIN_NAMESPACE
Q_DECLARE_TYPEINFO(KArchiveEntry, Q_PRIMITIVE_TYPE);
QT_END_NAMESPACE
class KArchivePrivate;
/*!
Class for archives management.
Example:
\code
KArchiveManager archive("/home/joe/archive.tar.gz");
kDebug() << archive.list();
QDir::mkpath("/tmp/destination");
archive.extract("dir/in/archive/", "/tmp/destination");
archive.delete("file/in/archive.txt");
\endcode
@note Paths ending with "/" will be considered as directories
@warning The operations are done on temporary file, copy of the orignal, which after
successfull operation (add or remove) replaces the orignal thus if it is interrupted the
source may get corrupted
@see KArchiveEntry
@since 4.22
@warning the API is subject to change
@todo set permissions on file after copy, same as the original
*/
class KARCHIVE_EXPORT KArchive
{
public:
KArchive(const QString &path);
~KArchive();
static bool isSupported();
/*!
@brief Add paths to the archive
@param strip string to remove from the start of every path
@param destination relative path where paths should be added to
*/
bool add(const QStringList &paths, const QByteArray &strip = "/", const QByteArray &destination = "") const;
//! @brief Remove paths from the archive
bool remove(const QStringList &paths) const;
/*!
@brief Extract paths to destination
@param destination existing directory, you can use @p QDir::mkpath(QString)
@param preserve preserve advanced attributes (ACL/ATTR)
*/
bool extract(const QStringList &paths, const QString &destination, bool preserve = true) const;
/*!
@brief List the content of the archive
@param path filter, anything not starting with @p path will not be listed
@note May return empty list on both failure and success
@note Some formats list directories, some do not
*/
QList<KArchiveEntry> list(const QString &path = QString()) const;
//! @brief Get entry information for path in archive
KArchiveEntry entry(const QString &path) const;
//! @brief Get data for path in archive
QByteArray data(const QString &path) const;
//! @brief Returns if path is readable archive
bool isReadable() const;
//! @brief Returns if path is writable archive
bool isWritable() const;
//! @brief Returns human-readable description of the error that occured
QString errorString() const;
//! @brief Returns list of archive MIME types that are readable
static QStringList readableMimeTypes();
//! @brief Returns list of archive MIME types that are writable
static QStringList writableMimeTypes();
private:
Q_DISABLE_COPY(KArchive);
KArchivePrivate* const d;
};
#endif // KARCHIVE_H

View file

@ -0,0 +1,9 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_BINARY_DIR}/..
)
kde4_add_test(karchive-karchivetest
karchivetest.cpp
)
target_link_libraries(karchive-karchivetest ${KDE4_KARCHIVE_LIBS} ${QT_QTTEST_LIBRARY})

View file

@ -0,0 +1,207 @@
/* This file is part of the KDE libraries
Copyright (C) 2022 Ivailo Monev <xakepa10@gmail.com>
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 "qtest_kde.h"
#include "karchive.h"
#include "ktemporaryfile.h"
#include "ktempdir.h"
#include "kdebug.h"
#include <sys/stat.h>
static QString tmpName(const QString &archiveext)
{
KTemporaryFile ktempfile;
ktempfile.setSuffix(archiveext);
ktempfile.open();
return ktempfile.fileName();
}
static QString tmpCopy(const QString &archivepath)
{
const QString tmpdir = QFile::encodeName(KDEBINDIR);
const QString tmpbase = QFileInfo(archivepath).fileName();
const QString tmpcopy = QString::fromLatin1("%1/%2").arg(tmpdir).arg(tmpbase);
QFile::remove(tmpcopy);
QFile::copy(archivepath, tmpcopy);
return tmpcopy;
}
class KArchiveTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void list_data();
void list();
void add_data();
void add();
void remove_data();
void remove();
void error_data();
void error();
};
QTEST_KDEMAIN_CORE(KArchiveTest)
void KArchiveTest::initTestCase()
{
if (!KArchive::isSupported()) {
QSKIP("Built without LibArchive", SkipAll);
}
}
void KArchiveTest::cleanupTestCase()
{
}
void KArchiveTest::list_data()
{
QTest::addColumn<QString>("archivepath");
QTest::newRow(".tar.gz") << QFile::decodeName(KDESRCDIR "/tests.tar.gz");
QTest::newRow(".zip") << QFile::decodeName(KDESRCDIR "/tests.zip");
}
void KArchiveTest::list()
{
QFETCH(QString, archivepath);
KArchive karchive(archivepath);
QVERIFY(karchive.isReadable());
QList<KArchiveEntry> karchiveentries = karchive.list();
QCOMPARE(karchiveentries.size(), 3);
QCOMPARE(karchiveentries.at(0).pathname, QByteArray("tests/"));
QVERIFY(S_ISDIR(karchiveentries.at(0).mode));
QCOMPARE(karchiveentries.at(1).pathname, QByteArray("tests/CMakeLists.txt"));
QVERIFY(S_ISREG(karchiveentries.at(1).mode));
}
void KArchiveTest::add_data()
{
QTest::addColumn<QString>("archiveext");
QTest::newRow(".tar.gz") << ".tar.gz";
QTest::newRow(".zip") << ".zip";
}
void KArchiveTest::add()
{
QFETCH(QString, archiveext);
{
KArchive karchive(tmpName(archiveext));
QVERIFY(karchive.isWritable());
QStringList toadd = QStringList()
<< QFile::decodeName(KDESRCDIR "/CMakeLists.txt");
QVERIFY(karchive.add(toadd));
QList<KArchiveEntry> karchiveentries = karchive.list();
QCOMPARE(karchiveentries.size(), 1);
QCOMPARE(karchiveentries.at(0).pathname, QByteArray(KDESRCDIR "CMakeLists.txt").mid(1));
QVERIFY(S_ISREG(karchiveentries.at(0).mode));
}
{
KArchive karchive(tmpName(archiveext));
QVERIFY(karchive.isWritable());
QStringList toadd = QStringList()
<< QFile::decodeName(KDESRCDIR "/CMakeLists.txt");
QVERIFY(karchive.add(toadd, QByteArray(), QByteArray("dir")));
QList<KArchiveEntry> karchiveentries = karchive.list();
QCOMPARE(karchiveentries.size(), 1);
QCOMPARE(karchiveentries.at(0).pathname, QByteArray("dir" KDESRCDIR "CMakeLists.txt"));
QVERIFY(S_ISREG(karchiveentries.at(0).mode));
}
}
void KArchiveTest::remove_data()
{
QTest::addColumn<QString>("archivepath");
QTest::newRow(".tar.gz") << QFile::decodeName(KDESRCDIR "/tests.tar.gz");
QTest::newRow(".zip") << QFile::decodeName(KDESRCDIR "/tests.zip");
}
void KArchiveTest::remove()
{
QFETCH(QString, archivepath);
KArchive karchive(tmpCopy(archivepath));
QVERIFY(karchive.isReadable());
QVERIFY(karchive.isWritable());
QList<KArchiveEntry> karchiveentries = karchive.list();
QCOMPARE(karchiveentries.size(), 3);
QStringList toremove = QStringList()
<< QFile::decodeName("tests/CMakeLists.txt");
QVERIFY(karchive.remove(toremove));
QList<KArchiveEntry> karchiveentries2 = karchive.list();
QCOMPARE(karchiveentries2.size(), 2);
QCOMPARE(karchiveentries2.at(0).pathname, QByteArray("tests/"));
QVERIFY(S_ISDIR(karchiveentries2.at(0).mode));
QCOMPARE(karchiveentries2.at(1).pathname, QByteArray("tests/karchivetest.cpp"));
QVERIFY(S_ISREG(karchiveentries2.at(1).mode));
}
void KArchiveTest::error_data()
{
QTest::addColumn<QString>("archivepath");
QTest::addColumn<QString>("expectederror");
QTest::addColumn<bool>("add");
QTest::addColumn<bool>("remove");
QTest::addColumn<bool>("extract");
QTest::addColumn<QStringList>("pathslist");
QTest::newRow("empty")
<< QFile::decodeName(KDESRCDIR "/tests.tar.gz")
<< QString()
<< true << false << false
<< QStringList();
QTest::newRow("add_does_not_exist")
<< QFile::decodeName(KDESRCDIR "/tests123.zip")
<< QString::fromLatin1("lstat: No such file or directory")
<< true << false << false
<< (QStringList() << "does_not_exist");
}
void KArchiveTest::error()
{
QFETCH(QString, archivepath);
QFETCH(QString, expectederror);
QFETCH(bool, add);
QFETCH(bool, remove);
QFETCH(bool, extract);
QFETCH(QStringList, pathslist);
KArchive karchive(archivepath);
QCOMPARE(karchive.errorString(), QString());
if (add) {
karchive.add(pathslist);
QCOMPARE(karchive.errorString(), expectederror);
} else if (remove) {
karchive.remove(pathslist);
QCOMPARE(karchive.errorString(), expectederror);
} else if (extract) {
KTempDir ktempdir;
QVERIFY(ktempdir.exists());
karchive.extract(pathslist, ktempdir.name());
QCOMPARE(karchive.errorString(), expectederror);
}
}
#include "karchivetest.moc"

Binary file not shown.

Binary file not shown.

View file

@ -30,7 +30,6 @@
#include <kmimetype.h>
#include <kplugininfo.h>
#include <kstandarddirs.h>
#include <ktar.h>
#include <kdebug.h>
#include "packagemetadata.h"

View file

@ -35,11 +35,8 @@
#include <kmimetype.h>
#include <kstandarddirs.h>
#include <kservicetypetrader.h>
#include <ktar.h>
#include <ktemporaryfile.h>
#include <ktempdir.h>
#include <kurl.h>
#include <kzip.h>
#include "package.h"
#include "private/packages_p.h"

View file

@ -28,7 +28,6 @@
#include <kdebug.h>
#include <ktemporaryfile.h>
#include <kzip.h>
#include <QFile>
#include <QFileInfo>

View file

@ -22,7 +22,6 @@
#include <QDir>
#include <QFile>
#include <kzip.h>
#include <kstandarddirs.h>
#include "plasma/applet.h"