Logo Search packages:      
Sourcecode: kat version File versions  Download package

katpreviewjob.cpp

/***************************************************************************
 *   Copyright (C) 2005 by Roberto Cappuccio and the Kat team              *
 *   Roberto Cappuccio : roberto.cappuccio@gmail.com                       *
 *                                                                         *
 *   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 Steet, Fifth Floor, Boston, MA 02110-1301, USA.           *
 ***************************************************************************/

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <qdir.h>
#include <qfile.h>
#include <qimage.h>
#include <qtimer.h>
#include <qregexp.h>
#include <kdatastream.h>
#include <kfileitem.h>
#include <kapplication.h>
#include <ktempfile.h>
#include <ktrader.h>
#include <kmdcodec.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kservice.h>
#include <kdebug.h>

#include "katpreviewjob.h"

#define TIMEOUT 30

struct KatPreviewItem;

using namespace KIO;

struct KatPreviewItem
{
    KFileItem *item;
    KService::Ptr plugin;
    QTimer* previewTimer;
};

struct KatPreviewJobPrivate
{
    enum
    {
        STATE_STATORIG,
        STATE_CREATETHUMB
    } state;

    KFileItemList initialItems;
    const QStringList *enabledPlugins;

    QValueList<KatPreviewItem> items; // Our todo list
    KatPreviewItem currentItem; // The current item
    time_t tOrig; // The modification time of that URL
    QString thumbPath; // Path to thumbnail cache for the current size

    // Original URL of current item in TMS format
    // (file:///path/to/file instead of file:/path/to/file)
    QString origName;

    QString thumbName; // Thumbnail file name for current item
    int width; // Size of thumbnail
    int height;
    int cacheWidth; // Unscaled size of thumbnail (128 or 256 if cache is enabled)
    int cacheHeight;
    bool bScale; // Whether the thumbnail should be scaled
    QString tempName; // If the file to create a thumb for was a temp file, this is its name
    unsigned long maximumSize; // Over that, it's too much
    int iconSize; // the size for the icon overlay
    int iconAlpha; // the transparency of the blended mimetype icon

    // Shared memory segment Id. The segment is allocated to a size
    // of extent x extent x 4 (32 bit image) on first need.
    int shmid;
    uchar *shmaddr; // And the data area

    bool deleteItems; // Delete the KFileItems when done?
    bool succeeded;
    bool ignoreMaximumSize;
};

KatPreviewJob::KatPreviewJob( const KFileItemList &items, int width, int height,
    int iconSize, int iconAlpha, bool scale, const QStringList *enabledPlugins, bool deleteItems )
    : KIO::Job( false /* no GUI */ )
{
    d = new KatPreviewJobPrivate;
    d->tOrig = 0;
    d->shmid = -1;
    d->shmaddr = 0;
    d->initialItems = items;
    d->enabledPlugins = enabledPlugins;
    d->width = width;
    d->height = height ? height : width;
    d->cacheWidth = d->width;
    d->cacheHeight = d->height;
    d->iconSize = iconSize;
    d->iconAlpha = iconAlpha;
    d->deleteItems = deleteItems;
    d->bScale = scale;
    d->succeeded = false;
    d->currentItem.item = 0;
    d->ignoreMaximumSize = false;

    // Return to event loop first, determineNextFile() might delete this;
    QTimer::singleShot( 0, this, SLOT( startPreview() ) );
}

void KatPreviewJob::slotPreviewTimeOut()
{
    kdDebug() << "TIMEOUT for " << d->currentItem.item->url() << endl;
    //determineNextFile();
    removeItem( d->currentItem.item );
}

KatPreviewJob::~KatPreviewJob()
{
    if ( d->shmaddr )
    {
        shmdt( (char*)d->shmaddr );
        shmctl( d->shmid, IPC_RMID, 0 );
    }

    delete d;
}

void KatPreviewJob::startPreview()
{
    // Load the list of plugins to determine which mimetypes are supported
    KTrader::OfferList plugins = KTrader::self()->query( "ThumbCreator" );
    QMap<QString, KService::Ptr> mimeMap;

    KTrader::OfferList::ConstIterator end( plugins.end() );
    for ( KTrader::OfferList::ConstIterator it = plugins.begin(); it != end; ++it )
        if ( !d->enabledPlugins || d->enabledPlugins->contains( (*it)->desktopEntryName() ) )
    {
        QStringList mimeTypes = (*it)->property( "MimeTypes" ).toStringList();
        QStringList::ConstIterator endMime(mimeTypes.end() );
        for ( QStringList::ConstIterator mt = mimeTypes.begin(); mt != endMime; ++mt )
            mimeMap.insert( *mt, *it );
    }

    // Look for images and store the items in our todo list :)
    for ( KFileItemListIterator it( d->initialItems ); it.current(); ++it )
    {
        KatPreviewItem item;
        item.item = it.current();
        QMap<QString, KService::Ptr>::ConstIterator plugin = mimeMap.find( it.current()->mimetype() );
        if ( plugin == mimeMap.end() )
        {
            QString mimeType = it.current()->mimetype();
            plugin = mimeMap.find( mimeType.replace( QRegExp( "/.*" ), "/*" ) );
        }
        if ( plugin != mimeMap.end() )
        {
            item.plugin = *plugin;
            d->items.append( item );
        }
        else
        {
            emitFailed( it.current() );
            if ( d->deleteItems )
                delete it.current();
        }
    }

    // Read configuration value for the maximum allowed size
    KConfig* config = KGlobal::config();
    KConfigGroupSaver cgs( config, "PreviewSettings" );
    d->maximumSize = config->readNumEntry( "MaximumSize", 1024*1024 /* 1MB */ );

    determineNextFile();
}

