kde-extraapps/kdeplasma-addons/applets/comic/comicarchivejob.cpp
2015-01-15 17:07:43 +00:00

435 lines
14 KiB
C++

/***************************************************************************
* Copyright (C) 2011 Matthias Fuchs <mat69@gmx.net> *
* *
* 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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "comicarchivejob.h"
#include <KDebug>
#include <KIO/NetAccess>
#include <KTemporaryFile>
#include <KZip>
#ifdef HAVE_NEPOMUK
#include <Nepomuk/Resource>
#include <Nepomuk/Tag>
#include <Nepomuk/Variant>
#include <Nepomuk/Vocabulary/NCO>
#include <Nepomuk/Vocabulary/NFO>
#include <Nepomuk/Vocabulary/PIMO>
using namespace Nepomuk::Vocabulary;
#endif
ComicArchiveJob::ComicArchiveJob( const KUrl &dest, Plasma::DataEngine *engine, ComicArchiveJob::ArchiveType archiveType, IdentifierType identifierType, const QString &pluginName, QObject *parent )
: KJob( parent ),
mType( archiveType ),
mDirection( Undefined ),
mIdentifierType( identifierType ),
mSuspend( false ),
mFindAmount( true ),
mHasVariants( false ),
mDone( false ),
mComicNumber( 0 ),
mProcessedFiles( 0 ),
mTotalFiles( -1 ),
mEngine( engine ),
mZipFile( new KTemporaryFile ),
mZip( 0 ),
mPluginName( pluginName ),
mDest( dest )
{
if ( mZipFile->open() ) {
mZip = new KZip( mZipFile->fileName() );
mZip->open( QIODevice::ReadWrite );
mZip->setCompression( KZip::NoCompression );
setCapabilities( Killable | Suspendable );
} else {
kError() << "Could not create a temporary file for the zip file.";
}
}
ComicArchiveJob::~ComicArchiveJob()
{
emitResultIfNeeded();
delete mZip;
delete mZipFile;
qDeleteAll( mBackwardFiles );
}
bool ComicArchiveJob::isValid() const
{
if ( mPluginName.isEmpty() ) {
kWarning() << "No plugin name specified.";
return false;
}
switch ( mType ) {
case ArchiveFromTo:
if ( mToIdentifier.isEmpty() || mFromIdentifier.isEmpty() ) {
kWarning() << "Not enought data provided to archive a range.";
return false;
}
break;
case ArchiveStartTo:
case ArchiveEndTo:
if ( mToIdentifier.isEmpty() ) {
kWarning() << "Not enough data provied to archive StartTo/EndTo.";
return false;
}
break;
default:
break;
}
return mEngine->isValid() && mZip && mZip->isOpen();
}
void ComicArchiveJob::setToIdentifier( const QString &toIdentifier )
{
mToIdentifier = toIdentifier;
mToIdentifierSuffix = mToIdentifier;
mToIdentifierSuffix.remove( mPluginName + ':' );
}
void ComicArchiveJob::setFromIdentifier( const QString &fromIdentifier )
{
mFromIdentifier = fromIdentifier;
mFromIdentifierSuffix = mFromIdentifier;
mFromIdentifierSuffix.remove( mPluginName + ':' );
}
void ComicArchiveJob::start()
{
switch ( mType ) {
case ArchiveAll:
requestComic( suffixToIdentifier( QString() ) );
break;
case ArchiveStartTo:
requestComic( mToIdentifier );
break;
case ArchiveEndTo: {
setFromIdentifier( mToIdentifier );
mToIdentifier.clear();
mToIdentifierSuffix.clear();
requestComic( suffixToIdentifier( QString() ) );
break;
}
case ArchiveFromTo:
mDirection = Foward;
defineTotalNumber();
requestComic( mFromIdentifier );
break;
}
}
void ComicArchiveJob::dataUpdated( const QString &source, const Plasma::DataEngine::Data &data )
{
if ( !mZip ) {
kWarning() << "No zip file, aborting.";
setErrorText( i18n( "No zip file is existing, aborting." ) );
setError( KilledJobError );
emitResultIfNeeded();
return;
}
const QString currentIdentifier = data[ "Identifier" ].toString();
QString currentIdentifierSuffix = currentIdentifier;
currentIdentifierSuffix.remove( mPluginName + ':' );
const QImage image = data[ "Image" ].value<QImage>();
const bool hasError = data[ "Error" ].toBool() || image.isNull();
const QString previousIdentifierSuffix = data[ "Previous identifier suffix" ].toString();
const QString nextIdentifierSuffix = data[ "Next identifier suffix" ].toString();
const QString firstIdentifierSuffix = data[ "First strip identifier suffix" ].toString();
mAuthors << data[ "Comic Author" ].toString().split( ',', QString::SkipEmptyParts );
mAuthors.removeDuplicates();
if ( mComicTitle.isEmpty() ) {
mComicTitle = data[ "Title" ].toString();
}
mEngine->disconnectSource( source, this );
if ( hasError ) {
kWarning() << "An error occured at" << source << "stopping.";
setErrorText( i18n( "An error happened for identifier %1.", source ) );
setError( KilledJobError );
copyZipFileToDestination();
return;
}
if ( mDirection == Undefined ) {
if ( ( mType == ArchiveAll ) || ( mType == ArchiveStartTo ) ) {
if ( !firstIdentifierSuffix.isEmpty() ) {
setFromIdentifier( suffixToIdentifier( firstIdentifierSuffix ) );
}
if ( mType == ArchiveAll ) {
setToIdentifier( currentIdentifier );
}
mDirection = ( firstIdentifierSuffix.isEmpty() ? Backward : Foward );
if ( mDirection == Foward ) {
requestComic( suffixToIdentifier( firstIdentifierSuffix ) );
return;
} else {
//backward, i.e. the to identifier is unknown
mToIdentifier.clear();
mToIdentifierSuffix.clear();
}
} else if ( mType == ArchiveEndTo ) {
mDirection = Foward;
setToIdentifier( currentIdentifier );
requestComic( mFromIdentifier );
return;
}
}
bool worked = false;
++mProcessedFiles;
if ( mDirection == Foward ) {
KTemporaryFile tempFile;
worked = tempFile.open();
worked = worked && tempFile.flush();
worked = ( worked ? image.save( tempFile.fileName(), "PNG" ) : worked );
worked = ( worked ? addFileToZip( tempFile.fileName() ) : worked );
if ( worked ) {
if ( ( currentIdentifier == mToIdentifier ) || ( currentIdentifierSuffix == nextIdentifierSuffix) || nextIdentifierSuffix.isEmpty() ) {
kDebug() << "Done downloading at:" << source;
copyZipFileToDestination();
} else {
requestComic( suffixToIdentifier( nextIdentifierSuffix ) );
}
}
} else if ( mDirection == Backward ) {
KTemporaryFile *tempFile = new KTemporaryFile;
mBackwardFiles << tempFile;
worked = tempFile->open();
worked = worked && tempFile->flush();
worked = ( worked ? image.save( tempFile->fileName(), "PNG" ) : worked );
if ( worked ) {
if ( ( currentIdentifier == mToIdentifier ) || ( currentIdentifierSuffix == previousIdentifierSuffix ) || previousIdentifierSuffix.isEmpty() ) {
kDebug() << "Done downloading at:" << source;
createBackwardZip();
} else {
requestComic( suffixToIdentifier( previousIdentifierSuffix) );
}
}
}
defineTotalNumber( currentIdentifierSuffix );
setProcessedAmount( Files, mProcessedFiles );
if ( mTotalFiles != -1 ) {
setPercent( ( 100 * mProcessedFiles ) / mTotalFiles );
}
if ( !worked ) {
kError() << "Could not write the file, identifier:" << source;
setErrorText( i18n( "Failed creating the file with identifier %1.", source ) );
setError( KilledJobError );
emitResultIfNeeded();
}
}
bool ComicArchiveJob::doKill()
{
mSuspend = true;
return KJob::doKill();
}
bool ComicArchiveJob::doSuspend()
{
mSuspend = true;
return true;
}
bool ComicArchiveJob::doResume()
{
mSuspend = false;
if ( !mRequest.isEmpty() ) {
requestComic( mRequest );
}
return true;
}
void ComicArchiveJob::defineTotalNumber( const QString &currentSuffix )
{
findTotalNumberFromTo();
if ( mTotalFiles == -1 ) {
kDebug() << "Unable to find the total number for" << mPluginName;
return;
}
//calculate a new value for total files, can be different from the previous one,
//if there are no strips for certain days/numbers
if ( !currentSuffix.isEmpty() ) {
if ( mIdentifierType == Date ) {
const QDate current = QDate::fromString( currentSuffix, "yyyy-MM-dd" );
const QDate to = QDate::fromString( mToIdentifierSuffix, "yyyy-MM-dd" );
if ( current.isValid() && to.isValid() ) {
//processed files + files still to download
mTotalFiles = mProcessedFiles + qAbs( current.daysTo( to ) );
}
} else if ( mIdentifierType == Number ) {
bool result = true;
bool ok;
const int current = currentSuffix.toInt( &ok );
result = ( result && ok );
const int to = mToIdentifierSuffix.toInt( &ok );
result = ( result && ok );
if ( result ) {
//processed files + files still to download
mTotalFiles = mProcessedFiles + qAbs( to - current );
}
}
}
if ( mTotalFiles != -1 ) {
setTotalAmount( Files, mTotalFiles );
}
}
void ComicArchiveJob::findTotalNumberFromTo()
{
if ( mTotalFiles != -1 ) {
return;
}
if ( mIdentifierType == Date ) {
const QDate from = QDate::fromString( mFromIdentifierSuffix, "yyyy-MM-dd" );
const QDate to = QDate::fromString( mToIdentifierSuffix, "yyyy-MM-dd" );
if ( from.isValid() && to.isValid() ) {
mTotalFiles = qAbs( from.daysTo( to ) ) + 1;
}
} else if ( mIdentifierType == Number ) {
bool result = true;
bool ok;
const int from = mFromIdentifierSuffix.toInt( &ok );
result = ( result && ok );
const int to = mToIdentifierSuffix.toInt( &ok );
result = ( result && ok );
if ( result ) {
mTotalFiles = qAbs( to - from ) + 1;
}
}
}
QString ComicArchiveJob::suffixToIdentifier( const QString &suffix ) const
{
return mPluginName + ':' + suffix;
}
void ComicArchiveJob::requestComic( QString identifier ) //krazy:exclude=passbyvalue
{
mRequest.clear();
if ( mSuspend ) {
mRequest = identifier;
return;
}
emit description( this, i18n( "Creating Comic Book Archive" ),
qMakePair( QString( "source" ), identifier ),
qMakePair( QString( "destination" ), mDest.prettyUrl() ) );
mEngine->connectSource( identifier, this );
mEngine->query( identifier );
}
bool ComicArchiveJob::addFileToZip( const QString &path )
{
//We use 6 signs, e.g. number 1 --> 000001.png, 123 --> 000123.png
//this way the comics should always be correctly sorted (otherwise evince e.g. has problems)
static const int numSigns = 6;
static const QString zero = QLatin1String( "0" );
QString number = QString::number( ++mComicNumber );
const int length = number.length();
if ( length < numSigns ) {
number = zero.repeated( numSigns - length ) + number;
}
return mZip->addLocalFile( path, number + QLatin1String( ".png" ) );
}
void ComicArchiveJob::createBackwardZip()
{
for ( int i = mBackwardFiles.count() - 1; i >= 0; --i ) {
if ( !addFileToZip( mBackwardFiles[i]->fileName() ) ) {
kWarning() << "Failed adding a file to the archive.";
setErrorText( i18n( "Failed adding a file to the archive." ) );
setError( KilledJobError );
emitResultIfNeeded();
return;
}
}
copyZipFileToDestination();
}
void ComicArchiveJob::copyZipFileToDestination()
{
mZip->close();
const bool worked = KIO::NetAccess::file_copy( KUrl( mZipFile->fileName() ), mDest );
//store additional data using Nepomuk
if (!worked) {
kWarning() << "Could not copy the zip file to the specified destination:" << mDest;
setErrorText( i18n( "Could not create the archive at the specified location." ) );
setError( KilledJobError );
emitResultIfNeeded();
return;
}
#ifdef HAVE_NEPOMUK
//store additional data using Nepomuk
Nepomuk::Resource res( mDest, NFO::FileDataObject() );
Nepomuk::Resource comicTopic( "Comic", PIMO::Topic() );
comicTopic.setLabel( i18n( "Comic" ) );
if ( !mComicTitle.isEmpty() ) {
Nepomuk::Resource topic( mComicTitle, PIMO::Topic() );
topic.setLabel( mComicTitle );
topic.setProperty( PIMO::superTopic(), comicTopic );
res.addTag( topic );
} else {
// res.addTag( comicTopic );//TODO activate this, see below
;
}
//FIXME also set the comic topic as tag, this is redundant, as topic has this as super topic
//though at this point the gui (Dolphin) does not manage to show the correct tags
res.addTag( comicTopic );
foreach ( QString author, mAuthors ) {
author = author.trimmed();
Nepomuk::Resource authorRes( author, NCO::PersonContact() );
authorRes.setProperty( NCO::fullname(), author );
res.addProperty( NCO::creator(), authorRes );
}
#endif
emitResultIfNeeded();
}
void ComicArchiveJob::emitResultIfNeeded()
{
if ( !mDone ) {
mDone = true;
emitResult();
}
}