[Content] Implemented native filtering
authorWojciech Kosowicz <w.kosowicz@samsung.com>
Tue, 17 Mar 2015 09:42:15 +0000 (10:42 +0100)
committerRafal Galka <r.galka@samsung.com>
Fri, 20 Mar 2015 10:56:07 +0000 (19:56 +0900)
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 <w.kosowicz@samsung.com>
src/content/content.gyp
src/content/content_api.js
src/content/content_filter.cc
src/content/content_filter.h
src/content/content_manager.cc
src/content/content_manager.h

index 26ec74297b648f5698d24043213c29f301607aa0..ef9bf4acdf85855e9eb355fc5610344c0391823d 100755 (executable)
@@ -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',
index 5bd488251146259c24e2c741aa97963523f08f7a..45d18aab95c2af702ce9e9282b440ada4eca805b 100755 (executable)
@@ -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;
index d162fbfaa8af06d9141518e0f3a8d16ee2bcbb82..9b60c37c6c095424f2f8914189bf2a1d70f8a6ec 100755 (executable)
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <amp>
+#include "content/content_filter.h"
 
-#include "content_filter.h"
+#include <vector>
 
-#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<std::string, std::string>& 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<std::string, std::string> 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<std::string, std::string>& 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<std::string, std::string>::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<std::vector<std::string> > partialqueries;
+  partialqueries.push_back(std::vector<std::string>());
+
+  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<std::string>(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<std::string>());
+    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<std::string>(initial_value));
+    std::string endValue = escapeValueString(JsonCast<std::string>(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
index f0db0198d42e094f1bb7b2c3327e97a07ad3796a..7c78d7232cebc9cadd9060b9fd7c4406125aaed2 100755 (executable)
@@ -5,26 +5,25 @@
 #ifndef CONTENT_FILTER_H_
 #define CONTENT_FILTER_H_
 
-#include "common/picojson.h"
-
-#include <string>
 #include <media_content.h>
+#include <string>
 
+#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
index df77c4b7ce89d4ace64c6369b7decc70e9e21e04..8f09678d4672e497e2d12e95f38ffe2c4619df0d 100755 (executable)
@@ -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 <unistd.h>
-#include <cstring>
-#include <string>
+#include "content/content_manager.h"
+
 #include <algorithm>
+#include <cstring>
 #include <dlog.h>
-#include <memory>
-#include <sstream>
 #include <metadata_extractor.h>
+#include <sstream>
+#include <string>
+#include <unistd.h>
 
+#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<picojson::value::array*>(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<ReplyCallbackData>& 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<picojson::null>() && vfilter.is<picojson::object>()) {
-      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<picojson::null>() && vSortMode.is<picojson::object>()) {
-      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<picojson::object>(user_data->args);
+    if (filterMechanism.buildQuery(
+            FromJson<picojson::object>(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<double>();
-  }
-  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<double>();
   }
   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<ReplyCallbackData>& user_data) {
   else {
     ret = media_info_foreach_media_from_db(filter, media_foreach_content_cb, static_cast<void*>(&arrayContent));
   }
-  media_filter_destroy(filter);
 
   if (ret == MEDIA_CONTENT_ERROR_NONE) {
     user_data->isSuccess = true;
index 412aac9e36ae7090dc81463929e8796314945676..03dde469448ec12556f2b278b63b98351eec9d69 100755 (executable)
@@ -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 <string>
+#include <glib.h>
 #include <list>
 #include <media_content.h>
-#include <glib.h>
+#include <memory>
+#include <string>
 
-#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 {