~scopecreator-team/scopecreator/rss-template

« back to all changes in this revision

Viewing changes to src/scope/query.cpp

- removed not needed boost foreach. Used usual for
- avoid including empty categories (no title or url)
- included scope metadata in query search
- readed logo, emblem, date time format from scope metadata instead of hardcoded path
- included BigFirstResult boolean setting in .ini file appearance section to show or not with a big image the first received result in surface mode, if not aggregated

but reading scope .ini file directly using QSettings instead of using Scope::metadata

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 */         
19
19
 
20
20
#include <boost/algorithm/string/trim.hpp>
 
21
#include <boost/algorithm/string/predicate.hpp>
21
22
 
22
23
#include <scope/localization.h>
23
24
#include <scope/query.h>
27
28
#include <unity/scopes/CategoryRenderer.h>
28
29
#include <unity/scopes/Department.h>
29
30
#include <unity/scopes/QueryBase.h>
 
31
#include <unity/scopes/CannedQuery.h>
30
32
#include <unity/scopes/SearchReply.h>
31
33
#include <unity/scopes/VariantBuilder.h>
 
34
#include <unity/scopes/SearchMetadata.h>
32
35
 
33
36
#include <iomanip>
34
37
#include <sstream>
38
41
#include <QJsonArray>
39
42
#include <QJsonDocument>
40
43
#include <QJsonObject>
 
44
#include <QDateTime>
41
45
 
42
46
namespace sc = unity::scopes;
43
47
namespace alg = boost::algorithm;
46
50
using namespace api;
47
51
using namespace scope;
48
52
 
 
53
std::string FIRST_NEWS_LAYOUT = R"(
 
54
{
 
55
    "schema-version" : 1,
 
56
    "template" : {
 
57
        "category-layout" : "grid",
 
58
        "card-size": "large",
 
59
        "overlay": true
 
60
    },
 
61
    "components" : {
 
62
        "title" : "title",
 
63
        "art" : {
 
64
            "field": "art",
 
65
            "aspect-ratio": 2.1
 
66
        }
 
67
    }
 
68
}
 
69
)";
49
70
 
50
71
std::string NEWS_LAYOUT = R"(
51
72
{
84
105
}
85
106
)";
86
107
 
87
 