void KatPreviewJob::removeItem( const KFileItem *item )
{
    QValueList<KatPreviewItem>::Iterator end( d->items.end() );
    for ( QValueList<KatPreviewItem>::Iterator it = d->items.begin(); it != end; ++it )
    {
        if ( (*it).item == item )
        {
            d->items.remove( it );
            break;
        }
    }

    if ( d->currentItem.item == item )
    {
        subjobs.first()->kill();
        subjobs.removeFirst();
        determineNextFile();
    }
}

void KatPreviewJob::setIgnoreMaximumSize( bool ignoreSize )
{
    d->ignoreMaximumSize = ignoreSize;
}

void KatPreviewJob::determineNextFile()
{
    if ( d->currentItem.item )
    {
        if ( !d->succeeded )
            emitFailed();

        if ( d->deleteItems )
        {
            delete d->currentItem.item;
            d->currentItem.item = 0L;
        }
    }

    // no more items ?
    if ( d->items.isEmpty() )
    {
        emitResult();
        return;
    }
    else
    {
        // first, stat the orig file
        d->state = KatPreviewJobPrivate::STATE_STATORIG;
        d->currentItem = d->items.first();
        d->succeeded = false;
        d->items.remove( d->items.begin() );
        KIO::Job *job = KIO::stat( d->currentItem.item->url(), false );
        job->addMetaData( "no-auth-prompt", "true" );
        addSubjob( job );

        d->currentItem.previewTimer = new QTimer();
        connect( d->currentItem.previewTimer, SIGNAL( timeout() ),
                 this, SLOT( slotPreviewTimeOut() ) );
        // TIMEOUT seconds to create this item's preview (SINGLE SHOT)
        d->currentItem.previewTimer->start( TIMEOUT * 1000, true );
    }
}

void KatPreviewJob::slotResult( KIO::Job *job )
{
    subjobs.remove( job );
//    Q_ASSERT ( subjobs.isEmpty() ); // We should have only one job at a time
    switch ( d->state )
    {
        case KatPreviewJobPrivate::STATE_STATORIG:
        {
            if ( job->error() )
            {
                kdDebug() << "JOB_ERROR" << endl;

                // Drop this one and move on to the next one
                determineNextFile();
                return;
            }
            KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
            KIO::UDSEntry::ConstIterator it = entry.begin();
            d->tOrig = 0;
            int found = 0;
            KIO::UDSEntry::ConstIterator end( entry.end() );
            for( ; it != end && found < 2; it++ )
            {
                if ( (*it).m_uds == KIO::UDS_MODIFICATION_TIME )
                {
                    d->tOrig = (time_t)((*it).m_long);
                    found++;
                }
                else if ( (*it).m_uds == KIO::UDS_SIZE )
                {
                    if ( filesize_t((*it).m_long) > d->maximumSize &&
                         !d->ignoreMaximumSize &&
                         !d->currentItem.plugin->property( "IgnoreMaximumSize" ).toBool() )
                    {
                        determineNextFile();
                        return;
                    }
                    found++;
                }
            }

            getOrCreateThumbnail();
            return;
        }
        case KatPreviewJobPrivate::STATE_CREATETHUMB:
        {
            if ( !d->tempName.isEmpty() )
            {
                QFile::remove( d->tempName );
                d->tempName = QString::null;
            }
            determineNextFile();
            return;
        }
    }
}

void KatPreviewJob::getOrCreateThumbnail()
{
    KURL currentURL = d->currentItem.item->url();
    createThumbnail( currentURL.path() );
}

