From 308213522cb0f3957e4c4823ed6d8750447f2123 Mon Sep 17 00:00:00 2001 From: Wojciech Kosowicz Date: Tue, 17 Mar 2015 10:42:15 +0100 Subject: [PATCH] [Content] Implemented native filtering Implemented filtering and also some small fixes for tct [Verification] If media content available 194 tests pass Change-Id: I89fd8cd06e5b463197ee8dd0c2fd398c6daa7010 Signed-off-by: Wojciech Kosowicz --- src/content/content.gyp | 2 + src/content/content_api.js | 12 +- src/content/content_filter.cc | 287 +++++++++++++++++++++------------ src/content/content_filter.h | 19 ++- src/content/content_manager.cc | 81 ++++------ src/content/content_manager.h | 14 +- 6 files changed, 242 insertions(+), 173 deletions(-) diff --git a/src/content/content.gyp b/src/content/content.gyp index 26ec7429..ef9bf4ac 100755 --- a/src/content/content.gyp +++ b/src/content/content.gyp @@ -10,6 +10,8 @@ 'content_api.js', 'content_extension.cc', 'content_extension.h', + 'content_filter.cc', + 'content_filter.h', 'content_instance.cc', 'content_instance.h', 'content_manager.h', diff --git a/src/content/content_api.js b/src/content/content_api.js index 5bd48825..45d18aab 100755 --- a/src/content/content_api.js +++ b/src/content/content_api.js @@ -1,6 +1,7 @@ // Content -var validator_ = xwalk.utils.validator; +var utils_ = xwalk.utils; +var validator_ = utils_.validator; var types_ = validator_.Types; @@ -604,11 +605,16 @@ ContentManager.prototype.find = function(successCallback) { {'name' : 'successCallback', 'type': types_.FUNCTION, 'values' : ['onsuccess']}, {'name' : 'errorCallback', 'type': types_.FUNCTION, optional : true, nullable : true}, {'name' : 'directoryId', 'type': types_.STRING, optional : true, nullable : true}, - {'name' : 'filter', 'type': types_.DICTIONARY, optional : true, nullable : true}, + {'name' : 'filter', 'type': types_.PLATFORM_OBJECT, + values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter], + optional : true, nullable : true }, {'name' : 'sortMode', 'type': types_.DICTIONARY, optional : true, nullable : true}, {'name' : 'count', 'type': types_.UNSIGNED_LONG, optional : true}, {'name' : 'offset', 'type': types_.UNSIGNED_LONG, optional : true} ]); + if (args.offset < 0 || args.count < 0) { + throw new WebAPIException(WebAPIException.INVALID_VALUES_ERR); + } var nativeParam = { }; @@ -617,7 +623,7 @@ ContentManager.prototype.find = function(successCallback) { nativeParam['directoryId'] = args.directoryId; } if (args['filter']) { - nativeParam['filter'] = args.filter; + nativeParam['filter'] = utils_.repackFilter(args.filter); } if (args['sortMode']) { nativeParam['sortMode'] = args.sortMode; diff --git a/src/content/content_filter.cc b/src/content/content_filter.cc index d162fbfa..9b60c37c 100755 --- a/src/content/content_filter.cc +++ b/src/content/content_filter.cc @@ -2,123 +2,206 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include +#include "content/content_filter.h" -#include "content_filter.h" +#include -#define __DEBUG__ 1 +#include "common/converter.h" +#include "common/logger.h" +using common::AttributeMatchFlag; +using common::CompositeFilterType; +using common::ErrorCode; +using common::JsonCast; +using common::PlatformResult; namespace extension { namespace content { -const std::map& attributeNameMap = { - {"id", "MEDIA_ID"}, - {"type", "MEDIA_TYPE"}, - {"mimeType", "MEDIA_MIME_TYPE"}, - {"name", "MEDIA_DISPLAY_NAME"}, - {"title", "MEDIA_TITLE"}, - {"contentURI", "MEDIA_PATH"}, - {"thumbnailURIs", "MEDIA_THUMBNAIL_PATH"}, - {"description", "MEDIA_DESCRIPTION"}, - {"rating", "MEDIA_RATING"}, - {"createdDate", "MEDIA_ADDED_TIME"}, - {"releaseDate", "MEDIA_DATETAKEN"}, - {"modifiedDate", "MEDIA_MODIFIED_TIME"}, - {"geolocation.latitude", "MEDIA_LATITUDE"}, - {"geolocation.longitude", "MEDIA_LONGITUDE"}, - {"duration", "MEDIA_DURATION"}, - {"album", "MEDIA_ALBUM"}, - {"artists", "MEDIA_ARTIST"}, - {"width", "MEDIA_WIDTH"}, - {"height", "MEDIA_HEIGHT"}, - {"genres", "MEDIA_GENRE"}, - {"size", "MEDIA_SIZE"}, +namespace { + +std::map const attributeNameMap = { + {"id", "MEDIA_ID"}, + {"type", "MEDIA_TYPE"}, + {"mimeType", "MEDIA_MIME_TYPE"}, + {"name", "MEDIA_DISPLAY_NAME"}, + {"title", "MEDIA_TITLE"}, + {"contentURI", "MEDIA_PATH"}, + {"thumbnailURIs", "MEDIA_THUMBNAIL_PATH"}, + {"description", "MEDIA_DESCRIPTION"}, + {"rating", "MEDIA_RATING"}, + {"createdDate", "MEDIA_ADDED_TIME"}, + {"releaseDate", "MEDIA_DATETAKEN"}, + {"modifiedDate", "MEDIA_MODIFIED_TIME"}, + {"geolocation.latitude", "MEDIA_LATITUDE"}, + {"geolocation.longitude", "MEDIA_LONGITUDE"}, + {"duration", "MEDIA_DURATION"}, + {"album", "MEDIA_ALBUM"}, + {"artists", "MEDIA_ARTIST"}, + {"width", "MEDIA_WIDTH"}, + {"height", "MEDIA_HEIGHT"}, + {"genres", "MEDIA_GENRE"}, + {"size", "MEDIA_SIZE"}, }; -const std::map& opMap = { - {"EXACTLY", " = "}, - {"FULLSTRING", " = "}, - {"CONTAINS", " LIKE "}, - {"STARTSWITH", " LIKE "}, - {"ENDSWITH", " LIKE "}, - {"EXISTS", " IS NOT NULL "}, -}; - -std::string ContentFilter::convert(const picojson::value& jsFilter) { - std::string attributeName = jsFilter.get("attributeName").to_str(); - std::string matchFlag = jsFilter.get("matchFlag").to_str(); - std::string matchValue = jsFilter.get("matchValue").to_str(); - -#ifdef DEBUG - std::cout << "Filter IN: " << attributeName << " " << matchFlag << " " << matchValue << std::endl; -#endif - - std::string query; - if (attributeName.empty() || matchFlag.empty()) { - std::cerr << - "Filter ERR: attribute or match flag missing" << std::endl; - return query; +std::string escapeValueString(const std::string& data) { + std::string out; + // If string won't be resized, then it will be faster + out.reserve(data.size()); + for (auto c : data) { + if (c == '\\') + out += "\\\\"; + else if (c == '\"') + out += "\\\""; + else if (c == '\'') + out += "\\\'"; + else if (c == '\n') + out += "\\\n"; + else if (c == '\r') + out += "\\\r"; + else + out += c; } + return out; +} - std::map::const_iterator it; - it = attributeNameMap.find(attributeName); - if (it == attributeNameMap.end()) { - std::cerr << "Filter ERR: unknown attributeName " << - attributeName << std::endl; - return query; +} // namespace + +PlatformResult ContentFilter::buildQuery(const picojson::object& jsFilter, + std::string* queryToCall) { + std::vector > partialqueries; + partialqueries.push_back(std::vector()); + + visitor.SetOnAttributeFilter([&](const std::string& name, + AttributeMatchFlag match_flag, + const picojson::value& match_value) { + std::string query; + LoggerD("entered OnAttributeFilter"); + std::string matchValue; + auto it = attributeNameMap.find(name); + if (it != attributeNameMap.end()) + query += it->second; + else + return PlatformResult(ErrorCode::INVALID_VALUES_ERR); + + if (AttributeMatchFlag::kExactly == match_flag || + AttributeMatchFlag::kFullString == match_flag) { + query += " = "; + } else if (AttributeMatchFlag::kContains == match_flag || + AttributeMatchFlag::kStartsWith == match_flag || + AttributeMatchFlag::kEndsWith == match_flag) { + query += " LIKE "; + } else if (AttributeMatchFlag::kExists == match_flag) { + query += " IS NOT NULL "; + } else { + return PlatformResult(ErrorCode::INVALID_VALUES_ERR); + } + query.append("\""); + + if (AttributeMatchFlag::kExists != match_flag) { + matchValue = escapeValueString(JsonCast(match_value)); + if (name == "type") { + if (matchValue == "IMAGE") { + matchValue = "0"; + } else if (matchValue == "VIDEO") { + matchValue = "1"; + } else if (matchValue == "AUDIO") { + matchValue = "3"; + } else { // OTHER + matchValue = "4"; + } + } + query += matchValue; + } + query.append("\""); + partialqueries.back().push_back(query); + + LoggerD("about to call with condition %s", query.c_str()); + return PlatformResult(ErrorCode::NO_ERROR); + }); + + visitor.SetOnCompositeFilterBegin([&](CompositeFilterType type) { + LoggerD("entered OnCompositeFilterBegin"); + partialqueries.push_back(std::vector()); + return PlatformResult(ErrorCode::NO_ERROR); + }); + + visitor.SetOnCompositeFilterEnd([&](CompositeFilterType calType) { + LoggerD("entered OnCompositeFilterEnd"); + std::string finalQuery; + std::string separator; + + if (CompositeFilterType::kUnion == calType) + separator = " OR "; + else + separator = " AND "; + + LoggerD("Composite filter: %i", partialqueries.back().size()); + if (partialqueries.back().empty()) { + partialqueries.pop_back(); + return PlatformResult(ErrorCode::NO_ERROR); + } + if (partialqueries.back().size() != 1) + finalQuery.append("("); + + for (unsigned long i = 0; i < partialqueries.back().size(); i++) { + finalQuery += partialqueries.back().at(i); + if (i != partialqueries.back().size() - 1) { + finalQuery += separator; + } + } + + if (partialqueries.back().size() != 1) + finalQuery.append(")"); + partialqueries.pop_back(); + partialqueries.back().push_back(finalQuery); + return PlatformResult(ErrorCode::NO_ERROR); + }); + + visitor.SetOnAttributeRangeFilter([&](const std::string& name, + const picojson::value& initial_value, + const picojson::value& end_value) { + LoggerD("entered OnAttributeFilter"); + std::string query = ""; + std::string paramName; + auto it = attributeNameMap.find(name); + if (it != attributeNameMap.end()) + paramName = it->second; + else + return PlatformResult(ErrorCode::INVALID_VALUES_ERR); + std::string initialValue = escapeValueString(JsonCast(initial_value)); + std::string endValue = escapeValueString(JsonCast(end_value)); + query += paramName; + query += " >= \""; + query += initialValue; + query += "\" AND "; + query += paramName; + query += " <= \""; + query += endValue; + query += "\""; + partialqueries.back().push_back(query); + + LoggerD("about to call with condition %s", query.c_str()); + return PlatformResult(ErrorCode::NO_ERROR); + }); + + if (!visitor.Visit(jsFilter)) { + return PlatformResult(ErrorCode::INVALID_VALUES_ERR); } - std::string lValue = it->second; - it = opMap.find(matchFlag); - if (it == attributeNameMap.end()) { - std::cerr << "Filter ERR: unknown matchFlag " << matchFlag << std::endl; - return query; + if (partialqueries.empty()) { + LoggerE("Filter parsing error!"); + return PlatformResult(ErrorCode::SYNTAX_ERR); } - std::string op = it->second; - - // Tizen requires this weird mapping on type - if (attributeName == "type") { - if (matchValue == "IMAGE") { - matchValue = "0"; - } else if (matchValue == "VIDEO") { - matchValue = "1"; - } else if (matchValue == "AUDIO") { - matchValue = "3"; - } else if (matchValue == "OTHER") { - matchValue = "4"; - } else { - std::cerr << "Filter ERR: unknown media type " << matchValue << std::endl; - return query; - } + if (partialqueries.back().empty()) { + LoggerD("Resolved to empty string!"); + *queryToCall = ""; + return PlatformResult(ErrorCode::NO_ERROR); } - const std::string STR_QUOTE("'"); - const std::string STR_PERCENT("%"); - std::string rValue; - if (matchFlag == "CONTAINS") - rValue = STR_QUOTE + STR_PERCENT + matchValue + STR_PERCENT + STR_QUOTE; - else if (matchFlag == "STARTSWITH") - rValue = STR_QUOTE + matchValue + STR_PERCENT + STR_QUOTE; - else if (matchFlag == "ENDSWITH") - rValue = STR_QUOTE + STR_PERCENT + matchValue + STR_QUOTE; - else if (matchFlag == "FULLSTRING") - rValue = STR_QUOTE + matchValue + STR_QUOTE + " COLLATE NOCASE "; - else if (matchFlag == "EXISTS") - rValue = ""; - else - rValue = STR_QUOTE + matchValue + STR_QUOTE; - - query = lValue + op + rValue; -#ifdef DEBUG - std::cout << "Filter OUT: " << query << std::endl; -#endif - return query; + *queryToCall = partialqueries.back().front(); + return PlatformResult(ErrorCode::NO_ERROR); } - - - -} // namespace content -} // namespace extension - +} // namespace content +} // namespace extension diff --git a/src/content/content_filter.h b/src/content/content_filter.h index f0db0198..7c78d723 100755 --- a/src/content/content_filter.h +++ b/src/content/content_filter.h @@ -5,26 +5,25 @@ #ifndef CONTENT_FILTER_H_ #define CONTENT_FILTER_H_ -#include "common/picojson.h" - -#include #include +#include +#include "common/filter-utils.h" +#include "common/picojson.h" +#include "common/platform_result.h" namespace extension { namespace content { class ContentFilter { public: - std::string convert(const picojson::value &jsFilter); - - private: - + common::PlatformResult buildQuery(const picojson::object &jsFilter, + std::string *query); + private: + common::FilterVisitor visitor; }; - } } -#endif - +#endif diff --git a/src/content/content_manager.cc b/src/content/content_manager.cc index df77c4b7..8f09678d 100755 --- a/src/content/content_manager.cc +++ b/src/content/content_manager.cc @@ -2,19 +2,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include -#include -#include +#include "content/content_manager.h" + #include +#include #include -#include -#include #include +#include +#include +#include +#include "common/converter.h" #include "common/logger.h" #include "common/platform_exception.h" -#include "content_manager.h" -#include "content_filter.h" +#include "common/platform_result.h" +#include "common/scope_exit.h" +#include "content/content_filter.h" using namespace std; using namespace common; @@ -22,8 +25,6 @@ using namespace common; namespace extension { namespace content { -#define TAG_DELIMETER '/' - static int get_utc_offset() { time_t zero = 24*60*60L; @@ -400,7 +401,6 @@ static int setContent(media_info_h media, picojson::value content) { std::string description = content.get("description").to_str(); std::string rating = content.get("rating").to_str(); std::string is_fav = content.get("isFavorite").to_str(); - if (media != NULL) { media_content_type_e type; ret = media_info_get_media_type(media, &type); @@ -433,7 +433,7 @@ static int setContent(media_info_h media, picojson::value content) { if (type == MEDIA_CONTENT_TYPE_IMAGE || type == MEDIA_CONTENT_TYPE_VIDEO) { picojson::value geo = content.get("geolocation"); double latitude = atof(geo.get("latitude").to_str().c_str()); - double longitude = atof(geo.get("longitude ").to_str().c_str()); + double longitude = atof(geo.get("longitude").to_str().c_str()); ret = media_info_set_latitude(media, latitude); if ( ret != MEDIA_CONTENT_ERROR_NONE) { LoggerD("Updating geolocation is failed."); @@ -461,7 +461,6 @@ static bool media_foreach_directory_cb(media_folder_h folder, void *user_data) { static bool media_foreach_content_cb(media_info_h media, void *user_data) { picojson::value::array *contents = static_cast(user_data); picojson::value::object o; - contentToJson(media, o); contents->push_back(picojson::value(o)); return true; @@ -617,58 +616,39 @@ void ContentManager::find(const std::shared_ptr& user_data) { double count, offset; std::string dirId, attributeName, matchFlag, matchValue; std::string sortModeName, sortModeOrder; + int error_code = 0; media_content_order_e order; picojson::value::array arrayContent; - filter_h filter = NULL; - ret = media_filter_create(&filter); - - if(ret != MEDIA_CONTENT_ERROR_NONE) { - UnknownException err("Memory allcation for filter is failed."); - user_data->isSuccess = false; - user_data->result = err.ToJSON(); - return; - } - if(user_data->args.contains("filter")) { - //to be implemented. dykim. - picojson::value vfilter = user_data->args.get("filter"); - if (!vfilter.is() && vfilter.is()) { - attributeName = vfilter.get("attributeName").to_str(); - matchFlag = vfilter.get("matchFlag").to_str(); - matchValue = vfilter.get("matchValue").to_str(); + filter_h filter = nullptr; + media_filter_create(&filter); + SCOPE_EXIT { + if (filter) { + media_filter_destroy(filter); } - } - if(user_data->args.contains("sortMode")) { - picojson::value vSortMode = user_data->args.get("sortMode"); - if (!vSortMode.is() && vSortMode.is()) { - sortModeName = vSortMode.get("attributeName").to_str(); - sortModeOrder = vSortMode.get("order").to_str(); - if ( !sortModeOrder.empty() ) { - if( sortModeOrder == "ASC" ) { - order = MEDIA_CONTENT_ORDER_ASC; - } - else if( sortModeOrder == "DESC" ) { - order = MEDIA_CONTENT_ORDER_DESC; - } - ret = media_filter_set_order(filter, order, sortModeName.c_str(), MEDIA_CONTENT_COLLATE_DEFAULT ); - if (MEDIA_CONTENT_ERROR_NONE != ret ) - { - LoggerD("Platform SortMode setting is failed."); - } + }; + if (user_data->args.contains("filter")) { + ContentFilter filterMechanism; + std::string query; + picojson::object argsObject = JsonCast(user_data->args); + if (filterMechanism.buildQuery( + FromJson(argsObject, "filter"), &query)) { + ret = media_filter_set_condition(filter, query.c_str(), + MEDIA_CONTENT_COLLATE_DEFAULT); + if (MEDIA_CONTENT_ERROR_NONE != ret) { } } } if(user_data->args.contains("count")) { count = user_data->args.get("count").get(); - } - else { - count = -1; + } else { + count = 100; //TODO rethink proper default count setting } if(user_data->args.contains("offset")) { offset = user_data->args.get("offset").get(); } else { - offset = -1; + offset = 0; } ret = media_filter_set_offset(filter, offset, count); if ( MEDIA_CONTENT_ERROR_NONE != ret) { @@ -681,7 +661,6 @@ void ContentManager::find(const std::shared_ptr& user_data) { else { ret = media_info_foreach_media_from_db(filter, media_foreach_content_cb, static_cast(&arrayContent)); } - media_filter_destroy(filter); if (ret == MEDIA_CONTENT_ERROR_NONE) { user_data->isSuccess = true; diff --git a/src/content/content_manager.h b/src/content/content_manager.h index 412aac9e..03dde469 100755 --- a/src/content/content_manager.h +++ b/src/content/content_manager.h @@ -5,16 +5,16 @@ #ifndef CONTENT_MANAGER_H_ #define CONTENT_MANAGER_H_ -#include "common/extension.h" -#include "common/picojson.h" -#include "common/platform_exception.h" - -#include +#include #include #include -#include +#include +#include -#include "content_instance.h" +#include "common/extension.h" +#include "common/picojson.h" +#include "common/platform_exception.h" +#include "content/content_instance.h" namespace extension { namespace content { -- 2.34.1