QString getDepartments(QString filePath, sc::SearchReplyProxy const& reply) {
 
108
QString Query::getDepartments(QString filePath, sc::SearchReplyProxy const& reply) {
88
109
    sc::DepartmentList depts;
89
110
    QString rootDeptUrl;
90
111
 
114
135
    rootDeptUrl = obj["id"].toString();
115
136
    topDept = move(sc::Department::create("",myquery,obj["label"].toString().toStdString()));
116
137
 
 
138
    insert_keyword_feed(obj["keyword"].toString().toStdString(), rootDeptUrl.toStdString());
 
139
 
117
140
    if (results.size() > 1) {
118
141
        for (int i = 1; i < results.size(); ++i)
119
142
        {
122
145
            sc::Department::SPtr aDept= move(sc::Department::create(obj["id"].toString().toStdString(),\
123
146
                                         myquery,obj["label"].toString().toStdString()));
124
147
            depts.insert(depts.end(),aDept);
 
148
 
 
149
            insert_keyword_feed(obj["keyword"].toString().toStdString(), aDept->id());
125
150
        }
126
151
 
127
152
        topDept->set_subdepartments(depts);
130
155
    return rootDeptUrl;
131
156
}
132
157
 
133
 
Query::Query(const sc::CannedQuery &query, const sc::SearchMetadata &metadata, const string scope_directory) :
134
 
    sc::SearchQueryBase(query, metadata),
135
 
    client_(),
136
 
    scope_directory_(scope_directory) {
 
158
void Query::insert_keyword_feed(const string& keyword, const string& feed_url) {
 
159
    if (!keyword.empty()) {
 
160
        keyword_feeds_.insert( pair<string, string>(keyword, feed_url) );
 
161
    }
 
162
}
 
163
 
 
164
pair<string,string> Query::find_url_for_keywords(const set<string>& search_keywords) {
 
165
    for(const string& search_keyword : search_keywords) {
 
166
        auto it = keyword_feeds_.find(search_keyword);
 
167
        if (it != keyword_feeds_.end()) {
 
168
            const string& found_url = it->second;
 
169
            return std::pair<string, string>(search_keyword, found_url);
 
170
        }
 
171
    }
 
172
    return std::pair<string, string>{};
 
173
}
 
174
 
 
175
Query::Query(const sc::CannedQuery &query,
 
176
             const sc::SearchMetadata &search_metadata,
 
177
             const string& scope_directory,
 
178
             const QSharedPointer<QSettings>& scopeConfig)
 
179
    : sc::SearchQueryBase(query, search_metadata)
 
180
    , client_{}
 
181
    , scope_directory_{scope_directory}
 
182
    , mScopeConfig{scopeConfig} {
137
183
}
138
184
 
139
185
void Query::cancelled() {
140
186
    client_.cancel();
141
187
}
142
188
 
143
 
 
144
189
void Query::run(sc::SearchReplyProxy const& reply) {
145
 
    try {        
 
190
    try {
146
191
        // Start by getting information about the query
147
192
        const sc::CannedQuery &query(sc::SearchQueryBase::query());
148
193
 
153
198
        QString deptFile = QString::fromStdString(scope_directory_) + "/feeds.json";
154
199
        string feedUrl = getDepartments(deptFile, reply).toStdString(); // feedUrl has the root URL
155
200
 
156
 
        // If the current dept ID is empty use the root URL otherwise use
157
 
        // the dept ID, which should be a department feed URL
158
 
        if (!query.department_id().empty()) {
159
 
            feedUrl = query.department_id();
 
201
        // Check if the search comes from an aggregator scope
 
202
        const sc::SearchMetadata &meta(sc::SearchQueryBase::search_metadata());
 
203
        string found_keyword; // this var will store the keyword related with a found url in case of agg. scope search.
 
204
        if (meta.is_aggregated()) {
 
205
            auto found_pair = find_url_for_keywords(meta.aggregated_keywords());
 
206
            found_keyword = found_pair.first;
 
207
            feedUrl = found_pair.second;
 
208
        } else {
 
209
            // If the current dept ID is empty use the root URL otherwise use
 
210
            // the dept ID, which should be a department feed URL
 
211
            if (!query.department_id().empty()) {
 
212
                feedUrl = query.department_id();
 
213
            }
160
214
        }
161
215
 
162
216
        if (feedUrl.empty()) {
167
221
 
168
222
        vector<Client::Item> results = client_.getItems(feedUrl);
169
223
 
170
 
        // Register a category for the forecast
 
224
        // Register a category for the common news
171
225
        auto news_cat = reply->register_category("news",
172
226
                                                 "", "", sc::CategoryRenderer(NEWS_LAYOUT));
173
 
 
174
 
        // For each of the forecast days
175
 
        for (const auto &item : results) {
176
 
 
177
 
            // Filter by search term if we have one
178
 
            if (!query_string.empty()) {
179
 
                QString qry = QString::fromStdString(query_string);
180
 
                QString title = QString::fromStdString(item.title);
181
 
                QString summary = QString::fromStdString(item.summary);
182
 
                QString content = QString::fromStdString(item.content);
183
 
 
184
 
                if (!title.contains(qry, Qt::CaseInsensitive) &&
185
 
                    !summary.contains(qry, Qt::CaseInsensitive) &&
186
 
                    !content.contains(qry, Qt::CaseInsensitive)) {
187
 
                    continue;
 
227
        auto first_news_cat = reply->register_category("first_news",
 
228
                                                       "", "", sc::CategoryRenderer(FIRST_NEWS_LAYOUT));
 
229
 
 
230
        if (results.size() > 0) {
 
231
 
 
232
            // Get common values for all results to avoid asking for them in every iteration
 
233
            std::string emblem = get_emblem();
 
234
            std::string date_time_format = get_date_time_format();
 
235
            bool show_big_result = get_big_first_result();
 
236
 
 
237
            // For each of the results vector items
 
238
            for (const auto &item : results) {
 
239
 
 
240
                // Filter by search term if we have one
 
241
                if (!query_string.empty()) {
 
242
                    QString qry = QString::fromStdString(query_string);
 
243
                    QString title = QString::fromStdString(item.title);
 
244
                    QString summary = QString::fromStdString(item.summary);
 
245
                    QString content = QString::fromStdString(item.content);
 
246
 
 
247
                    if (!title.contains(qry, Qt::CaseInsensitive) &&
 
248
                        !summary.contains(qry, Qt::CaseInsensitive) &&
 
249
                        !content.contains(qry, Qt::CaseInsensitive)) {
 
250
                        continue;
 
251
                    }
188
252
                }
189
 
            }
190
 
 
191
 
            // Create a result
192
 
            sc::CategorisedResult res(news_cat);
193
 
 
194
 
            // We must have a URI
195
 
            res.set_uri(item.uri);
196
 
            res.set_dnd_uri(item.uri);
197
 
 
198
 
            // Set the rest of the attributes
199
 
            res.set_title(item.title);
200
 
            res.set_art(item.art);
201
 
            res["published"] = sc::Variant(item.published);
202
 
            res["author"] = sc::Variant(item.author);
203
 
            res["summary"] = sc::Variant(item.summary);
204
 
            res["content"] = sc::Variant(item.content);
205
 
 
206
 
            if (res.art().empty()) {
207
 
                res.set_art(scope_directory_ + "/icon.png");
208
 
            }
209
 
 
210
 
            sc::VariantBuilder actions;
211
 
            actions.add_tuple({
212
 
                {"id", sc::Variant("open")},
213
 
                {"label", sc::Variant(_("Open"))},
214
 
                {"uri", sc::Variant(res.dnd_uri())}
215
 
            });
216
 
            res["actions"] = actions.end();
217
 
 
218
 
            // Push the result
219
 
            if (!reply->push(res)) {
220
 
                // If we fail to push, it means the query has been cancelled.
221
 
                // So don't continue;
222
 
                return;
223
 
            }
224
 
        }
 
253
 
 
254
                // Create a result if having title at least
 
255
                if (!item.title.empty() && !item.uri.empty()) {
 
256
 
 
257
                    // If surfacing in not aggregated mode and set big-first-result setting to true, show first result in big
 
258
                    sc::Category::SCPtr cat =
 
259
                            show_big_result && !meta.is_aggregated() && query_string.empty() ?
 
260
                                first_news_cat :
 
261
                                news_cat;
 
262
 
 
263
                    show_big_result = false;
 
264
 
 
265
                    sc::CategorisedResult res(cat);
 
266
 
 
267
                    // We must have a URI
 
268
                    res.set_uri(item.uri);
 
269
                    res.set_dnd_uri(item.uri);
 
270
 
 
271
                    // Set the rest of the attributes
 
272
                    res.set_title(item.title);
 
273
                    res.set_art(item.art);
 
274
                    res["author"] = sc::Variant(item.author);
 
275
                    res["summary"] = sc::Variant(item.summary);
 
276
                    res["content"] = sc::Variant(item.content);
 
277
                    res["emblem"] = sc::Variant(emblem);
 
278
 
 
279
                    if (res.art().empty()) {
 
280
                        res.set_art(get_icon());
 
281
                    }
 
282
 
 
283
                    QString date = QString::fromStdString(item.published);
 
284
 
 
285
                    if (!date.isEmpty()){
 
286
                        QDateTime ipostTime = QDateTime::fromString(date, Qt::ISODate);
 
287
                        QString readableTime = ipostTime.toString(QString::fromStdString(date_time_format));
 
288
                        res["published"] = readableTime.toStdString();
 
289
                    }
 
290
 
 
291
                    // if searched from aggregated, set formatted date to subtitle in case found keyword is related with news
 
292
                    if (meta.is_aggregated()) {
 
293
                        if (boost::starts_with(found_keyword, "news.")) {
 
294
                            res["subtitle"] = res["published"];
 
295
                        }
 
296
                    }
 
297
 
 
298
                    sc::VariantBuilder actions;
 
299
                    actions.add_tuple({
 
300
                        {"id", sc::Variant("open")},
 
301
                        {"label", sc::Variant(_("Open"))},
 
302
                        {"uri", sc::Variant(res.dnd_uri())}
 
303
                    });
 
304
 
 
305
                    if (meta.is_aggregated()) {
 
306
                        actions.add_tuple({
 
307
                            {"id", sc::Variant("more")},
 
308
                            {"label", sc::Variant(_("More from ") + get_display_name())},
 
309
                            {"uri",  sc::Variant(query.to_uri())}
 
310
                        });
 
311
                    }
 
312
                    res["actions"] = actions.end();
 
313
 
 
314
                    // Push the result
 
315
                    if (!reply->push(res)) {
 
316
                        // If we fail to push, it means the query has been cancelled.
 
317
                        // So don't continue;
 
318
                        return;
 
319
                    }
 
320
                } // end if (!item.title.empty() && !item.uri.empty()) ...
 
321
            } // end for (const auto &item : results) ...
 
322
        } // end if (results.size > 0) ...
225
323
 
226
324
    } catch (domain_error &e) {
227
325
        // Handle exceptions being thrown by the client API
230
328
    }
231
329
}
232
330
 
 
331
string Query::get_icon() {
 
332
    return scope_directory_ + "/" + mScopeConfig->value("ScopeConfig/Icon", "/icon.png").toString().toStdString();
 
333
}
 
334
 
 
335
string Query::get_display_name() {
 
336
    return mScopeConfig->value("ScopeConfig/DisplayName", "").toString().toStdString();
 
337
}
 
338
 
 
339
string Query::get_emblem() {
 
340
    return scope_directory_ + "/" + mScopeConfig->value("Appearance/Emblem", "/emblem.png").toString().toStdString();
 
341
}
 
342
 
 
343
string Query::get_date_time_format() {
 
344
    return mScopeConfig->value("Appearance/DateTimeFormat", "hh:mm, d MMMM").toString().toStdString();
 
345
}
 
346
 
 
347
bool Query::get_big_first_result() {
 
348
    return mScopeConfig->value("Appearance/BigFirstResult", false).toBool();
 
349
}
 
350