// -*- c++ -*- // vim: ts=4 sw=4 et /* This file is part of the KDE libraries Copyright (C) 2000 David Faure 2000 Carsten Pfeiffer 2001 Malte Starostik 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 #ifdef __FreeBSD__ #include #endif #include #ifdef Q_OS_UNIX #include #include #endif #include #include #include #include #include #include // Do not remove, needed for correct bool serialization #include #include #include #include #include #include #include #include #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 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 TDETrader::OfferList plugins = TDETrader::self()->query("ThumbCreator"); TQMap mimeMap; for (TDETrader::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::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/"); TDEStandardDirs::makeDir(d->thumbPath, 0700); } else d->bSave = false; determineNextFile(); } void PreviewJob::removeItem( const KFileItem *item ) { for (TQValueList::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(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; TDETrader::OfferList plugins = TDETrader::self()->query("ThumbCreator"); for (TDETrader::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; TDETrader::OfferList plugins = TDETrader::self()->query("ThumbCreator"); for (TDETrader::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 ); }