summaryrefslogtreecommitdiffstats
path: root/tdeio/tdeio/previewjob.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tdeio/tdeio/previewjob.cpp')
-rw-r--r--tdeio/tdeio/previewjob.cpp597
1 files changed, 597 insertions, 0 deletions
diff --git a/tdeio/tdeio/previewjob.cpp b/tdeio/tdeio/previewjob.cpp
new file mode 100644
index 000000000..539d88bea
--- /dev/null
+++ b/tdeio/tdeio/previewjob.cpp
@@ -0,0 +1,597 @@
+// -*- c++ -*-
+// vim: ts=4 sw=4 et
+/* This file is part of the KDE libraries
+ Copyright (C) 2000 David Faure <faure@kde.org>
+ 2000 Carsten Pfeiffer <pfeiffer@kde.org>
+ 2001 Malte Starostik <malte.starostik@t-online.de>
+
+ 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 "previewjob.h"
+
+#include <sys/stat.h>
+#ifdef __FreeBSD__
+ #include <machine/param.h>
+#endif
+#include <sys/types.h>
+
+#ifdef Q_OS_UNIX
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#endif
+
+#include <tqdir.h>
+#include <tqfile.h>
+#include <tqimage.h>
+#include <tqtimer.h>
+#include <tqregexp.h>
+
+#include <kdatastream.h> // Do not remove, needed for correct bool serialization
+#include <tdefileitem.h>
+#include <kapplication.h>
+#include <ktempfile.h>
+#include <ktrader.h>
+#include <kmdcodec.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+
+#include <tdeio/kservice.h>
+
+#include "previewjob.moc"
+
+namespace TDEIO { struct PreviewItem; }
+using namespace TDEIO;
+
+struct TDEIO::PreviewItem
+{
+ KFileItem *item;
+ KService::Ptr plugin;
+};
+
+struct TDEIO::PreviewJobPrivate
+{
+ enum { STATE_STATORIG, // if the thumbnail exists
+ STATE_GETORIG, // if we create it
+ STATE_CREATETHUMB // thumbnail:/ slave
+ } state;
+ KFileItemList initialItems;
+ const TQStringList *enabledPlugins;
+ // Our todo list :)
+ TQValueList<PreviewItem> items;
+ // The current item
+ PreviewItem currentItem;
+ // The modification time of that URL
+ time_t tOrig;
+ // Path to thumbnail cache for the current size
+ TQString thumbPath;
+ // Original URL of current item in TMS format
+ // (file:///path/to/file instead of file:/path/to/file)
+ TQString origName;
+ // Thumbnail file name for current item
+ TQString thumbName;
+ // Size of thumbnail
+ int width;
+ int height;
+ // Unscaled size of thumbnail (128 or 256 if cache is enabled)
+ int cacheWidth;
+ int cacheHeight;
+ // Whether the thumbnail should be scaled
+ bool bScale;
+ // Whether we should save the thumbnail
+ bool bSave;
+ // If the file to create a thumb for was a temp file, this is its name
+ TQString tempName;
+ // Over that, it's too much
+ unsigned long maximumSize;
+ // the size for the icon overlay
+ int iconSize;
+ // the transparency of the blended mimetype icon
+ int iconAlpha;
+ // 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;
+ // And the data area
+ uchar *shmaddr;
+ // Delete the KFileItems when done?
+ bool deleteItems;
+ bool succeeded;
+ // Root of thumbnail cache
+ TQString thumbRoot;
+ bool ignoreMaximumSize;
+ TQTimer startPreviewTimer;
+};
+
+PreviewJob::PreviewJob( const KFileItemList &items, int width, int height,
+ int iconSize, int iconAlpha, bool scale, bool save,
+ const TQStringList *enabledPlugins, bool deleteItems )
+ : TDEIO::Job( false /* no GUI */ )
+{
+ d = new PreviewJobPrivate;
+ 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->bSave = save && scale;
+ d->succeeded = false;
+ d->currentItem.item = 0;
+ d->thumbRoot = TQDir::homeDirPath() + "/.thumbnails/";
+ d->ignoreMaximumSize = false;
+
+ // Return to event loop first, determineNextFile() might delete this;
+ connect(&d->startPreviewTimer, TQT_SIGNAL(timeout()), TQT_SLOT(startPreview()) );
+ d->startPreviewTimer.start(0, true);
+}
+
+PreviewJob::~PreviewJob()
+{
+#ifdef Q_OS_UNIX
+ if (d->shmaddr) {
+ shmdt((char*)d->shmaddr);
+ shmctl(d->shmid, IPC_RMID, 0);
+ }
+#endif
+ delete d;
+}
+
+void PreviewJob::startPreview()
+{
+ // Load the list of plugins to determine which mimetypes are supported
+ KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
+ TQMap<TQString, KService::Ptr> mimeMap;
+
+ for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
+ if (!d->enabledPlugins || d->enabledPlugins->contains((*it)->desktopEntryName()))
+ {
+ TQStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
+ for (TQStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
+ mimeMap.insert(*mt, *it);
+ }
+
+ // Look for images and store the items in our todo list :)
+ bool bNeedCache = false;
+ for (KFileItemListIterator it(d->initialItems); it.current(); ++it )
+ {
+ PreviewItem item;
+ item.item = it.current();
+ TQMap<TQString, KService::Ptr>::ConstIterator plugin = mimeMap.find(it.current()->mimetype());
+ if (plugin == mimeMap.end()
+ && (it.current()->mimetype() != "application/x-desktop")
+ && (it.current()->mimetype() != "media/builtin-mydocuments")
+ && (it.current()->mimetype() != "media/builtin-mycomputer")
+ && (it.current()->mimetype() != "media/builtin-mynetworkplaces")
+ && (it.current()->mimetype() != "media/builtin-printers")
+ && (it.current()->mimetype() != "media/builtin-trash")
+ && (it.current()->mimetype() != "media/builtin-webbrowser"))
+ {
+ TQString mimeType = it.current()->mimetype();
+ plugin = mimeMap.find(mimeType.replace(TQRegExp("/.*"), "/*"));
+
+ if (plugin == mimeMap.end())
+ {
+ // check mime type inheritance
+ KMimeType::Ptr mimeInfo = KMimeType::mimeType(it.current()->mimetype());
+ TQString parentMimeType = mimeInfo->parentMimeType();
+ while (!parentMimeType.isEmpty())
+ {
+ plugin = mimeMap.find(parentMimeType);
+ if (plugin != mimeMap.end()) break;
+
+ KMimeType::Ptr parentMimeInfo = KMimeType::mimeType(parentMimeType);
+ if (!parentMimeInfo) break;
+
+ parentMimeType = parentMimeInfo->parentMimeType();
+ }
+ }
+
+ if (plugin == mimeMap.end())
+ {
+ // check X-TDE-Text property
+ KMimeType::Ptr mimeInfo = KMimeType::mimeType(it.current()->mimetype());
+ TQVariant textProperty = mimeInfo->property("X-TDE-text");
+ if (textProperty.isValid() && textProperty.type() == TQVariant::Bool)
+ {
+ if (textProperty.toBool())
+ {
+ plugin = mimeMap.find("text/plain");
+ if (plugin == mimeMap.end())
+ {
+ plugin = mimeMap.find( "text/*" );
+ }
+ }
+ }
+ }
+ }
+
+ if (plugin != mimeMap.end())
+ {
+ item.plugin = *plugin;
+ d->items.append(item);
+ if (!bNeedCache && d->bSave &&
+ (it.current()->url().protocol() != "file" ||
+ !it.current()->url().directory( false ).startsWith(d->thumbRoot)) &&
+ (*plugin)->property("CacheThumbnail").toBool())
+ bNeedCache = true;
+ }
+ else
+ {
+ emitFailed(it.current());
+ if (d->deleteItems)
+ delete it.current();
+ }
+ }
+
+ // Read configuration value for the maximum allowed size
+ TDEConfig * config = TDEGlobal::config();
+ TDEConfigGroupSaver cgs( config, "PreviewSettings" );
+ d->maximumSize = config->readNumEntry( "MaximumSize", 1024*1024 /* 1MB */ );
+
+ if (bNeedCache)
+ {
+ if (d->width <= 128 && d->height <= 128) d->cacheWidth = d->cacheHeight = 128;
+ else d->cacheWidth = d->cacheHeight = 256;
+ d->thumbPath = d->thumbRoot + (d->cacheWidth == 128 ? "normal/" : "large/");
+ KStandardDirs::makeDir(d->thumbPath, 0700);
+ }
+ else
+ d->bSave = false;
+ determineNextFile();
+}
+
+void PreviewJob::removeItem( const KFileItem *item )
+{
+ for (TQValueList<PreviewItem>::Iterator it = d->items.begin(); it != d->items.end(); ++it)
+ if ((*it).item == item)
+ {
+ d->items.remove(it);
+ break;
+ }
+
+ if (d->currentItem.item == item)
+ {
+ subjobs.first()->kill();
+ subjobs.removeFirst();
+ determineNextFile();
+ }
+}
+
+void PreviewJob::setIgnoreMaximumSize(bool ignoreSize)
+{
+ d->ignoreMaximumSize = ignoreSize;
+}
+
+void PreviewJob::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 = PreviewJobPrivate::STATE_STATORIG;
+ d->currentItem = d->items.first();
+ d->succeeded = false;
+ d->items.remove(d->items.begin());
+ TDEIO::Job *job = TDEIO::stat( d->currentItem.item->url(), false );
+ job->addMetaData( "no-auth-prompt", "true" );
+ addSubjob(job);
+ }
+}
+
+void PreviewJob::slotResult( TDEIO::Job *job )
+{
+ subjobs.remove( job );
+ Q_ASSERT ( subjobs.isEmpty() ); // We should have only one job at a time ...
+ switch ( d->state )
+ {
+ case PreviewJobPrivate::STATE_STATORIG:
+ {
+ if (job->error()) // that's no good news...
+ {
+ // Drop this one and move on to the next one
+ determineNextFile();
+ return;
+ }
+ TDEIO::UDSEntry entry = ((TDEIO::StatJob*)job)->statResult();
+ TDEIO::UDSEntry::ConstIterator it = entry.begin();
+ d->tOrig = 0;
+ int found = 0;
+ for( ; it != entry.end() && found < 2; it++ )
+ {
+ if ( (*it).m_uds == TDEIO::UDS_MODIFICATION_TIME )
+ {
+ d->tOrig = (time_t)((*it).m_long);
+ found++;
+ }
+ else if ( (*it).m_uds == TDEIO::UDS_SIZE )
+ {
+ if ( filesize_t((*it).m_long) > d->maximumSize &&
+ !d->ignoreMaximumSize &&
+ !d->currentItem.plugin->property("IgnoreMaximumSize").toBool() )
+ {
+ determineNextFile();
+ return;
+ }
+ found++;
+ }
+ }
+
+ if ( !d->currentItem.plugin->property( "CacheThumbnail" ).toBool() )
+ {
+ // This preview will not be cached, no need to look for a saved thumbnail
+ // Just create it, and be done
+ getOrCreateThumbnail();
+ return;
+ }
+
+ if ( statResultThumbnail() )
+ return;
+
+ getOrCreateThumbnail();
+ return;
+ }
+ case PreviewJobPrivate::STATE_GETORIG:
+ {
+ if (job->error())
+ {
+ determineNextFile();
+ return;
+ }
+
+ createThumbnail( static_cast<TDEIO::FileCopyJob*>(job)->destURL().path() );
+ return;
+ }
+ case PreviewJobPrivate::STATE_CREATETHUMB:
+ {
+ if (!d->tempName.isEmpty())
+ {
+ TQFile::remove(d->tempName);
+ d->tempName = TQString::null;
+ }
+ determineNextFile();
+ return;
+ }
+ }
+}
+
+bool PreviewJob::statResultThumbnail()
+{
+ if ( d->thumbPath.isEmpty() )
+ return false;
+
+ KURL url = d->currentItem.item->url();
+ // Don't include the password if any
+ url.setPass(TQString::null);
+ // The TMS defines local files as file:///path/to/file instead of KDE's
+ // way (file:/path/to/file)
+#ifdef KURL_TRIPLE_SLASH_FILE_PROT
+ d->origName = url.url();
+#else
+ if (url.protocol() == "file") d->origName = "file://" + url.path();
+ else d->origName = url.url();
+#endif
+
+ KMD5 md5( TQFile::encodeName( d->origName ).data() );
+ d->thumbName = TQFile::encodeName( md5.hexDigest() ) + ".png";
+
+ TQImage thumb;
+ if ( !thumb.load( d->thumbPath + d->thumbName ) ) return false;
+
+ if ( thumb.text( "Thumb::URI", 0 ) != d->origName ||
+ thumb.text( "Thumb::MTime", 0 ).toInt() != d->tOrig ) return false;
+
+ // Found it, use it
+ emitPreview( thumb );
+ d->succeeded = true;
+ determineNextFile();
+ return true;
+}
+
+
+void PreviewJob::getOrCreateThumbnail()
+{
+ // We still need to load the orig file ! (This is getting tedious) :)
+ const KFileItem* item = d->currentItem.item;
+ const TQString localPath = item->localPath();
+ if ( !localPath.isEmpty() )
+ createThumbnail( localPath );
+ else
+ {
+ d->state = PreviewJobPrivate::STATE_GETORIG;
+ KTempFile localFile;
+ KURL localURL;
+ localURL.setPath( d->tempName = localFile.name() );
+ const KURL currentURL = item->url();
+ TDEIO::Job * job = TDEIO::file_copy( currentURL, localURL, -1, true,
+ false, false /* No GUI */ );
+ job->addMetaData("thumbnail","1");
+ addSubjob(job);
+ }
+}
+
+// KDE 4: Make it const TQString &
+void PreviewJob::createThumbnail( TQString pixPath )
+{
+ d->state = PreviewJobPrivate::STATE_CREATETHUMB;
+ KURL thumbURL;
+ thumbURL.setProtocol("thumbnail");
+ thumbURL.setPath(pixPath);
+ TDEIO::TransferJob *job = TDEIO::get(thumbURL, false, false);
+ addSubjob(job);
+ connect(job, TQT_SIGNAL(data(TDEIO::Job *, const TQByteArray &)), TQT_SLOT(slotThumbData(TDEIO::Job *, const TQByteArray &)));
+ bool save = d->bSave && d->currentItem.plugin->property("CacheThumbnail").toBool();
+ job->addMetaData("mimeType", d->currentItem.item->mimetype());
+ job->addMetaData("width", TQString().setNum(save ? d->cacheWidth : d->width));
+ job->addMetaData("height", TQString().setNum(save ? d->cacheHeight : d->height));
+ job->addMetaData("iconSize", TQString().setNum(save ? 64 : d->iconSize));
+ job->addMetaData("iconAlpha", TQString().setNum(d->iconAlpha));
+ job->addMetaData("plugin", d->currentItem.plugin->library());
+#ifdef Q_OS_UNIX
+ 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 = (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", TQString().setNum(d->shmid));
+#endif
+}
+
+void PreviewJob::slotThumbData(TDEIO::Job *, const TQByteArray &data)
+{
+ bool save = d->bSave &&
+ d->currentItem.plugin->property("CacheThumbnail").toBool() &&
+ (d->currentItem.item->url().protocol() != "file" ||
+ !d->currentItem.item->url().directory( false ).startsWith(d->thumbRoot));
+ TQImage thumb;
+#ifdef Q_OS_UNIX
+ if (d->shmaddr)
+ {
+ TQDataStream str(data, IO_ReadOnly);
+ int width, height, depth;
+ bool alpha;
+ str >> width >> height >> depth >> alpha;
+ thumb = TQImage(d->shmaddr, width, height, depth, 0, 0, TQImage::IgnoreEndian);
+ thumb.setAlphaBuffer(alpha);
+ }
+ else
+#endif
+ thumb.loadFromData(data);
+
+ if (save)
+ {
+ thumb.setText("Thumb::URI", 0, d->origName);
+ thumb.setText("Thumb::MTime", 0, TQString::number(d->tOrig));
+ thumb.setText("Thumb::Size", 0, number(d->currentItem.item->size()));
+ thumb.setText("Thumb::Mimetype", 0, d->currentItem.item->mimetype());
+ thumb.setText("Software", 0, "KDE Thumbnail Generator");
+ KTempFile temp(d->thumbPath + "kde-tmp-", ".png");
+ if (temp.status() == 0) //Only try to write out the thumbnail if we
+ { //actually created the temp file.
+ thumb.save(temp.name(), "PNG");
+ rename(TQFile::encodeName(temp.name()), TQFile::encodeName(d->thumbPath + d->thumbName));
+ }
+ }
+ emitPreview( thumb );
+ d->succeeded = true;
+}
+
+void PreviewJob::emitPreview(const TQImage &thumb)
+{
+ TQPixmap 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)TQMAX((double)d->height / imgRatio, 1), d->height));
+ else pix.convertFromImage(
+ thumb.smoothScale(d->width, (int)TQMAX((double)d->width * imgRatio, 1)));
+ }
+ else pix.convertFromImage(thumb);
+ emit gotPreview(d->currentItem.item, pix);
+}
+
+void PreviewJob::emitFailed(const KFileItem *item)
+{
+ if (!item)
+ item = d->currentItem.item;
+ emit failed(item);
+}
+
+TQStringList PreviewJob::availablePlugins()
+{
+ TQStringList result;
+ KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
+ for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
+ if (!result.contains((*it)->desktopEntryName()))
+ result.append((*it)->desktopEntryName());
+ return result;
+}
+
+TQStringList PreviewJob::supportedMimeTypes()
+{
+ TQStringList result;
+ KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
+ for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
+ result += (*it)->property("MimeTypes").toStringList();
+ return result;
+}
+
+void PreviewJob::kill( bool quietly )
+{
+ d->startPreviewTimer.stop();
+ Job::kill( quietly );
+}
+
+PreviewJob *TDEIO::filePreview( const KFileItemList &items, int width, int height,
+ int iconSize, int iconAlpha, bool scale, bool save,
+ const TQStringList *enabledPlugins )
+{
+ return new PreviewJob(items, width, height, iconSize, iconAlpha,
+ scale, save, enabledPlugins);
+}
+
+PreviewJob *TDEIO::filePreview( const KURL::List &items, int width, int height,
+ int iconSize, int iconAlpha, bool scale, bool save,
+ const TQStringList *enabledPlugins )
+{
+ KFileItemList fileItems;
+ for (KURL::List::ConstIterator it = items.begin(); it != items.end(); ++it)
+ fileItems.append(new KFileItem(KFileItem::Unknown, KFileItem::Unknown, *it, true));
+ return new PreviewJob(fileItems, width, height, iconSize, iconAlpha,
+ scale, save, enabledPlugins, true);
+}
+
+void PreviewJob::virtual_hook( int id, void* data )
+{ TDEIO::Job::virtual_hook( id, data ); }
+