kde-workspace/kioslave/thumbnail/comiccreator.cpp
Ivailo Monev 4f889e1438 kioslave: try different unrar version argument if "--version" does not work
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2021-08-03 20:45:34 +03:00

308 lines
8.9 KiB
C++

/**
* This file is part of the KDE libraries
*
* Comic Book Thumbnailer for KDE 4 v0.1
* Creates cover page previews for comic-book files (.cbr/z/t).
* Copyright (c) 2009 Harsh J <harsh@harshj.com>
*
* Some code borrowed from Okular's comicbook generators,
* by Tobias Koenig <tokoe@kde.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// comiccreator.cpp
#include "comiccreator.h"
#include <kdemacros.h>
#include <kmimetype.h>
#include <kstandarddirs.h>
#include <kzip.h>
#include <ktar.h>
#include <kdebug.h>
#include <ktempdir.h>
#include <memory>
#include <QtCore/QFile>
#include <QtCore/QEventLoop>
#include <QtCore/QProcess>
// For KIO-Thumbnail debug outputs
#define KIO_THUMB 11371
extern "C"
{
KDE_EXPORT ThumbCreator *new_creator()
{
return new ComicCreator;
}
}
ComicCreator::ComicCreator() : m_loop(0) {}
bool ComicCreator::create(const QString& path, int width, int height, QImage& img)
{
Q_UNUSED(width);
Q_UNUSED(height);
QImage cover;
// Detect mime type.
const KMimeType::Ptr mime = KMimeType::findByFileContent(path);
if (mime->is("application/x-cbz") || mime->is("application/zip")) {
// ZIP archive.
cover = extractArchiveImage(path, ZIP);
} else if (mime->is("application/x-cbt") ||
mime->is("application/x-gzip") ||
mime->is("application/x-tar")) {
// TAR archive
cover = extractArchiveImage(path, TAR);
} else if (mime->is("application/x-cbr") || mime->is("application/x-rar")) {
// RAR archive.
cover = extractRARImage(path);
}
if (cover.isNull()) {
kDebug(KIO_THUMB) << "Error creating the comic book thumbnail.";
return false;
}
// Copy the extracted cover to KIO::ThumbCreator's img reference.
img = cover;
return true;
}
void ComicCreator::filterImages(QStringList& entries)
{
/// Sort case-insensitive, then remove non-image entries.
QMap<QString, QString> entryMap;
Q_FOREACH(const QString& entry, entries) {
if (entry.endsWith(QLatin1String(".gif"), Qt::CaseInsensitive) ||
entry.endsWith(QLatin1String(".jpg"), Qt::CaseInsensitive) ||
entry.endsWith(QLatin1String(".jpeg"), Qt::CaseInsensitive) ||
entry.endsWith(QLatin1String(".png"), Qt::CaseInsensitive)) {
entryMap.insert(entry.toLower(), entry);
}
}
entries = entryMap.values();
}
QImage ComicCreator::extractArchiveImage(const QString& path, const ComicCreator::Type type)
{
/// Extracts the cover image out of the .cbz or .cbt file.
QScopedPointer<KArchive> cArchive;
if (type==ZIP) {
// Open the ZIP archive.
cArchive.reset(new KZip(path));
} else if (type==TAR) {
// Open the TAR archive.
cArchive.reset(new KTar(path));
} else {
// Reject all other types for this method.
return QImage();
}
// Can our archive be opened?
if (!cArchive->open(QIODevice::ReadOnly)) {
return QImage();
}
// Get the archive's directory.
const KArchiveDirectory* cArchiveDir = 0;
cArchiveDir = cArchive->directory();
if (!cArchiveDir) {
return QImage();
}
QStringList entries;
// Get and filter the entries from the archive.
getArchiveFileList(entries, QString(), cArchiveDir);
filterImages(entries);
if (entries.isEmpty()) {
return QImage();
}
// Extract the cover file.
const KArchiveFile *coverFile = static_cast<const KArchiveFile*>
(cArchiveDir->entry(entries[0]));
if (!coverFile) {
return QImage();
}
return QImage::fromData(coverFile->data());
}
void ComicCreator::getArchiveFileList(QStringList& entries, const QString& prefix,
const KArchiveDirectory *dir)
{
/// Recursively list all files in the ZIP archive into 'entries'.
Q_FOREACH (const QString& entry, dir->entries()) {
const KArchiveEntry *e = dir->entry(entry);
if (e->isDirectory()) {
getArchiveFileList(entries, prefix + entry + '/',
static_cast<const KArchiveDirectory*>(e));
} else if (e->isFile()) {
entries.append(prefix + entry);
}
}
}
QImage ComicCreator::extractRARImage(const QString& path)
{
/// Extracts the cover image out of the .cbr file.
// Check if unrar is available. Get its path in 'unrarPath'.
QString unrar = unrarPath("--version");
if (unrar.isEmpty()) {
unrar = unrarPath("-v");
}
if (unrar.isEmpty()) {
kWarning(KIO_THUMB) << "A suitable version of unrar is not available.";
return QImage();
}
// Get the files and filter the images out.
QStringList entries = getRARFileList(path, unrar);
filterImages(entries);
if (entries.isEmpty()) {
return QImage();
}
// Clear previously used data arrays.
m_stdOut.clear();
m_stdErr.clear();
// Extract the cover file alone. Use verbose paths.
// unrar x -n<file> path/to/archive /path/to/temp
KTempDir cUnrarTempDir;
startProcess(unrar, QStringList() << "x" << "-n" + entries[0] << path << cUnrarTempDir.name());
// Load cover file data into image.
QImage cover;
cover.load(cUnrarTempDir.name() + entries[0]);
cUnrarTempDir.unlink();
return cover;
}
QStringList ComicCreator::getRARFileList(const QString& path,
const QString& unrarPath)
{
/// Get a verbose unrar listing so we can extract a single file later.
// CMD: unrar vb /path/to/archive
QStringList entries;
startProcess(unrarPath, QStringList() << "vb" << path);
entries = QString::fromLocal8Bit(m_stdOut).split('\n', QString::SkipEmptyParts);
return entries;
}
QString ComicCreator::unrarPath(const QString& versionarg) const
{
/// Check the standard paths to see if a suitable unrar is available.
QString unrar = KStandardDirs::findExe("unrar");
if (unrar.isEmpty()) {
unrar = KStandardDirs::findExe("unrar-nonfree");
}
if (unrar.isEmpty()) {
unrar = KStandardDirs::findExe("rar");
}
if (!unrar.isEmpty()) {
QProcess proc;
proc.start(unrar, QStringList() << versionarg);
proc.waitForFinished(-1);
const QStringList lines = QString::fromLocal8Bit(proc.readAllStandardOutput()).split
('\n', QString::SkipEmptyParts);
if (!lines.isEmpty()) {
if (lines.first().startsWith("RAR ") || lines.first().startsWith("UNRAR ")) {
return unrar;
}
}
}
return QString();
}
void ComicCreator::readProcessOut()
{
/// Read all std::out data and store to the data array.
if (!m_process)
return;
m_stdOut += m_process->readAllStandardOutput();
}
void ComicCreator::readProcessErr()
{
/// Read available std:err data and kill process if there is any.
if (!m_process)
return;
m_stdErr += m_process->readAllStandardError();
if (!m_stdErr.isEmpty())
{
m_process->kill();
return;
}
}
void ComicCreator::finishedProcess(int exitCode, QProcess::ExitStatus exitStatus)
{
/// Run when process finishes.
Q_UNUSED(exitCode)
if (m_loop)
{
m_loop->exit(exitStatus == QProcess::CrashExit ? 1 : 0);
}
}
int ComicCreator::startProcess(const QString& processPath, const QStringList& args)
{
/// Run a process and store std::out, std::err data in their respective buffers.
int ret = 0;
m_process.reset(new QProcess(this));
m_process->setProcessChannelMode(QProcess::SeparateChannels);
connect(m_process.data(), SIGNAL(readyReadStandardOutput()), SLOT(readProcessOut()));
connect(m_process.data(), SIGNAL(readyReadStandardError()), SLOT(readProcessErr()));
connect(m_process.data(), SIGNAL(finished(int, QProcess::ExitStatus)),
SLOT(finishedProcess(int, QProcess::ExitStatus)));
m_process->start(processPath, args, QIODevice::ReadWrite | QIODevice::Unbuffered);
QEventLoop loop;
m_loop = &loop;
ret = loop.exec(QEventLoop::WaitForMoreEvents);
m_loop = 0;
return ret;
}
ThumbCreator::Flags ComicCreator::flags() const
{
return DrawFrame;
}
#include "moc_comiccreator.cpp"