From 59ecea8a75cf15c6e1da946d41443d343b51d6c2 Mon Sep 17 00:00:00 2001 From: Yoonsang Lee Date: Wed, 4 Feb 2015 17:43:40 +0900 Subject: [PATCH] Add features to download images over http protocol. - Add ResourceThreadImage::Download() to download an image using libcurl. - Add ResoureThreadBase::RequestDownload, ResourceThreadBase::Download() - Refactor ResoureThreadImage::Load(), Decode(). - Add one more thread object, mThreadImageRemote, to ResourceBitmapRequester to download images over http protocol in a seperate thread. Without this seperate thread, local image files which can be loaded very shortly may be delayed until other time-consuming remote images loading is finished. - Employ libcurl to download files over http protocol. Change-Id: Ie10e5b0b1fa374af74a48ad2c9f0df301d27fe5e --- build/tizen/adaptor/Makefile.am | 2 + build/tizen/configure.ac | 1 + packaging/dali-adaptor-mobile.spec | 1 + packaging/dali-adaptor-tv.spec | 1 + packaging/dali-adaptor-wearable.spec | 1 + packaging/dali-adaptor.spec | 1 + .../resource-loader/resource-bitmap-requester.cpp | 50 ++++-- .../resource-loader/resource-bitmap-requester.h | 3 +- .../slp/resource-loader/resource-thread-base.cpp | 13 ++ .../slp/resource-loader/resource-thread-base.h | 8 + .../resource-thread-distance-field.h | 2 +- .../slp/resource-loader/resource-thread-image.cpp | 200 ++++++++++++++++----- .../slp/resource-loader/resource-thread-image.h | 28 ++- 13 files changed, 255 insertions(+), 56 deletions(-) diff --git a/build/tizen/adaptor/Makefile.am b/build/tizen/adaptor/Makefile.am index 7b1bbc1..bce2054 100644 --- a/build/tizen/adaptor/Makefile.am +++ b/build/tizen/adaptor/Makefile.am @@ -235,6 +235,7 @@ libdali_adaptor_la_CXXFLAGS = \ $(SENSOR_CFLAGS) \ $(LIBDRM_CFLAGS) \ $(LIBEXIF_CFLAGS) \ + $(LIBCURL_CFLAGS) \ $(ASSIMP_CFLAGS) \ $(CAPI_SYSTEM_SYSTEM_SETTINGS_CFLAGS) @@ -254,6 +255,7 @@ libdali_adaptor_la_LIBADD = \ $(SENSOR_LIBS) \ $(LIBDRM_LIBS) \ $(LIBEXIF_LIBS) \ + $(LIBCURL_LIBS) \ $(CAPI_SYSTEM_SYSTEM_SETTINGS_LIBS) \ $(CAPI_APPFW_APPLICATION_LIBS) \ -lgif \ diff --git a/build/tizen/configure.ac b/build/tizen/configure.ac index 3b0590f..6aec94b 100644 --- a/build/tizen/configure.ac +++ b/build/tizen/configure.ac @@ -44,6 +44,7 @@ PKG_CHECK_MODULES(PNG, libpng) PKG_CHECK_MODULES(XML, libxml-2.0) PKG_CHECK_MODULES(LIBEXIF, libexif) PKG_CHECK_MODULES(LIBDRM, libdrm) +PKG_CHECK_MODULES(LIBCURL, libcurl) # Check for availability of BulletPhysics PKG_CHECK_EXISTS(bullet, [ diff --git a/packaging/dali-adaptor-mobile.spec b/packaging/dali-adaptor-mobile.spec index aeb9277..59b533d 100644 --- a/packaging/dali-adaptor-mobile.spec +++ b/packaging/dali-adaptor-mobile.spec @@ -44,6 +44,7 @@ BuildRequires: pkgconfig(capi-system-system-settings) BuildRequires: pkgconfig(libpng) BuildRequires: pkgconfig(opengl-es-20) BuildRequires: pkgconfig(efl-assist) +BuildRequires: libcurl-devel %if 0%{?dali_assimp_plugin} BuildRequires: pkgconfig(assimp) diff --git a/packaging/dali-adaptor-tv.spec b/packaging/dali-adaptor-tv.spec index 3269568..5138b01 100644 --- a/packaging/dali-adaptor-tv.spec +++ b/packaging/dali-adaptor-tv.spec @@ -41,6 +41,7 @@ BuildRequires: pkgconfig(xfixes) BuildRequires: pkgconfig(xdamage) BuildRequires: pkgconfig(utilX) BuildRequires: pkgconfig(gles20) +BuildRequires: libcurl-devel %if 0%{?dali_assimp_plugin} BuildRequires: pkgconfig(assimp) diff --git a/packaging/dali-adaptor-wearable.spec b/packaging/dali-adaptor-wearable.spec index a56f880..8ed97ec 100644 --- a/packaging/dali-adaptor-wearable.spec +++ b/packaging/dali-adaptor-wearable.spec @@ -43,6 +43,7 @@ BuildRequires: pkgconfig(capi-system-system-settings) BuildRequires: pkgconfig(libpng) BuildRequires: pkgconfig(gles20) BuildRequires: pkgconfig(efl-assist) +BuildRequires: libcurl-devel %if 0%{?dali_assimp_plugin} BuildRequires: pkgconfig(assimp) diff --git a/packaging/dali-adaptor.spec b/packaging/dali-adaptor.spec index 0ae856c..05d1773 100644 --- a/packaging/dali-adaptor.spec +++ b/packaging/dali-adaptor.spec @@ -65,6 +65,7 @@ BuildRequires: pkgconfig(capi-system-system-settings) BuildRequires: pkgconfig(libpng) BuildRequires: pkgconfig(glesv2) BuildRequires: pkgconfig(egl) +BuildRequires: libcurl-devel %if %{with wayland} BuildRequires: pkgconfig(ecore-wayland) diff --git a/platform-abstractions/slp/resource-loader/resource-bitmap-requester.cpp b/platform-abstractions/slp/resource-loader/resource-bitmap-requester.cpp index fd08ba2..e9523fe 100644 --- a/platform-abstractions/slp/resource-loader/resource-bitmap-requester.cpp +++ b/platform-abstractions/slp/resource-loader/resource-bitmap-requester.cpp @@ -28,25 +28,29 @@ namespace SlpPlatform ResourceBitmapRequester::ResourceBitmapRequester( ResourceLoader& resourceLoader ) : ResourceRequesterBase( resourceLoader ) { - mThreadImage = new ResourceThreadImage( resourceLoader ); + mThreadImageLocal = new ResourceThreadImage( resourceLoader, false ); + mThreadImageRemote = new ResourceThreadImage( resourceLoader, true ); mThreadDistanceField = new ResourceThreadDistanceField( resourceLoader ); } ResourceBitmapRequester::~ResourceBitmapRequester() { - delete mThreadImage; + delete mThreadImageLocal; + delete mThreadImageRemote; delete mThreadDistanceField; } void ResourceBitmapRequester::Pause() { - mThreadImage->Pause(); + mThreadImageLocal->Pause(); + mThreadImageRemote->Pause(); mThreadDistanceField->Pause(); } void ResourceBitmapRequester::Resume() { - mThreadImage->Resume(); + mThreadImageLocal->Resume(); + mThreadImageRemote->Resume(); mThreadDistanceField->Resume(); } @@ -56,13 +60,38 @@ void ResourceBitmapRequester::LoadResource( Integration::ResourceRequest& reques BitmapResourceType* resType = static_cast(request.GetType()); if( resType ) { - // Work out if the resource is in memory or a file: - const ResourceThreadBase::RequestType requestType = request.GetResource().Get() ? ResourceThreadBase::RequestDecode : ResourceThreadBase::RequestLoad; - // Work out what thread to decode / load the image on: - ResourceThreadBase* const imageThread = mThreadImage; + ResourceThreadBase* const localImageThread = mThreadImageLocal; + ResourceThreadBase* const remoteImageThread = mThreadImageRemote; ResourceThreadBase* const distanceFieldThread = mThreadDistanceField ; - ResourceThreadBase* const workerThread = ( !resType->imageAttributes.IsDistanceField() ) ? imageThread : distanceFieldThread; + ResourceThreadBase* workerThread; + + // Work out if the resource is in memory, a file, or in a remote server: + ResourceThreadBase::RequestType requestType; + if( request.GetResource().Get() ) + { + requestType = ResourceThreadBase::RequestDecode; + workerThread = localImageThread; + } + else + { + const std::string& resourcePath = request.GetPath(); + if( resourcePath.length() > 7 && strncasecmp( resourcePath.c_str(), "http://", 7 ) == 0 ) + { + requestType = ResourceThreadBase::RequestDownload; + workerThread = remoteImageThread; + } + else + { + requestType = ResourceThreadBase::RequestLoad; + workerThread = localImageThread; + } + } + + if( resType->imageAttributes.IsDistanceField() ) + { + workerThread = distanceFieldThread; + } // Dispatch the job to the right thread: workerThread->AddRequest( request, requestType ); @@ -82,7 +111,8 @@ void ResourceBitmapRequester::SaveResource(const Integration::ResourceRequest& r void ResourceBitmapRequester::CancelLoad(Integration::ResourceId id, Integration::ResourceTypeId typeId) { - mThreadImage->CancelRequest(id); + mThreadImageLocal->CancelRequest(id); + mThreadImageRemote->CancelRequest(id); mThreadDistanceField->CancelRequest(id); } diff --git a/platform-abstractions/slp/resource-loader/resource-bitmap-requester.h b/platform-abstractions/slp/resource-loader/resource-bitmap-requester.h index b012504..a1158f9 100644 --- a/platform-abstractions/slp/resource-loader/resource-bitmap-requester.h +++ b/platform-abstractions/slp/resource-loader/resource-bitmap-requester.h @@ -75,7 +75,8 @@ public: virtual void CancelLoad(Integration::ResourceId id, Integration::ResourceTypeId typeId); protected: - ResourceThreadImage* mThreadImage; ///< Image loader thread object + ResourceThreadImage* mThreadImageLocal; ///< Image loader thread object to load images in local machine + ResourceThreadImage* mThreadImageRemote; ///< Image loader thread object to download images in remote http server ResourceThreadDistanceField* mThreadDistanceField; ///< Distance field generator thread. }; diff --git a/platform-abstractions/slp/resource-loader/resource-thread-base.cpp b/platform-abstractions/slp/resource-loader/resource-thread-base.cpp index 1708b24..7a8e4e4 100644 --- a/platform-abstractions/slp/resource-loader/resource-thread-base.cpp +++ b/platform-abstractions/slp/resource-loader/resource-thread-base.cpp @@ -272,6 +272,12 @@ void ResourceThreadBase::ProcessNextRequest() } break; + case RequestDownload: + { + Download(*request); + } + break; + case RequestDecode: { Decode(*request); @@ -299,6 +305,13 @@ void ResourceThreadBase::UninstallLogging() Dali::Integration::Log::UninstallLogFunction(); } +void ResourceThreadBase::Download(const Integration::ResourceRequest& request) +{ + DALI_LOG_TRACE_METHOD(mLogFilter); + DALI_LOG_WARNING("Resource Downloading from a remote server not supported for this type."); + ///! If you need this for a subclassed thread, look to ResourceThreadImage::Download() for an example implementation. +} + void ResourceThreadBase::Decode(const Integration::ResourceRequest& request) { DALI_LOG_TRACE_METHOD(mLogFilter); diff --git a/platform-abstractions/slp/resource-loader/resource-thread-base.h b/platform-abstractions/slp/resource-loader/resource-thread-base.h index 4dab9d7..ae5aadd 100644 --- a/platform-abstractions/slp/resource-loader/resource-thread-base.h +++ b/platform-abstractions/slp/resource-loader/resource-thread-base.h @@ -48,6 +48,8 @@ public: { /** Pull a resource out of the platform's file system. */ RequestLoad, + /** Pull a resource over http protocol. */ + RequestDownload, /** Pull a resource out of a memory buffer. */ RequestDecode, /** Push a resource's data out to the file system. */ @@ -127,6 +129,12 @@ protected: virtual void Load(const Integration::ResourceRequest& request) = 0; /** + * Download a resource + * @param[in] request The requested resource/file url and attributes + */ + virtual void Download(const Integration::ResourceRequest& request); + + /** * Decode a resource exactly as if it were being loaded but source its data * from a memory buffer attached directly to the request object. * @param[in] request The requested resource data and attributes diff --git a/platform-abstractions/slp/resource-loader/resource-thread-distance-field.h b/platform-abstractions/slp/resource-loader/resource-thread-distance-field.h index 8884c57..82a34a7 100644 --- a/platform-abstractions/slp/resource-loader/resource-thread-distance-field.h +++ b/platform-abstractions/slp/resource-loader/resource-thread-distance-field.h @@ -61,7 +61,7 @@ private: */ virtual void Save(const Integration::ResourceRequest& request); -}; // class ResourceThreadImage +}; // class ResourceThreadDistanceField } // namespace SlpPlatform diff --git a/platform-abstractions/slp/resource-loader/resource-thread-image.cpp b/platform-abstractions/slp/resource-loader/resource-thread-image.cpp index 76ce3a0..a339807 100755 --- a/platform-abstractions/slp/resource-loader/resource-thread-image.cpp +++ b/platform-abstractions/slp/resource-loader/resource-thread-image.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "portable/file-closer.h" #include "image-loaders/image-loader.h" @@ -32,7 +33,7 @@ namespace Dali namespace SlpPlatform { -ResourceThreadImage::ResourceThreadImage(ResourceLoader& resourceLoader) +ResourceThreadImage::ResourceThreadImage(ResourceLoader& resourceLoader, bool forRemoteImage) : ResourceThreadBase(resourceLoader) { } @@ -46,6 +47,142 @@ void ResourceThreadImage::Load(const ResourceRequest& request) DALI_LOG_TRACE_METHOD( mLogFilter ); DALI_LOG_INFO( mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str() ); + LoadImageFromLocalFile(request); +} + +void ResourceThreadImage::Download(const ResourceRequest& request) +{ + bool succeeded; + + DALI_LOG_TRACE_METHOD( mLogFilter ); + DALI_LOG_INFO( mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str() ); + + Dali::Vector dataBuffer; + size_t dataSize; + succeeded = DownloadRemoteImageIntoMemory( request, dataBuffer, dataSize ); + if( succeeded ) + { + DecodeImageFromMemory(static_cast(&dataBuffer[0]), dataBuffer.Size(), request); + } +} + +void ResourceThreadImage::Decode(const ResourceRequest& request) +{ + DALI_LOG_TRACE_METHOD( mLogFilter ); + DALI_LOG_INFO(mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str()); + + // Get the blob of binary data that we need to decode: + DALI_ASSERT_DEBUG( request.GetResource() ); + + DALI_ASSERT_DEBUG( 0 != dynamic_cast*>( request.GetResource().Get() ) && "Only blobs of binary data can be decoded." ); + Dali::RefCountedVector* const encodedBlob = reinterpret_cast*>( request.GetResource().Get() ); + + if( 0 != encodedBlob ) + { + const size_t blobSize = encodedBlob->GetVector().Size(); + uint8_t * const blobBytes = &(encodedBlob->GetVector()[0]); + DecodeImageFromMemory(blobBytes, blobSize, request); + } + else + { + FailedResource resource(request.GetId(), FailureUnknown); + mResourceLoader.AddFailedLoad(resource); + } +} + +void ResourceThreadImage::Save(const Integration::ResourceRequest& request) +{ + DALI_LOG_TRACE_METHOD( mLogFilter ); + DALI_ASSERT_DEBUG( request.GetType()->id == ResourceBitmap ); + DALI_LOG_WARNING( "Image saving not supported on background resource threads." ); +} + +bool ResourceThreadImage::DownloadRemoteImageIntoMemory(const Integration::ResourceRequest& request, Dali::Vector& dataBuffer, size_t& dataSize) +{ + bool succeeded = true; + CURLcode cresult; + + CURL* curl_handle = curl_easy_init(); + curl_easy_setopt( curl_handle, CURLOPT_VERBOSE, 0 ); + curl_easy_setopt( curl_handle, CURLOPT_URL, request.GetPath().c_str() ); + curl_easy_setopt( curl_handle, CURLOPT_FAILONERROR, 1 ); + + // Download header first to get data size + char* headerBytes = NULL; + size_t headerSize = 0; + FILE* header_fp = open_memstream( &headerBytes, &headerSize ); + double size; + + if( NULL != header_fp) + { + curl_easy_setopt( curl_handle, CURLOPT_HEADER, 1 ); + curl_easy_setopt( curl_handle, CURLOPT_NOBODY, 1 ); + curl_easy_setopt( curl_handle, CURLOPT_WRITEDATA, header_fp ); + + cresult = curl_easy_perform( curl_handle ); + if( cresult == CURLE_OK ) + { + curl_easy_getinfo( curl_handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &size ); + } + else + { + DALI_LOG_WARNING( "Failed to download file to load \"%s\"\n", request.GetPath().c_str() ); + succeeded = false; + } + + fclose( header_fp ); + } + else + { + succeeded = false; + } + + if( NULL != headerBytes ) + { + free( headerBytes ); + } + + if( succeeded ) + { + // Download file data + dataSize = static_cast( size ); + dataBuffer.Reserve( dataSize ); + dataBuffer.Resize( dataSize ); + + Dali::Internal::Platform::FileCloser fileCloser( static_cast(&dataBuffer[0]), dataSize, "wb" ); + FILE* data_fp = fileCloser.GetFile(); + if( NULL != data_fp ) + { + curl_easy_setopt( curl_handle, CURLOPT_HEADER, 0 ); + curl_easy_setopt( curl_handle, CURLOPT_NOBODY, 0 ); + curl_easy_setopt( curl_handle, CURLOPT_WRITEDATA, data_fp ); + + cresult = curl_easy_perform( curl_handle ); + if( CURLE_OK != cresult ) + { + DALI_LOG_WARNING( "Failed to download file to load \"%s\"\n", request.GetPath().c_str() ); + succeeded = false; + } + } + else + { + succeeded = false; + } + } + + curl_easy_cleanup( curl_handle ); + + if( !succeeded ) + { + FailedResource resource(request.GetId(), FailureUnknown); + mResourceLoader.AddFailedLoad(resource); + } + + return succeeded; +} + +void ResourceThreadImage::LoadImageFromLocalFile(const Integration::ResourceRequest& request) +{ bool fileNotFound = false; BitmapPtr bitmap = 0; bool result = false; @@ -53,7 +190,7 @@ void ResourceThreadImage::Load(const ResourceRequest& request) Dali::Internal::Platform::FileCloser fileCloser( request.GetPath().c_str(), "rb" ); FILE * const fp = fileCloser.GetFile(); - if( fp != NULL ) + if( NULL != fp ) { result = ImageLoader::ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, *this, bitmap ); // Last chance to interrupt a cancelled load before it is reported back to clients @@ -92,46 +229,31 @@ void ResourceThreadImage::Load(const ResourceRequest& request) } } -void ResourceThreadImage::Decode(const ResourceRequest& request) +void ResourceThreadImage::DecodeImageFromMemory(void* blobBytes, size_t blobSize, const Integration::ResourceRequest& request) { - DALI_LOG_TRACE_METHOD( mLogFilter ); - DALI_LOG_INFO(mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str()); - BitmapPtr bitmap = 0; - // Get the blob of binary data that we need to decode: - DALI_ASSERT_DEBUG( request.GetResource() ); - - DALI_ASSERT_DEBUG( 0 != dynamic_cast*>( request.GetResource().Get() ) && "Only blobs of binary data can be decoded." ); - Dali::RefCountedVector* const encodedBlob = reinterpret_cast*>( request.GetResource().Get() ); + DALI_ASSERT_DEBUG( blobSize > 0U ); + DALI_ASSERT_DEBUG( blobBytes != 0U ); - if( encodedBlob != 0 ) + if( blobBytes != 0 && blobSize > 0U ) { - const size_t blobSize = encodedBlob->GetVector().Size(); - uint8_t * const blobBytes = &(encodedBlob->GetVector()[0]); - DALI_ASSERT_DEBUG( blobSize > 0U ); - DALI_ASSERT_DEBUG( blobBytes != 0U ); - - if( blobBytes != 0 && blobSize > 0U ) + // Open a file handle on the memory buffer: + Dali::Internal::Platform::FileCloser fileCloser( blobBytes, blobSize, "rb" ); + FILE * const fp = fileCloser.GetFile(); + if ( NULL != fp ) { - // Open a file handle on the memory buffer: - Dali::Internal::Platform::FileCloser fileCloser( blobBytes, blobSize, "rb" ); - FILE * const fp = fileCloser.GetFile(); - if ( fp != NULL ) + bool result = ImageLoader::ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, StubbedResourceLoadingClient(), bitmap ); + if ( result && bitmap ) + { + // Construct LoadedResource and ResourcePointer for image data + LoadedResource resource( request.GetId(), request.GetType()->id, ResourcePointer( bitmap.Get() ) ); + // Queue the loaded resource + mResourceLoader.AddLoadedResource( resource ); + } + else { - bool result = ImageLoader::ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, StubbedResourceLoadingClient(), bitmap ); - - if ( result && bitmap ) - { - // Construct LoadedResource and ResourcePointer for image data - LoadedResource resource( request.GetId(), request.GetType()->id, ResourcePointer( bitmap.Get() ) ); - // Queue the loaded resource - mResourceLoader.AddLoadedResource( resource ); - } - else - { - DALI_LOG_WARNING( "Unable to decode bitmap supplied as in-memory blob.\n" ); - } + DALI_LOG_WARNING( "Unable to decode bitmap supplied as in-memory blob.\n" ); } } } @@ -143,14 +265,6 @@ void ResourceThreadImage::Decode(const ResourceRequest& request) } } -void ResourceThreadImage::Save(const Integration::ResourceRequest& request) -{ - DALI_LOG_TRACE_METHOD( mLogFilter ); - DALI_ASSERT_DEBUG( request.GetType()->id == ResourceBitmap ); - DALI_LOG_WARNING( "Image saving not supported on background resource threads." ); -} - - } // namespace SlpPlatform } // namespace Dali diff --git a/platform-abstractions/slp/resource-loader/resource-thread-image.h b/platform-abstractions/slp/resource-loader/resource-thread-image.h index 36be426..9b5079b 100644 --- a/platform-abstractions/slp/resource-loader/resource-thread-image.h +++ b/platform-abstractions/slp/resource-loader/resource-thread-image.h @@ -35,7 +35,7 @@ public: * Constructor * @param[in] resourceLoader A reference to the ResourceLoader */ - ResourceThreadImage(ResourceLoader& resourceLoader); + ResourceThreadImage(ResourceLoader& resourceLoader, bool forRemoteImage); /** * Destructor @@ -50,6 +50,11 @@ private: virtual void Load(const Integration::ResourceRequest& request); /** + * @copydoc ResourceThreadBase::Download + */ + virtual void Download(const Integration::ResourceRequest& request); + + /** * @copydoc ResourceThreadBase::Decode */ virtual void Decode(const Integration::ResourceRequest& request); @@ -59,6 +64,27 @@ private: */ virtual void Save(const Integration::ResourceRequest& request); + /** + * Download a requested image into a memory buffer. + * @param[in] request The requested resource/file url and attributes + * @param[out] dataBuffer A memory buffer object to be written with downloaded image data. + * @param[out] dataSize The size of the memory buffer. + */ + bool DownloadRemoteImageIntoMemory(const Integration::ResourceRequest& request, Dali::Vector& dataBuffer, size_t& dataSize); + + /** + * Load a requested image from a local file. + * @param[in] request The requested resource/file url and attributes + */ + void LoadImageFromLocalFile(const Integration::ResourceRequest& request); + + /** + * Decode a requested image from a memory buffer. + * @param[in] blobBytes A pointer to the memory buffer containig the requested image data. + * @param[in] blobSize The size of the memory buffer containing the requested image data. + * @param[in] request The requested resource/file url and attributes + */ + void DecodeImageFromMemory(void* blobBytes, size_t blobSize, const Integration::ResourceRequest& request); }; // class ResourceThreadImage } // namespace SlpPlatform -- 2.7.4