1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pump.provider; 18 19 import android.net.Uri; 20 import android.text.TextUtils; 21 22 import androidx.annotation.NonNull; 23 import androidx.annotation.WorkerThread; 24 25 import com.android.pump.util.Clog; 26 import com.android.pump.util.Http; 27 28 import org.json.JSONArray; 29 import org.json.JSONException; 30 import org.json.JSONObject; 31 import org.json.JSONTokener; 32 33 import java.io.IOException; 34 import java.nio.charset.StandardCharsets; 35 import java.util.ArrayList; 36 import java.util.Iterator; 37 import java.util.List; 38 39 @WorkerThread 40 public final class Wikidata { 41 private static final String TAG = Clog.tag(Wikidata.class); 42 Wikidata()43 private Wikidata() { } 44 search(@onNull Query query)45 public static void search(@NonNull Query query) throws IOException { 46 search(query, 1); 47 } 48 search(@onNull Query query, int maxResults)49 public static void search(@NonNull Query query, int maxResults) throws IOException { 50 Clog.i(TAG, "search(" + query + ", " + maxResults + ")"); 51 getContentForResults(getSearchResults(getQueryString(query), maxResults)); 52 } 53 getQueryString(Query query)54 private static String getQueryString(Query query) { 55 StringBuilder builder = new StringBuilder(); 56 builder.append(query.getName()); 57 if (query.hasYear()) { 58 builder.append(' '); 59 builder.append(query.getYear()); 60 } 61 if (query.isEpisode()) { 62 builder.append(' '); 63 builder.append('s'); 64 builder.append(query.getSeason()); 65 66 builder.append(' '); 67 builder.append('e'); 68 builder.append(query.getEpisode()); 69 } 70 return builder.toString(); 71 } 72 getSearchResults(String search, int maxResults)73 private static List<String> getSearchResults(String search, int maxResults) throws IOException { 74 String uri = getSearchUri(search, maxResults); 75 Clog.i(TAG, uri); 76 String result = new String(Http.get(uri), StandardCharsets.UTF_8); 77 Clog.i(TAG, result); 78 try { 79 Object root = new JSONTokener(result).nextValue(); 80 dumpJson(root); 81 JSONObject resultRoot = (JSONObject) root; 82 JSONArray resultArray = resultRoot.getJSONObject("query").getJSONArray("search"); 83 List<String> ids = new ArrayList<>(resultArray.length()); 84 for (int i = 0; i < resultArray.length(); ++i) { 85 ids.add(resultArray.getJSONObject(i).getString("title")); 86 } 87 return ids; 88 } catch (JSONException e) { 89 Clog.w(TAG, "Failed to parse search result", e); 90 throw new IOException(e); 91 } 92 } 93 getContentForResults(List<String> ids)94 private static void getContentForResults(List<String> ids) throws IOException { 95 /* 96 String uri = getContentUri(ids); 97 Clog.i(TAG, uri); 98 String result = new String(Http.get(uri), StandardCharsets.UTF_8); 99 //Clog.i(TAG, result); 100 try { 101 Object root = new JSONTokener(result).nextValue(); 102 //dumpJson(root); 103 JSONObject resultRoot = (JSONObject) root; 104 } catch (JSONException e) { 105 Clog.w(TAG, "Failed to parse data", e); 106 throw new IOException(e); 107 } 108 */ 109 getSparqlForResults(ids); 110 } 111 getSparqlForResults(List<String> ids)112 private static void getSparqlForResults(List<String> ids) throws IOException { 113 String uri = getSparqlUri(ids); 114 Clog.i(TAG, uri); 115 String result = new String(Http.get(uri), StandardCharsets.UTF_8); 116 Clog.i(TAG, result); 117 try { 118 Object root = new JSONTokener(result).nextValue(); 119 dumpJson(root); 120 JSONObject resultRoot = (JSONObject) root; 121 } catch (JSONException e) { 122 Clog.w(TAG, "Failed to parse sparql", e); 123 throw new IOException(e); 124 } 125 } 126 getSearchUri(String search, int maxResults)127 private static String getSearchUri(String search, int maxResults) { 128 Uri.Builder ub = new Uri.Builder(); 129 ub.scheme("https"); 130 ub.authority("www.wikidata.org"); 131 ub.appendPath("w"); 132 ub.appendPath("api.php"); 133 ub.appendQueryParameter("action", "query"); 134 ub.appendQueryParameter("list", "search"); 135 ub.appendQueryParameter("format", "json"); 136 ub.appendQueryParameter("formatversion", "2"); 137 ub.appendQueryParameter("srsearch", search); 138 ub.appendQueryParameter("srlimit", Integer.toString(maxResults)); 139 ub.appendQueryParameter("srprop", ""); 140 ub.appendQueryParameter("srinfo", ""); 141 return ub.build().toString(); 142 } 143 144 /* 145 */ 146 147 // https://www.wikidata.org/wiki/Wikidata:SPARQL_query_service 148 149 /* 150 const endpointUrl = 'https://query.wikidata.org/sparql', 151 sparqlQuery = `SELECT DISTINCT ?item ?itemLabel ?itemDescription 152 WHERE 153 { 154 ?item wdt:P31/wdt:P279* wd:Q11424. 155 SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" } 156 ?item wdt:P3383 ?itemPoster. 157 }`, 158 fullUrl = endpointUrl + '?query=' + encodeURIComponent( sparqlQuery ), 159 headers = { 'Accept': 'application/sparql-results+json' }; 160 161 fetch( fullUrl, { headers } ).then( body => body.json() ).then( json => { 162 const { head: { vars }, results } = json; 163 for ( const result of results.bindings ) { 164 for ( const variable of vars ) { 165 console.log( '%s: %o', variable, result[variable] ); 166 } 167 console.log( '---' ); 168 } 169 } ); 170 */ 171 172 // ?action=wbgetentities&ids=Q775450|Q3041294|Q646968|Q434841|Q11920&format=jsonfm&props=labels&languages=en|de|fr getContentUri(List<String> ids)173 private static String getContentUri(List<String> ids) { 174 Uri.Builder ub = new Uri.Builder(); 175 ub.scheme("https"); 176 ub.authority("www.wikidata.org"); 177 ub.appendPath("w"); 178 ub.appendPath("api.php"); 179 ub.appendQueryParameter("action", "wbgetentities"); 180 ub.appendQueryParameter("props", "labels|descriptions"); 181 ub.appendQueryParameter("format", "json"); 182 ub.appendQueryParameter("formatversion", "2"); 183 ub.appendQueryParameter("languages", "en"); 184 ub.appendQueryParameter("languagefallback", ""); 185 ub.appendQueryParameter("ids", TextUtils.join("|", ids)); 186 return ub.build().toString(); 187 } 188 getSparqlUri(List<String> ids)189 private static String getSparqlUri(List<String> ids) { 190 List<String> dbIds = new ArrayList<>(ids.size()); 191 for (String id : ids) { 192 dbIds.add("wd:" + id); 193 } 194 String sparqlQuery = "" 195 + "SELECT * WHERE {" 196 + "VALUES ?item {" 197 + TextUtils.join(" ", dbIds) 198 + "}" 199 + "FILTER EXISTS {?item wdt:P31/wdt:P279* wd:Q11424.}" 200 + "OPTIONAL {?item wdt:P3383 ?poster.}" 201 + "?item rdfs:label ?title FILTER (lang(?title) = 'en')." 202 + "}"; 203 Uri.Builder ub = new Uri.Builder(); 204 ub.scheme("https"); 205 ub.authority("query.wikidata.org"); 206 ub.appendPath("sparql"); 207 ub.appendQueryParameter("format", "json"); 208 ub.appendQueryParameter("query", sparqlQuery); 209 return ub.build().toString(); 210 } 211 dumpJson(Object root)212 private static void dumpJson(Object root) throws JSONException { 213 dumpJson(null, "", root); 214 } 215 dumpJson(String name, String indent, Object object)216 private static void dumpJson(String name, String indent, Object object) throws JSONException { 217 name = name != null ? name + ": " : ""; 218 if (object == JSONObject.NULL) { 219 Clog.d(TAG, indent + name + "null"); 220 } else if (object instanceof JSONObject) { 221 Clog.d(TAG, indent + name + "{"); 222 JSONObject jsonObject = (JSONObject) object; 223 Iterator<String> keys = jsonObject.keys(); 224 while (keys.hasNext()) { 225 String key = keys.next(); 226 dumpJson(key, indent + " ", jsonObject.get(key)); 227 } 228 Clog.d(TAG, indent + "}"); 229 } else if (object instanceof JSONArray) { 230 Clog.d(TAG, indent + name + "["); 231 JSONArray jsonArray = (JSONArray) object; 232 for (int i = 0; i < jsonArray.length(); ++i) { 233 dumpJson(null, indent + " ", jsonArray.get(i)); 234 } 235 Clog.d(TAG, indent + "]"); 236 } else if (object instanceof String) { 237 String jsonString = (String) object; 238 Clog.d(TAG, indent + name + jsonString + " (string)"); 239 } else if (object instanceof Boolean) { 240 Boolean jsonBoolean = (Boolean) object; 241 Clog.d(TAG, indent + name + jsonBoolean + " (boolean)"); 242 } else if (object instanceof Integer) { 243 Integer jsonInteger = (Integer) object; 244 Clog.d(TAG, indent + name + jsonInteger + " (int)"); 245 } else if (object instanceof Long) { 246 Long jsonLong = (Long) object; 247 Clog.d(TAG, indent + name + jsonLong + " (long)"); 248 } else if (object instanceof Double) { 249 Double jsonDouble = (Double) object; 250 Clog.d(TAG, indent + name + jsonDouble + " (double)"); 251 } 252 } 253 } 254