mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
251 lines
7.3 KiB
C++
251 lines
7.3 KiB
C++
/*
|
|
Avatar provider - based on Microblog dataengine implementation
|
|
Copyright (C) 2008 Aaron Seigo <aseigo@kde.org>
|
|
Copyright (C) 2012 Martin Klapetek <martin.klapetek@gmail.com>
|
|
|
|
This library is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 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 "imageprovider.h"
|
|
|
|
#include <KDebug>
|
|
#include <KIO/Job>
|
|
#include <KImageCache>
|
|
|
|
#include <QPainter>
|
|
|
|
class Akonadi::ImageProviderPrivate
|
|
{
|
|
Q_DECLARE_PUBLIC( ImageProvider )
|
|
|
|
public:
|
|
|
|
struct QueuedJobHelper {
|
|
QString who;
|
|
KUrl url;
|
|
bool polishImage;
|
|
};
|
|
|
|
ImageProviderPrivate( ImageProvider *q ) : q_ptr( q )
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Makes the corners of the image rounded
|
|
*/
|
|
QImage polishImage( const QImage &img )
|
|
{
|
|
const int sz = 48 * 4;
|
|
QImage roundedImage = QImage( QSize( sz, sz ), QImage::Format_ARGB32_Premultiplied );
|
|
roundedImage.fill( Qt::transparent );
|
|
QPainter p;
|
|
p.begin( &roundedImage );
|
|
QPainterPath clippingPath;
|
|
QRectF imgRect = QRectF( QPoint( 0, 0 ), roundedImage.size() );
|
|
clippingPath.addRoundedRect( imgRect, 24, 24 );
|
|
p.setClipPath( clippingPath );
|
|
p.setClipping( true );
|
|
p.drawImage( QRectF( QPointF( 0, 0 ), roundedImage.size() ), img );
|
|
return roundedImage;
|
|
}
|
|
|
|
///All active jobs
|
|
QHash<KJob *, QString> jobs;
|
|
|
|
///Received data from the jobs
|
|
QHash<KJob *, QByteArray> jobData;
|
|
|
|
///Number of running jobs
|
|
int runningJobs;
|
|
|
|
///Queued jobs when the limit is reached
|
|
QList<QueuedJobHelper> queuedJobs;
|
|
|
|
///String list containing the list of loaded persons avatars
|
|
QStringList pendingPersons;
|
|
|
|
///KImageCache instance
|
|
KImageCache *imageCache;
|
|
|
|
private:
|
|
/**
|
|
* This slot is called when fetching the avatar has finished
|
|
*/
|
|
void result( KJob *job );
|
|
|
|
/**
|
|
* Handling received data from KJob
|
|
*/
|
|
void recv( KIO::Job *job, const QByteArray &data );
|
|
|
|
ImageProvider *q_ptr;
|
|
};
|
|
|
|
void Akonadi::ImageProviderPrivate::recv( KIO::Job *job, const QByteArray &data )
|
|
{
|
|
jobData[job] += data;
|
|
}
|
|
|
|
void Akonadi::ImageProviderPrivate::result( KJob *job )
|
|
{
|
|
Q_Q( ImageProvider );
|
|
if ( !jobs.contains( job ) ) {
|
|
kDebug() << "Tried to handle unknown job, returning...";
|
|
return;
|
|
}
|
|
|
|
runningJobs--;
|
|
|
|
if ( queuedJobs.count() > 0 ) {
|
|
QueuedJobHelper helper = queuedJobs.takeFirst();
|
|
q->loadImage( helper.who, helper.url, helper.polishImage );
|
|
}
|
|
|
|
if ( job->error() ) {
|
|
// TODO: error handling
|
|
KIO::TransferJob* kiojob = dynamic_cast<KIO::TransferJob*>( job );
|
|
kError() << "Image job for" << jobs.value( job ) << "returned error:" << kiojob->errorString();
|
|
} else {
|
|
const QString who = jobs.value( job );
|
|
|
|
QImage image;
|
|
image.loadFromData( jobData.value( job ) );
|
|
KIO::TransferJob* kiojob = dynamic_cast<KIO::TransferJob*>( job );
|
|
const QString cacheKey = who + QLatin1Char( '@' ) +
|
|
kiojob->property( "imageUrl" ).value<KUrl>().pathOrUrl();
|
|
|
|
kDebug() << "Downloaded image for" << who << "(key:" << cacheKey << ")";
|
|
|
|
imageCache->insertImage( cacheKey, image );
|
|
pendingPersons.removeAll( cacheKey );
|
|
|
|
bool polishImage = job->property( "polishImage" ).toBool();
|
|
|
|
Q_EMIT q->imageLoaded( who,
|
|
kiojob->property( "imageUrl" ).value<KUrl>(),
|
|
polishImage ? this->polishImage( image ) : image );
|
|
}
|
|
|
|
jobs.remove( job );
|
|
jobData.remove( job );
|
|
}
|
|
|
|
Akonadi::ImageProvider::ImageProvider( QObject *parent )
|
|
: QObject( parent ),
|
|
d_ptr( new ImageProviderPrivate( this ) )
|
|
{
|
|
Q_D( ImageProvider );
|
|
d->imageCache = 0;
|
|
d->runningJobs = 0;
|
|
}
|
|
|
|
Akonadi::ImageProvider::~ImageProvider()
|
|
{
|
|
Q_D( ImageProvider );
|
|
delete d;
|
|
}
|
|
|
|
QImage Akonadi::ImageProvider::loadImage( const QString &who, const KUrl &url,
|
|
bool polishImage, KImageCache *cache )
|
|
{
|
|
Q_D( ImageProvider );
|
|
|
|
if ( who.isEmpty() ) {
|
|
return QImage();
|
|
}
|
|
|
|
if ( !d->imageCache && !cache ) {
|
|
//if no old cache and no passed cache, default to plasma_engine_preview
|
|
d->imageCache = new KImageCache( QLatin1String( "plasma_engine_preview" ),
|
|
10485760 ); // Re-use previewengine's cache
|
|
} else if ( !d->imageCache && cache ) {
|
|
//if there is no old cache, set the new one
|
|
d->imageCache = cache;
|
|
} else if ( d->imageCache && cache ) {
|
|
//delete old one and set new one
|
|
//delete d->imageCache; //FIXME: crashes
|
|
d->imageCache = cache;
|
|
}
|
|
|
|
const QString cacheKey = who + QLatin1Char( '@' ) + url.pathOrUrl();
|
|
|
|
// Make sure we only start one job per user
|
|
if ( d->pendingPersons.contains( cacheKey ) ) {
|
|
kDebug() << "Job for" << who << "already running, returning";
|
|
return QImage();
|
|
}
|
|
|
|
// Check if the image is in the cache, if so emit it and return
|
|
QImage preview;
|
|
preview.fill( Qt::transparent );
|
|
|
|
if ( d->imageCache->findImage( cacheKey, &preview ) ) {
|
|
// cache hit
|
|
kDebug() << "Image for" << who << "already in cache, returning it";
|
|
return polishImage ? d->polishImage( preview ) : preview;
|
|
}
|
|
|
|
if ( !url.isValid() ) {
|
|
kDebug() << "Invalid url, returning";
|
|
return QImage();
|
|
}
|
|
|
|
kDebug() << "No cache, fetching image for" << who;
|
|
|
|
d->pendingPersons << cacheKey;
|
|
//FIXME: since kio_http bombs the system with too many request put a temporary
|
|
// arbitrary limit here, revert as soon as BUG 192625 is fixed
|
|
// Note: seems fixed.
|
|
if ( d->runningJobs < 500 ) {
|
|
d->runningJobs++;
|
|
kDebug() << "Starting fetch job for" << who;
|
|
KIO::Job *job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
|
|
job->setAutoDelete( true );
|
|
d->jobs[job] = who;
|
|
connect( job, SIGNAL(data(KIO::Job*,QByteArray)),
|
|
this, SLOT(recv(KIO::Job*,QByteArray)) );
|
|
connect( job, SIGNAL(result(KJob*)),
|
|
this, SLOT(result(KJob*)) );
|
|
// The url needs to be stored explicitly because, for example, facebook redirects
|
|
// randomly between its servers causing the url to be different every time, which
|
|
// makes storing the job's url in cache impossible therefore we set it here and
|
|
// use this when saving to the cache instead of job->url()
|
|
job->setProperty( "imageUrl", url );
|
|
job->setProperty( "polishImage", polishImage );
|
|
job->start();
|
|
} else {
|
|
kDebug() << "Queuing job for" << who;
|
|
ImageProviderPrivate::QueuedJobHelper helper;
|
|
helper.who = who;
|
|
helper.url = url;
|
|
helper.polishImage = polishImage;
|
|
d->queuedJobs.append( helper );
|
|
}
|
|
|
|
return QImage();
|
|
}
|
|
|
|
void Akonadi::ImageProvider::abortAllJobs()
|
|
{
|
|
Q_D( ImageProvider );
|
|
Q_FOREACH ( KJob *job, d->jobs.keys() ) {
|
|
job->kill();
|
|
d->jobs.remove( job );
|
|
d->jobData.remove( job );
|
|
}
|
|
}
|
|
|
|
#include "moc_imageprovider.cpp"
|