void KatPreviewJob::createThumbnail( const QString &pixPath )
{
    d->state = KatPreviewJobPrivate::STATE_CREATETHUMB;
    KURL thumbURL;
    thumbURL.setProtocol( "thumbnail" );
    thumbURL.setPath( pixPath );
    KIO::TransferJob* job = KIO::get( thumbURL, false, false );
    addSubjob( job );
    connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
             SLOT( slotThumbData( KIO::Job*, const QByteArray& ) ) );

    job->addMetaData( "mimeType", d->currentItem.item->mimetype() );
    job->addMetaData( "width", QString().setNum( d->width ) );
    job->addMetaData( "height", QString().setNum( d->height ) );
    job->addMetaData( "iconSize", QString().setNum( d->iconSize ) );
    job->addMetaData( "iconAlpha", QString().setNum( d->iconAlpha ) );
    job->addMetaData( "plugin", d->currentItem.plugin->library() );

    if ( d->shmid == -1 )
    {
        if ( d->shmaddr )
        {
            shmdt( (char*)d->shmaddr );
            shmctl( d->shmid, IPC_RMID, 0 );
        }
        d->shmid = shmget( IPC_PRIVATE, d->cacheWidth * d->cacheHeight * 4, IPC_CREAT|0600 );
        if ( d->shmid != -1 )
        {
            d->shmaddr = static_cast<uchar*>( shmat( d->shmid, 0, SHM_RDONLY ) );
            if ( d->shmaddr == (uchar*)-1 )
            {
                shmctl( d->shmid, IPC_RMID, 0 );
                d->shmaddr = 0;
                d->shmid = -1;
            }
        }
        else
        {
            d->shmaddr = 0;
        }
    }
    if ( d->shmid != -1 )
        job->addMetaData( "shmid", QString().setNum( d->shmid ) );
}

void KatPreviewJob::slotThumbData( KIO::Job *, const QByteArray &data )
{
    QImage thumb;

    if ( d->shmaddr )
    {
        QDataStream str( data, IO_ReadOnly );
        int width, height, depth;
        bool alpha;
        str >> width >> height >> depth >> alpha;
        thumb = QImage( d->shmaddr, width, height, depth, 0, 0, QImage::IgnoreEndian );
        thumb.setAlphaBuffer( alpha );
    }

    emitPreview( thumb );
    d->succeeded = true;
}

void KatPreviewJob::emitPreview( const QImage &thumb )
{
    QPixmap pix;
    if ( thumb.width() > d->width || thumb.height() > d->height )
    {
        double imgRatio = (double)thumb.height() / (double)thumb.width();
        if ( imgRatio > (double)d->height / (double)d->width )
            pix.convertFromImage( thumb.smoothScale((int)QMAX( (double)d->height / imgRatio, 1 ), d->height) );
        else
            pix.convertFromImage( thumb.smoothScale( d->width, (int)QMAX( (double)d->width * imgRatio, 1 ) ) );
    }
    else
    {
        pix.convertFromImage( thumb );
    }
    emit gotPreview( d->currentItem.item, pix );
}

void KatPreviewJob::emitFailed( const KFileItem *item )
{
    if ( !item )
        item = d->currentItem.item;

    emit failed( item );
}

QStringList KatPreviewJob::availablePlugins()
{
    QStringList result;
    KTrader::OfferList plugins = KTrader::self()->query( "ThumbCreator" );
    KTrader::OfferList::ConstIterator end( plugins.end() );
    for ( KTrader::OfferList::ConstIterator it = plugins.begin(); it != end; ++it )
        if ( !result.contains( (*it)->desktopEntryName() ) )
            result.append( (*it)->desktopEntryName() );

    return result;
}

QStringList KatPreviewJob::supportedMimeTypes()
{
    QStringList result;
    KTrader::OfferList plugins = KTrader::self()->query( "ThumbCreator" );
    KTrader::OfferList::ConstIterator end( plugins.end() );

    for ( KTrader::OfferList::ConstIterator it = plugins.begin(); it != end; ++it )
        result += (*it)->property( "MimeTypes" ).toStringList();

    return result;
}

KatPreviewJob* filePreview( const KFileItemList &items, int width, int height,
    int iconSize, int iconAlpha, bool scale, const QStringList *enabledPlugins )
{
    return new KatPreviewJob( items, width, height, iconSize, iconAlpha, scale, enabledPlugins );
}

KatPreviewJob* filePreview( const KURL::List &items, int width, int height,
    int iconSize, int iconAlpha, bool scale, const QStringList *enabledPlugins )
{
    KFileItemList fileItems;
    KURL::List::ConstIterator end( items.end() );
    for ( KURL::List::ConstIterator it = items.begin(); it != end; ++it )
        fileItems.append( new KFileItem( KFileItem::Unknown, KFileItem::Unknown, *it, true ) );

    return new KatPreviewJob( fileItems, width, height, iconSize, iconAlpha,
        scale, enabledPlugins, true );
}

void KatPreviewJob::virtual_hook( int id, void* data )
{
    KIO::Job::virtual_hook( id, data );
}

#include "katpreviewjob.moc"


Generated by  Doxygen 1.6.0   Back to index