Merge lp:~stolowski/unity-scope-click/local-departments into lp:unity-scope-click/devel

Proposed by Paweł Stołowski
Status: Work in progress
Proposed branch: lp:~stolowski/unity-scope-click/local-departments
Merge into: lp:unity-scope-click/devel
Diff against target: 1506 lines (+902/-36)
24 files modified
debian/control (+1/-0)
libclickscope/click/CMakeLists.txt (+4/-0)
libclickscope/click/departments-db.cpp (+318/-0)
libclickscope/click/departments-db.h (+100/-0)
libclickscope/click/interface.cpp (+8/-2)
libclickscope/click/interface.h (+3/-1)
libclickscope/click/package.cpp (+9/-0)
libclickscope/click/package.h (+2/-0)
libclickscope/click/preview.cpp (+46/-7)
libclickscope/click/preview.h (+9/-3)
libclickscope/tests/CMakeLists.txt (+5/-1)
libclickscope/tests/test_departments-db.cpp (+228/-0)
libclickscope/tests/test_index.cpp (+4/-2)
scope/clickapps/CMakeLists.txt (+2/-1)
scope/clickapps/apps-query.cpp (+82/-7)
scope/clickapps/apps-query.h (+3/-1)
scope/clickapps/apps-scope.cpp (+11/-2)
scope/clickapps/apps-scope.h (+4/-0)
scope/clickstore/CMakeLists.txt (+2/-1)
scope/clickstore/store-query.cpp (+39/-1)
scope/clickstore/store-query.h (+3/-1)
scope/clickstore/store-scope.cpp (+12/-2)
scope/clickstore/store-scope.h (+2/-0)
scope/tests/test_query.cpp (+5/-4)
To merge this branch: bzr merge lp:~stolowski/unity-scope-click/local-departments
Reviewer Review Type Date Requested Status
Alejandro J. Cura (community) Needs Fixing
Review via email: mp+224870@code.launchpad.net

Commit message

Support for departments in click apps scope, for locally installed apps.

Description of the change

Support for departments in click apps scope, for locally installed apps.
Note: this is still WIP, and needs a change to /api/v1/package server call (as described on https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#Package_Details).

To post a comment you must log in.
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Great work pawel!

I know the branch is in progress, so let me give an initial observation: in order for the branch to be more easily reviewable, and to make sure that it does not grow much more, please split it in a few branches.

The first one should only include all the methods that do the queries and the db access, and all the corresponding tests.
The second one should start filling the db from preview.cpp using the branch above.
The third one would query the db to build the combo of departments.

review: Needs Fixing
315. By Paweł Stołowski

Use recursive sql query when fetching packages from a department.

316. By Paweł Stołowski

Depend on sqlite >= 3.8.5 to ensure recursive sql statement is supported at runtime.

317. By Paweł Stołowski

Uncomment unlink.

Unmerged revisions

317. By Paweł Stołowski

Uncomment unlink.

316. By Paweł Stołowski

Depend on sqlite >= 3.8.5 to ensure recursive sql statement is supported at runtime.

315. By Paweł Stołowski

Use recursive sql query when fetching packages from a department.

314. By Paweł Stołowski

Apply department filter when querying for locally installed apps.

313. By Paweł Stołowski

Throw on empty arguments to database store methods. Keep database in clickscope subdir.

312. By Paweł Stołowski

Store package - department mapping when displaying the preview.

311. By Paweł Stołowski

More checks in the test.

310. By Paweł Stołowski

Test department name updates.

309. By Paweł Stołowski

Fixes around table definitions; test for no duplicated records.

308. By Paweł Stołowski

Populdate department in details.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-06-23 15:00:28 +0000
3+++ debian/control 2014-07-02 12:34:49 +0000
4@@ -29,6 +29,7 @@
5 ubuntu-app-launch-tools,
6 ubuntu-download-manager,
7 ubuntu-sdk-libs,
8+ sqlite3 (>= 3.8.5),
9 ${misc:Depends},
10 ${shlibs:Depends},
11 Breaks: unity (<< 7.0),
12
13=== modified file 'libclickscope/click/CMakeLists.txt'
14--- libclickscope/click/CMakeLists.txt 2014-06-23 15:00:28 +0000
15+++ libclickscope/click/CMakeLists.txt 2014-07-02 12:34:49 +0000
16@@ -1,6 +1,7 @@
17 SET (CMAKE_INCLUDE_CURRENT_DIR ON)
18 SET (CMAKE_AUTOMOC ON)
19 find_package (Qt5Core REQUIRED)
20+find_package (Qt5Sql REQUIRED)
21 pkg_check_modules(JSON_CPP REQUIRED jsoncpp)
22
23 add_definitions(
24@@ -14,6 +15,7 @@
25 download-manager.cpp
26 department-lookup.cpp
27 departments.cpp
28+ departments-db.cpp
29 highlights.cpp
30 index.cpp
31 interface.cpp
32@@ -30,6 +32,8 @@
33 webclient.cpp
34 )
35
36+qt5_use_modules(${SCOPE_LIB_NAME} Sql)
37+
38 include_directories(
39 ${JSON_CPP_INCLUDE_DIRS}
40 ${CMAKE_SOURCE_DIR}/libclickscope
41
42=== added file 'libclickscope/click/departments-db.cpp'
43--- libclickscope/click/departments-db.cpp 1970-01-01 00:00:00 +0000
44+++ libclickscope/click/departments-db.cpp 2014-07-02 12:34:49 +0000
45@@ -0,0 +1,318 @@
46+/*
47+ * Copyright (C) 2014 Canonical Ltd.
48+ *
49+ * This program is free software: you can redistribute it and/or modify it
50+ * under the terms of the GNU General Public License version 3, as published
51+ * by the Free Software Foundation.
52+ *
53+ * This program is distributed in the hope that it will be useful, but
54+ * WITHOUT ANY WARRANTY; without even the implied warranties of
55+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
56+ * PURPOSE. See the GNU General Public License for more details.
57+ *
58+ * You should have received a copy of the GNU General Public License along
59+ * with this program. If not, see <http://www.gnu.org/licenses/>.
60+ *
61+ * In addition, as a special exception, the copyright holders give
62+ * permission to link the code of portions of this program with the
63+ * OpenSSL library under certain conditions as described in each
64+ * individual source file, and distribute linked combinations
65+ * including the two.
66+ * You must obey the GNU General Public License in all respects
67+ * for all of the code used other than OpenSSL. If you modify
68+ * file(s) with this exception, you may extend this exception to your
69+ * version of the file(s), but you are not obligated to do so. If you
70+ * do not wish to do so, delete this exception statement from your
71+ * version. If you delete this exception statement from all source
72+ * files in the program, then also delete it here.
73+ */
74+
75+#include "departments-db.h"
76+#include <stdexcept>
77+#include <iostream>
78+#include <QSqlError>
79+#include <QSqlRecord>
80+#include <QVariant>
81+#include <QStandardPaths>
82+#include <QDir>
83+
84+namespace click
85+{
86+
87+std::unique_ptr<click::DepartmentsDb> DepartmentsDb::create_db()
88+{
89+ auto const path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
90+ if (!path.isEmpty())
91+ {
92+ QDir("/").mkpath(path);
93+ const std::string dbpath = path.toStdString() + "/clickscope/click-departments.db";
94+ return std::unique_ptr<DepartmentsDb>(new DepartmentsDb(dbpath));
95+ }
96+ throw std::runtime_error("Cannot determine cache directory");
97+}
98+
99+DepartmentsDb::DepartmentsDb(const std::string& name)
100+{
101+ init_db(name);
102+
103+ delete_pkgmap_query_.reset(new QSqlQuery(db_));
104+ insert_pkgmap_query_.reset(new QSqlQuery(db_));
105+ insert_dept_id_query_.reset(new QSqlQuery(db_));
106+ insert_dept_name_query_.reset(new QSqlQuery(db_));
107+ select_pkgs_by_dept_.reset(new QSqlQuery(db_));
108+ select_pkgs_by_dept_recursive_.reset(new QSqlQuery(db_));
109+ select_parent_dept_.reset(new QSqlQuery(db_));
110+ select_children_depts_.reset(new QSqlQuery(db_));
111+ select_dept_name_.reset(new QSqlQuery(db_));
112+
113+ delete_pkgmap_query_->prepare("DELETE FROM pkgmap WHERE pkgid=:pkgid");
114+ insert_pkgmap_query_->prepare("INSERT OR REPLACE INTO pkgmap (pkgid, deptid) VALUES (:pkgid, :deptid)");
115+ insert_dept_id_query_->prepare("INSERT OR REPLACE INTO depts (deptid, parentid) VALUES (:deptid, :parentid)");
116+ insert_dept_name_query_->prepare("INSERT OR REPLACE INTO deptnames (deptid, locale, name) VALUES (:deptid, :locale, :name)");
117+ select_pkgs_by_dept_->prepare("SELECT pkgid FROM pkgmap WHERE deptid=:deptid");
118+ select_pkgs_by_dept_recursive_->prepare("WITH RECURSIVE recdepts(deptid) AS (SELECT deptid FROM depts_v WHERE deptid=:deptid UNION SELECT depts_v.deptid FROM recdepts,depts_v WHERE recdepts.deptid=depts_v.parentid) SELECT pkgid FROM pkgmap NATURAL JOIN recdepts");
119+ select_children_depts_->prepare("SELECT deptid,(SELECT COUNT(1) from DEPTS_V AS inner WHERE inner.parentid=outer.deptid) FROM DEPTS_V AS outer WHERE parentid=:parentid");
120+ select_parent_dept_->prepare("SELECT parentid FROM depts_v WHERE deptid=:deptid");
121+ select_dept_name_->prepare("SELECT name FROM deptnames WHERE deptid=:deptid AND locale=:locale");
122+}
123+
124+void DepartmentsDb::init_db(const std::string& name)
125+{
126+ db_ = QSqlDatabase::addDatabase("QSQLITE");
127+ db_.setDatabaseName(QString::fromStdString(name));
128+ if (!db_.open())
129+ {
130+ throw std::runtime_error("Cannot open departments database");
131+ }
132+
133+ QSqlQuery query;
134+
135+ //query.exec("PRAGMA foreign_keys = ON");
136+
137+ // package id -> department id mapping table
138+ if (!query.exec("CREATE TABLE IF NOT EXISTS pkgmap (pkgid TEXT, deptid TEXT, CONSTRAINT pkey PRIMARY KEY (pkgid, deptid))"))
139+ {
140+ report_db_error(query.lastError(), "Failed to create pkgmap table");
141+ }
142+
143+ // department id -> parent department id mapping table
144+ if (!query.exec("CREATE TABLE IF NOT EXISTS depts (deptid TEXT, parentid TEXT, CONSTRAINT pkey PRIMARY KEY (deptid, parentid), CONSTRAINT fkey FOREIGN KEY (deptid) REFERENCES deptnames(deptid))"))
145+ {
146+ report_db_error(query.lastError(), "Failed to create depts table");
147+ }
148+
149+ // department id, locale -> deparment name mapping table
150+ if (!query.exec("CREATE TABLE IF NOT EXISTS deptnames (deptid TEXT, locale TEXT, name TEXT, CONSTRAINT deptuniq PRIMARY KEY (deptid, locale))"))
151+ {
152+ report_db_error(query.lastError(), "Failed to create depts table");
153+ }
154+
155+ // name -> value table for storing arbitrary values such as schema version
156+ if (!query.exec("CREATE TABLE IF NOT EXISTS meta (name TEXT PRIMARY KEY, value TEXT)"))
157+ {
158+ report_db_error(query.lastError(), "Failed to create meta table");
159+ }
160+ query.exec("INSERT INTO meta (name, value) VALUES ('version', 1)");
161+
162+ // view of the depts table that automatically adds fake "" department for root departments
163+ if (!query.exec("CREATE VIEW IF NOT EXISTS depts_v AS SELECT deptid, parentid FROM depts UNION SELECT deptid,'' AS parentid FROM deptnames WHERE NOT EXISTS "
164+ "(SELECT * FROM depts WHERE depts.deptid=deptnames.deptid)"))
165+ {
166+ report_db_error(query.lastError(), "Failed to create depts_v view");
167+ }
168+}
169+
170+void DepartmentsDb::report_db_error(const QSqlError& error, const std::string& message)
171+{
172+ throw std::runtime_error(message + ": " + error.text().toStdString());
173+}
174+
175+std::string DepartmentsDb::get_department_name(const std::string& department_id, const std::list<std::string>& locales)
176+{
177+ for (auto const& locale: locales)
178+ {
179+ select_dept_name_->bindValue(":deptid", QVariant(QString::fromStdString(department_id)));
180+ select_dept_name_->bindValue(":locale", QVariant(QString::fromStdString(locale)));
181+
182+ if (!select_dept_name_->exec())
183+ {
184+ report_db_error(select_dept_name_->lastError(), "Failed to query for department name of " + department_id + ", locale " + locale);
185+ }
186+
187+ if (select_dept_name_->next())
188+ {
189+ return select_dept_name_->value(0).toString().toStdString();
190+ }
191+ }
192+ throw std::logic_error("No name for department " + department_id);
193+}
194+
195+std::string DepartmentsDb::get_parent_department_id(const std::string& department_id)
196+{
197+ select_parent_dept_->bindValue(":deptid", QVariant(QString::fromStdString(department_id)));
198+ if (!select_parent_dept_->exec())
199+ {
200+ report_db_error(select_parent_dept_->lastError(), "Failed to query for parent department " + department_id);
201+ }
202+ if (!select_parent_dept_->next())
203+ {
204+ throw std::logic_error("Unknown department '" + department_id + "'");
205+ }
206+ return select_parent_dept_->value(0).toString().toStdString();
207+}
208+
209+std::list<DepartmentsDb::DepartmentInfo> DepartmentsDb::get_children_departments(const std::string& department_id)
210+{
211+ // TODO: this should only return departments that have results, and set 'has_children' flag on the same basis.
212+ // to be fixed when new sqlite3 is available.
213+ select_children_depts_->bindValue(":parentid", QVariant(QString::fromStdString(department_id)));
214+ if (!select_children_depts_->exec())
215+ {
216+ report_db_error(select_children_depts_->lastError(), "Failed to query for children departments of " + department_id);
217+ }
218+
219+ std::list<DepartmentInfo> depts;
220+ while (select_children_depts_->next())
221+ {
222+ const DepartmentInfo inf(select_children_depts_->value(0).toString().toStdString(), select_children_depts_->value(1).toBool());
223+ depts.push_back(inf);
224+ }
225+
226+ return depts;
227+}
228+
229+std::unordered_set<std::string> DepartmentsDb::get_packages_for_department(const std::string& department_id, bool recursive)
230+{
231+ std::unordered_set<std::string> pkgs;
232+ QSqlQuery *query = recursive ? select_pkgs_by_dept_recursive_.get() : select_pkgs_by_dept_.get();
233+ query->bindValue(":deptid", QVariant(QString::fromStdString(department_id)));
234+ if (!query->exec())
235+ {
236+ report_db_error(query->lastError(), "Failed to query for packages of department " + department_id);
237+ }
238+ while (query->next())
239+ {
240+ pkgs.insert(query->value(0).toString().toStdString());
241+ }
242+ return pkgs;
243+}
244+
245+void DepartmentsDb::store_package_mapping(const std::string& package_id, const std::set<std::string>& department_ids)
246+{
247+ if (package_id.empty())
248+ {
249+ throw std::logic_error("Invalid empty package_id");
250+ }
251+
252+ if (department_ids.size() == 0)
253+ {
254+ throw std::logic_error("Invalid empty departments list");
255+ }
256+
257+ if (!db_.transaction())
258+ {
259+ std::cerr << "Failed to start transaction" << std::endl;
260+ }
261+
262+ // delete package mapping first from any departments
263+ delete_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id)));
264+ delete_pkgmap_query_->exec();
265+
266+ for (auto const& dept: department_ids)
267+ {
268+ if (dept.empty())
269+ {
270+ throw std::logic_error("Invalid empty department id");
271+ }
272+
273+ insert_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id)));
274+ insert_pkgmap_query_->bindValue(":deptid", QVariant(QString::fromStdString(dept)));
275+ if (!insert_pkgmap_query_->exec())
276+ {
277+ if (!db_.rollback())
278+ {
279+ std::cerr << "Failed to rollback transaction" << std::endl;
280+ }
281+ report_db_error(insert_pkgmap_query_->lastError(), "Failed to insert into pkgmap");
282+ }
283+ }
284+
285+ if (!db_.commit())
286+ {
287+ std::cerr << "Failed to commit transaction" << std::endl;
288+ }
289+}
290+
291+void DepartmentsDb::store_department_mapping(const std::string& department_id, const std::string& parent_department_id)
292+{
293+ if (department_id.empty())
294+ {
295+ throw std::logic_error("Invalid empty department id");
296+ }
297+
298+ if (parent_department_id.empty())
299+ {
300+ throw std::logic_error("Invalid empty parent department id");
301+ }
302+
303+ insert_dept_id_query_->bindValue(":deptid", QVariant(QString::fromStdString(department_id)));
304+ insert_dept_id_query_->bindValue(":parentid", QVariant(QString::fromStdString(parent_department_id)));
305+ if (!insert_dept_id_query_->exec())
306+ {
307+ report_db_error(insert_dept_id_query_->lastError(), "Failed to insert into depts");
308+ }
309+}
310+
311+void DepartmentsDb::store_department_name(const std::string& department_id, const std::string& locale, const std::string& name)
312+{
313+ if (department_id.empty())
314+ {
315+ throw std::logic_error("Invalid empty department id");
316+ }
317+
318+ if (name.empty())
319+ {
320+ throw std::logic_error("Invalid empty department name");
321+ }
322+
323+ insert_dept_name_query_->bindValue(":deptid", QVariant(QString::fromStdString(department_id)));
324+ insert_dept_name_query_->bindValue(":locale", QVariant(QString::fromStdString(locale)));
325+ insert_dept_name_query_->bindValue(":name", QVariant(QString::fromStdString(name)));
326+
327+ if (!insert_dept_name_query_->exec())
328+ {
329+ report_db_error(insert_dept_name_query_->lastError(), "Failed to insert into deptnames");
330+ }
331+}
332+
333+int DepartmentsDb::department_mapping_count() const
334+{
335+ QSqlQuery q(db_);
336+ if (!q.exec("SELECT COUNT(*) FROM depts") || !q.next())
337+ {
338+ report_db_error(q.lastError(), "Failed to query depts table");
339+ }
340+ return q.value(0).toInt();
341+}
342+
343+int DepartmentsDb::package_count() const
344+{
345+ QSqlQuery q(db_);
346+ if (!q.exec("SELECT COUNT(*) FROM pkgmap") || !q.next())
347+ {
348+ report_db_error(q.lastError(), "Failed to query pkgmap table");
349+ }
350+ return q.value(0).toInt();
351+}
352+
353+int DepartmentsDb::department_name_count() const
354+{
355+ QSqlQuery q(db_);
356+ if (!q.exec("SELECT COUNT(*) FROM deptnames") || !q.next())
357+ {
358+ report_db_error(q.lastError(), "Failed to query deptnames table");
359+ }
360+ return q.value(0).toInt();
361+}
362+
363+}
364
365=== added file 'libclickscope/click/departments-db.h'
366--- libclickscope/click/departments-db.h 1970-01-01 00:00:00 +0000
367+++ libclickscope/click/departments-db.h 2014-07-02 12:34:49 +0000
368@@ -0,0 +1,100 @@
369+/*
370+ * Copyright (C) 2014 Canonical Ltd.
371+ *
372+ * This program is free software: you can redistribute it and/or modify it
373+ * under the terms of the GNU General Public License version 3, as published
374+ * by the Free Software Foundation.
375+ *
376+ * This program is distributed in the hope that it will be useful, but
377+ * WITHOUT ANY WARRANTY; without even the implied warranties of
378+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
379+ * PURPOSE. See the GNU General Public License for more details.
380+ *
381+ * You should have received a copy of the GNU General Public License along
382+ * with this program. If not, see <http://www.gnu.org/licenses/>.
383+ *
384+ * In addition, as a special exception, the copyright holders give
385+ * permission to link the code of portions of this program with the
386+ * OpenSSL library under certain conditions as described in each
387+ * individual source file, and distribute linked combinations
388+ * including the two.
389+ * You must obey the GNU General Public License in all respects
390+ * for all of the code used other than OpenSSL. If you modify
391+ * file(s) with this exception, you may extend this exception to your
392+ * version of the file(s), but you are not obligated to do so. If you
393+ * do not wish to do so, delete this exception statement from your
394+ * version. If you delete this exception statement from all source
395+ * files in the program, then also delete it here.
396+ */
397+
398+#ifndef CLICK_DEPARTMENTS_DB_H
399+#define CLICK_DEPARTMENTS_DB_H
400+
401+#include <string>
402+#include <set>
403+#include <unordered_set>
404+#include <list>
405+#include <QSqlDatabase>
406+#include <QSqlQuery>
407+#include <memory>
408+
409+class QSqlError;
410+
411+namespace click
412+{
413+
414+class DepartmentsDb final
415+{
416+public:
417+ struct DepartmentInfo
418+ {
419+ DepartmentInfo(const std::string &id, bool children): id(id), has_children(children) {}
420+ std::string id;
421+ bool has_children;
422+
423+ bool operator==(const DepartmentInfo& other) const
424+ {
425+ return id == other.id && has_children == other.has_children;
426+ }
427+ };
428+
429+ DepartmentsDb(const std::string& name);
430+ DepartmentsDb(const DepartmentsDb& other) = delete;
431+ DepartmentsDb& operator=(const DepartmentsDb&) = delete;
432+
433+ std::string get_department_name(const std::string& department_id, const std::list<std::string>& locales);
434+ std::unordered_set<std::string> get_packages_for_department(const std::string& department_id, bool recursive = true);
435+ std::string get_parent_department_id(const std::string& department_id);
436+ std::list<DepartmentInfo> get_children_departments(const std::string& department_id);
437+
438+ // FIXME: package can belong to only one department, get rid of set
439+ void store_package_mapping(const std::string& package_id, const std::set<std::string>& department_ids);
440+ void store_department_mapping(const std::string& department_id, const std::string& parent_department_id);
441+ void store_department_name(const std::string& department_id, const std::string& locale, const std::string& name);
442+
443+ // these methods are mostly for tests
444+ int department_mapping_count() const;
445+ int package_count() const;
446+ int department_name_count() const;
447+
448+ static std::unique_ptr<DepartmentsDb> create_db();
449+
450+private:
451+ void init_db(const std::string& name);
452+ static void report_db_error(const QSqlError& error, const std::string& message);
453+
454+ QSqlDatabase db_;
455+ std::unique_ptr<QSqlQuery> delete_pkgmap_query_;
456+ std::unique_ptr<QSqlQuery> insert_pkgmap_query_;
457+ std::unique_ptr<QSqlQuery> insert_dept_id_query_;
458+ std::unique_ptr<QSqlQuery> insert_dept_name_query_;
459+ std::unique_ptr<QSqlQuery> select_pkgs_by_dept_;
460+ std::unique_ptr<QSqlQuery> select_pkgs_by_dept_recursive_;
461+ std::unique_ptr<QSqlQuery> select_parent_dept_;
462+ std::unique_ptr<QSqlQuery> select_children_depts_;
463+ std::unique_ptr<QSqlQuery> select_dept_name_;
464+};
465+
466+}
467+
468+#endif
469
470=== modified file 'libclickscope/click/interface.cpp'
471--- libclickscope/click/interface.cpp 2014-06-16 15:38:49 +0000
472+++ libclickscope/click/interface.cpp 2014-07-02 12:34:49 +0000
473@@ -228,13 +228,15 @@
474 *
475 * Find all of the installed apps matching @search_query in a timeout.
476 */
477-std::vector<click::Application> Interface::find_installed_apps(const std::string& search_query)
478+std::vector<click::Application> Interface::find_installed_apps(const std::string& search_query,
479+ const std::unordered_set<std::string>& packages_in_department,
480+ bool department_filter)
481 {
482 std::vector<Application> result;
483
484 bool include_desktop_results = show_desktop_apps();
485
486- auto enumerator = [&result, this, search_query, include_desktop_results]
487+ auto enumerator = [&result, this, search_query, packages_in_department, department_filter, include_desktop_results]
488 (const unity::util::IniParser& keyFile, const std::string& filename)
489 {
490 if (keyFile.has_group(DESKTOP_FILE_GROUP) == false) {
491@@ -250,6 +252,10 @@
492 || Interface::is_non_click_app(QString::fromStdString(filename))) {
493 auto app = load_app_from_desktop(keyFile, filename);
494
495+ // check if apps is present in current department
496+ if (department_filter && packages_in_department.find(app.name) == packages_in_department.end())
497+ return;
498+
499 if (search_query.empty()) {
500 result.push_back(app);
501 } else {
502
503=== modified file 'libclickscope/click/interface.h'
504--- libclickscope/click/interface.h 2014-06-16 15:38:49 +0000
505+++ libclickscope/click/interface.h 2014-07-02 12:34:49 +0000
506@@ -90,7 +90,9 @@
507 virtual Application load_app_from_desktop(const unity::util::IniParser& keyFile,
508 const std::string& filename);
509 static std::vector<Application> sort_apps(const std::vector<Application>& apps);
510- virtual std::vector<Application> find_installed_apps(const std::string& search_query);
511+ virtual std::vector<Application> find_installed_apps(const std::string& search_query,
512+ const std::unordered_set<std::string>& packages_in_department = std::unordered_set<std::string>(),
513+ bool department_filter = false);
514
515 static bool is_non_click_app(const QString& filename);
516
517
518=== modified file 'libclickscope/click/package.cpp'
519--- libclickscope/click/package.cpp 2014-06-26 17:31:28 +0000
520+++ libclickscope/click/package.cpp 2014-07-02 12:34:49 +0000
521@@ -126,6 +126,15 @@
522 details.download_url = root[JsonKeys::download_url].asString();
523 details.license = root[JsonKeys::license].asString();
524
525+ if (root[JsonKeys::department].isArray())
526+ {
527+ auto const dept_array = root[JsonKeys::department];
528+ if (dept_array.size() > 0)
529+ {
530+ details.department = dept_array[dept_array.size() - 1].asString();
531+ }
532+ }
533+
534 // Optional details go here.
535 if (root[JsonKeys::version].isString())
536 details.version = root[JsonKeys::version].asString();
537
538=== modified file 'libclickscope/click/package.h'
539--- libclickscope/click/package.h 2014-06-26 17:31:28 +0000
540+++ libclickscope/click/package.h 2014-07-02 12:34:49 +0000
541@@ -126,6 +126,7 @@
542 constexpr static const char* terms_of_service{"terms_of_service"};
543 constexpr static const char* license{"license"};
544 constexpr static const char* publisher{"publisher"};
545+ constexpr static const char* department{"department"};
546 constexpr static const char* main_screenshot_url{"screenshot_url"};
547 constexpr static const char* more_screenshot_urls{"screenshot_urls"};
548 constexpr static const char* binary_filesize{"binary_filesize"};
549@@ -149,6 +150,7 @@
550 json::Value::UInt64 binary_filesize;
551 std::string version;
552 std::string framework;
553+ std::string department;
554 };
555
556 std::ostream& operator<<(std::ostream& out, const PackageDetails& details);
557
558=== modified file 'libclickscope/click/preview.cpp'
559--- libclickscope/click/preview.cpp 2014-06-23 15:00:28 +0000
560+++ libclickscope/click/preview.cpp 2014-07-02 12:34:49 +0000
561@@ -34,6 +34,7 @@
562 #include <click/download-manager.h>
563 #include <click/launcher.h>
564 #include <click/dbus_constants.h>
565+#include <click/departments-db.h>
566
567 #include <boost/algorithm/string/replace.hpp>
568
569@@ -58,16 +59,18 @@
570 Preview::Preview(const unity::scopes::Result& result,
571 const unity::scopes::ActionMetadata& metadata,
572 const QSharedPointer<click::web::Client>& client,
573- const QSharedPointer<click::network::AccessManager>& nam)
574+ const QSharedPointer<click::network::AccessManager>& nam,
575+ std::shared_ptr<click::DepartmentsDb> depts)
576 : PreviewQueryBase(result, metadata)
577 {
578- strategy.reset(choose_strategy(result, metadata, client, nam));
579+ strategy.reset(choose_strategy(result, metadata, client, nam, depts));
580 }
581
582 PreviewStrategy* Preview::choose_strategy(const unity::scopes::Result &result,
583 const unity::scopes::ActionMetadata &metadata,
584 const QSharedPointer<web::Client> &client,
585- const QSharedPointer<click::network::AccessManager>& nam)
586+ const QSharedPointer<click::network::AccessManager>& nam,
587+ std::shared_ptr<click::DepartmentsDb> depts)
588 {
589 if (metadata.scope_data().which() != scopes::Variant::Type::Null) {
590 auto metadict = metadata.scope_data().get_dict();
591@@ -86,7 +89,7 @@
592 std::string action_id = metadict["action_id"].get_string();
593 std::string download_url = metadict["download_url"].get_string();
594 if (action_id == click::Preview::Actions::INSTALL_CLICK) {
595- return new InstallingPreview(download_url, result, client, nam);
596+ return new InstallingPreview(download_url, result, client, nam, depts);
597 } else {
598 qWarning() << "unexpected action id " << QString::fromStdString(action_id)
599 << " given with download_url" << QString::fromStdString(download_url);
600@@ -346,9 +349,11 @@
601 InstallingPreview::InstallingPreview(const std::string &download_url,
602 const unity::scopes::Result &result,
603 const QSharedPointer<click::web::Client>& client,
604- const QSharedPointer<click::network::AccessManager> &nam)
605+ const QSharedPointer<click::network::AccessManager> &nam,
606+ std::shared_ptr<click::DepartmentsDb> depts)
607 : PreviewStrategy(result, client), download_url(download_url),
608- downloader(new click::Downloader(nam))
609+ downloader(new click::Downloader(nam)),
610+ depts_db(depts)
611 {
612 }
613
614@@ -383,7 +388,9 @@
615 default:
616 std::string object_path = rc.first;
617 qDebug() << "Successfully created UDM Download.";
618- populateDetails([this, reply, object_path](const PackageDetails &details){
619+ populateDetails([this, reply, object_path](const PackageDetails &details) {
620+ store_department(details);
621+
622 reply->push(headerWidgets(details));
623 reply->push(progressBarWidget(object_path));
624 reply->push(descriptionWidgets(details));
625@@ -403,6 +410,38 @@
626 });
627 }
628
629+void InstallingPreview::store_department(const PackageDetails& details)
630+{
631+ //
632+ // store package -> department mapping in sqlite db
633+ if (depts_db)
634+ {
635+ if (!details.department.empty())
636+ {
637+ try
638+ {
639+ depts_db->store_package_mapping(details.package.name, { details.department });
640+ qDebug() << "Storing mapping for" << QString::fromStdString(details.package.name) << ":" << QString::fromStdString(details.department);
641+ }
642+ catch (const std::exception& e)
643+ {
644+ qWarning() << "Failed to store package mapping for package '"
645+ << QString::fromStdString(details.package.name)
646+ << "', department '" << QString::fromStdString(details.department)
647+ << "':" << QString::fromStdString(e.what());
648+ }
649+ }
650+ else
651+ {
652+ qWarning() << "Department is empty for package" << QString::fromStdString(details.package.name);
653+ }
654+ }
655+ else
656+ {
657+ qWarning() << "Departments database not available";
658+ }
659+}
660+
661 scopes::PreviewWidgetList InstallingPreview::progressBarWidget(const std::string& object_path)
662 {
663 scopes::PreviewWidgetList widgets;
664
665=== modified file 'libclickscope/click/preview.h'
666--- libclickscope/click/preview.h 2014-06-18 04:39:23 +0000
667+++ libclickscope/click/preview.h 2014-07-02 12:34:49 +0000
668@@ -49,6 +49,7 @@
669
670 class Manifest;
671 class PreviewStrategy;
672+class DepartmentsDb;
673
674 class Preview : public unity::scopes::PreviewQueryBase
675 {
676@@ -57,7 +58,8 @@
677 PreviewStrategy* choose_strategy(const unity::scopes::Result& result,
678 const unity::scopes::ActionMetadata& metadata,
679 const QSharedPointer<web::Client> &client,
680- const QSharedPointer<click::network::AccessManager>& nam);
681+ const QSharedPointer<click::network::AccessManager>& nam,
682+ std::shared_ptr<click::DepartmentsDb> depts);
683 public:
684 struct Actions
685 {
686@@ -82,7 +84,8 @@
687 Preview(const unity::scopes::Result& result,
688 const unity::scopes::ActionMetadata& metadata,
689 const QSharedPointer<click::web::Client>& client,
690- const QSharedPointer<click::network::AccessManager>& nam);
691+ const QSharedPointer<click::network::AccessManager>& nam,
692+ std::shared_ptr<click::DepartmentsDb> depts);
693 // From unity::scopes::PreviewQuery
694 void cancelled() override;
695 virtual void run(unity::scopes::PreviewReplyProxy const& reply) override;
696@@ -140,7 +143,8 @@
697 InstallingPreview(std::string const& download_url,
698 const unity::scopes::Result& result,
699 const QSharedPointer<click::web::Client>& client,
700- const QSharedPointer<click::network::AccessManager>& nam);
701+ const QSharedPointer<click::network::AccessManager>& nam,
702+ std::shared_ptr<click::DepartmentsDb> depts);
703
704 virtual ~InstallingPreview();
705
706@@ -148,8 +152,10 @@
707
708 protected:
709 virtual scopes::PreviewWidgetList progressBarWidget(const std::string& object_path);
710+ void store_department(const PackageDetails& pkg);
711 std::string download_url;
712 QSharedPointer<click::Downloader> downloader;
713+ std::shared_ptr<click::DepartmentsDb> depts_db;
714 void startLauncherAnimation(const PackageDetails& details);
715 };
716
717
718=== modified file 'libclickscope/tests/CMakeLists.txt'
719--- libclickscope/tests/CMakeLists.txt 2014-06-26 17:52:31 +0000
720+++ libclickscope/tests/CMakeLists.txt 2014-07-02 12:34:49 +0000
721@@ -6,6 +6,7 @@
722 SET (CMAKE_AUTOMOC ON)
723
724 find_package(Qt5Core REQUIRED)
725+find_package(Qt5Sql REQUIRED)
726
727 include_directories (
728 ${CMAKE_SOURCE_DIR}/libclickscope
729@@ -15,6 +16,7 @@
730 )
731
732 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test_data.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/test_data.cpp)
733+add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}")
734
735 add_executable (${LIBCLICKSCOPE_TESTS_TARGET}
736 mock_network_access_manager.h
737@@ -31,11 +33,13 @@
738 test_reviews.cpp
739 test_smartconnect.cpp
740 test_webclient.cpp
741+ test_departments.cpp
742+ test_departments-db.cpp
743
744 ${CMAKE_CURRENT_BINARY_DIR}/test_data.cpp
745 )
746
747-qt5_use_modules(${LIBCLICKSCOPE_TESTS_TARGET} Core)
748+qt5_use_modules(${LIBCLICKSCOPE_TESTS_TARGET} Core Sql)
749
750 target_link_libraries(${LIBCLICKSCOPE_TESTS_TARGET}
751 ${SCOPE_LIB_NAME}
752
753=== added file 'libclickscope/tests/test_departments-db.cpp'
754--- libclickscope/tests/test_departments-db.cpp 1970-01-01 00:00:00 +0000
755+++ libclickscope/tests/test_departments-db.cpp 2014-07-02 12:34:49 +0000
756@@ -0,0 +1,228 @@
757+/*
758+ * Copyright (C) 2014 Canonical Ltd.
759+ *
760+ * This program is free software: you can redistribute it and/or modify it
761+ * under the terms of the GNU General Public License version 3, as published
762+ * by the Free Software Foundation.
763+ *
764+ * This program is distributed in the hope that it will be useful, but
765+ * WITHOUT ANY WARRANTY; without even the implied warranties of
766+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
767+ * PURPOSE. See the GNU General Public License for more details.
768+ *
769+ * You should have received a copy of the GNU General Public License along
770+ * with this program. If not, see <http://www.gnu.org/licenses/>.
771+ *
772+ * In addition, as a special exception, the copyright holders give
773+ * permission to link the code of portions of this program with the
774+ * OpenSSL library under certain conditions as described in each
775+ * individual source file, and distribute linked combinations
776+ * including the two.
777+ * You must obey the GNU General Public License in all respects
778+ * for all of the code used other than OpenSSL. If you modify
779+ * file(s) with this exception, you may extend this exception to your
780+ * version of the file(s), but you are not obligated to do so. If you
781+ * do not wish to do so, delete this exception statement from your
782+ * version. If you delete this exception statement from all source
783+ * files in the program, then also delete it here.
784+ */
785+
786+#include <gtest/gtest.h>
787+#include "fake_json.h"
788+#include <click/departments-db.h>
789+#include <memory>
790+#include <algorithm>
791+#include <unistd.h>
792+
793+using namespace click;
794+
795+class DepartmentsDbTest: public ::testing::Test
796+{
797+public:
798+ const std::string db_path = TEST_DIR "/departments-db-test.sqlite";
799+
800+ void SetUp() override
801+ {
802+ db.reset(new DepartmentsDb(db_path));
803+ db->store_department_name("tools", "", "Tools");
804+ db->store_department_name("office", "", "Office");
805+
806+ db->store_department_mapping("office", "tools");
807+
808+ db->store_package_mapping("app1", {"tools"});
809+ db->store_package_mapping("app2", {"office"});
810+
811+ db->store_department_name("games", "", "Games");
812+ db->store_department_name("games", "pl_PL", "Gry");
813+ db->store_department_name("rpg", "", "RPG");
814+ db->store_department_name("card", "", "Card");
815+ db->store_department_name("fps", "", "First Person Shooter");
816+
817+ db->store_department_mapping("rpg", "games");
818+ db->store_department_mapping("card", "games");
819+ db->store_department_mapping("fps", "games");
820+
821+ db->store_package_mapping("game1", {"rpg", "fps"});
822+ db->store_package_mapping("game2", {"fps"});
823+ }
824+
825+ void TearDown() override
826+ {
827+ unlink(db_path.c_str());
828+ }
829+
830+protected:
831+ std::unique_ptr<DepartmentsDb> db;
832+};
833+
834+TEST_F(DepartmentsDbTest, testDepartmentNameLookup)
835+{
836+ {
837+ EXPECT_EQ("Games", db->get_department_name("games", {"en_EN", ""}));
838+ EXPECT_EQ("Gry", db->get_department_name("games", {"pl_PL", ""}));
839+ EXPECT_EQ("First Person Shooter", db->get_department_name("fps", {"en_EN", ""}));
840+ EXPECT_EQ("Office", db->get_department_name("office", {"en_EN", ""}));
841+ EXPECT_EQ("Tools", db->get_department_name("tools", {"en_EN", ""}));
842+
843+ EXPECT_THROW(db->get_department_name("xyz", {"en_EN", ""}), std::logic_error);
844+ }
845+}
846+
847+TEST_F(DepartmentsDbTest, testDepartmentNameUpdates)
848+{
849+ {
850+ EXPECT_EQ(7u, db->department_name_count());
851+ db->store_department_name("tools", "", "Tools!");
852+ EXPECT_EQ("Tools!", db->get_department_name("tools", {"en_EN", ""}));
853+ db->store_department_name("games", "pl_PL", "Gry!!!");
854+ EXPECT_EQ("Gry!!!", db->get_department_name("games", {"pl_PL"}));
855+ EXPECT_EQ(7u, db->department_name_count());
856+ }
857+}
858+
859+TEST_F(DepartmentsDbTest, testDepartmentParentLookup)
860+{
861+ {
862+ EXPECT_EQ("games", db->get_parent_department_id("rpg"));
863+ EXPECT_EQ("games", db->get_parent_department_id("card"));
864+ EXPECT_EQ("", db->get_parent_department_id("games"));
865+
866+ EXPECT_EQ("tools", db->get_parent_department_id("office"));
867+ EXPECT_EQ("", db->get_parent_department_id("tools"));
868+
869+ EXPECT_THROW(db->get_parent_department_id("xyz"), std::logic_error);
870+ }
871+}
872+
873+TEST_F(DepartmentsDbTest, testDepartmentChildrenLookup)
874+{
875+ {
876+ EXPECT_EQ(0, db->get_children_departments("xyz").size());
877+ }
878+ {
879+ auto depts = db->get_children_departments("");
880+ EXPECT_EQ(2u, depts.size());
881+ EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("tools", true)) != depts.end());
882+ EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("games", true)) != depts.end());
883+ }
884+ {
885+ auto depts = db->get_children_departments("tools");
886+ EXPECT_EQ(1u, depts.size());
887+ EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("office", false)) != depts.end());
888+ }
889+ {
890+ auto depts = db->get_children_departments("games");
891+ EXPECT_EQ(3u, depts.size());
892+ EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("rpg", false)) != depts.end());
893+ EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("fps", false)) != depts.end());
894+ EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("card", false)) != depts.end());
895+ }
896+}
897+
898+TEST_F(DepartmentsDbTest, testPackageLookup)
899+{
900+ {
901+ auto pkgs = db->get_packages_for_department("rpg", false);
902+ EXPECT_EQ(1u, pkgs.size());
903+ EXPECT_TRUE(pkgs.find("game1") != pkgs.end());
904+ }
905+ {
906+ auto pkgs = db->get_packages_for_department("fps", false);
907+ EXPECT_EQ(2u, pkgs.size());
908+ EXPECT_TRUE(pkgs.find("game1") != pkgs.end());
909+ EXPECT_TRUE(pkgs.find("game2") != pkgs.end());
910+ }
911+ {
912+ auto pkgs = db->get_packages_for_department("card", false);
913+ EXPECT_EQ(0, pkgs.size());
914+ }
915+}
916+
917+TEST_F(DepartmentsDbTest, testRecursivePackageLookup)
918+{
919+ {
920+ // get packages from subdepartments of games department
921+ auto pkgs = db->get_packages_for_department("games", true);
922+ EXPECT_EQ(2u, pkgs.size());
923+ EXPECT_TRUE(pkgs.find("game1") != pkgs.end());
924+ EXPECT_TRUE(pkgs.find("game2") != pkgs.end());
925+ }
926+ {
927+ // rpg has no subdepartments, so we just get the packages of rpg department
928+ auto pkgs = db->get_packages_for_department("rpg", true);
929+ EXPECT_EQ(1u, pkgs.size());
930+ EXPECT_TRUE(pkgs.find("game1") != pkgs.end());
931+ }
932+ {
933+ auto pkgs = db->get_packages_for_department("card", true);
934+ EXPECT_EQ(0, pkgs.size());
935+ }
936+}
937+
938+TEST_F(DepartmentsDbTest, testPackageUpdates)
939+{
940+ auto pkgs = db->get_packages_for_department("fps");
941+ EXPECT_EQ(2, pkgs.size());
942+ EXPECT_TRUE(pkgs.find("game2") != pkgs.end());
943+
944+ // game2 gets moved to card and removed from fps; game1 still in fps
945+ db->store_package_mapping("game2", {"card"});
946+
947+ pkgs = db->get_packages_for_department("fps", false);
948+ EXPECT_EQ(1, pkgs.size());
949+ EXPECT_TRUE(pkgs.find("game2") == pkgs.end());
950+ pkgs = db->get_packages_for_department("card", false);
951+ EXPECT_EQ(1, pkgs.size());
952+ EXPECT_TRUE(pkgs.find("game2") != pkgs.end());
953+}
954+
955+TEST_F(DepartmentsDbTest, testInvalidDepartments)
956+{
957+ EXPECT_THROW(db->get_parent_department_id(""), std::logic_error);
958+ EXPECT_THROW(db->get_parent_department_id("foooo"), std::logic_error);
959+}
960+
961+TEST_F(DepartmentsDbTest, testEmptyArguments)
962+{
963+ EXPECT_THROW(db->store_department_name("", "", "Foo"), std::logic_error);
964+ EXPECT_THROW(db->store_department_name("foo", "", ""), std::logic_error);
965+ EXPECT_THROW(db->store_department_mapping("", "foo"), std::logic_error);
966+ EXPECT_THROW(db->store_department_mapping("foo", ""), std::logic_error);
967+ EXPECT_THROW(db->store_package_mapping("", {"foo"}), std::logic_error);
968+ EXPECT_THROW(db->store_package_mapping("foo", {}), std::logic_error);
969+ EXPECT_THROW(db->store_package_mapping("foo", {""}), std::logic_error);
970+}
971+
972+TEST_F(DepartmentsDbTest, testNoDuplicates)
973+{
974+ db->store_package_mapping("game2", {"fps"});
975+ db->store_package_mapping("game2", {"fps"});
976+ db->store_department_name("games", "pl_PL", "Gry");
977+ db->store_department_name("games", "pl_PL", "Gry");
978+ db->store_department_mapping("office", "tools");
979+ db->store_department_mapping("office", "tools");
980+
981+ EXPECT_EQ(7u, db->department_name_count());
982+ EXPECT_EQ(5u, db->package_count());
983+}
984+
985
986=== modified file 'libclickscope/tests/test_index.cpp'
987--- libclickscope/tests/test_index.cpp 2014-06-26 18:57:08 +0000
988+++ libclickscope/tests/test_index.cpp 2014-07-02 12:34:49 +0000
989@@ -342,7 +342,8 @@
990 {"sshot1", "sshot2"},
991 177582,
992 "0.2",
993- "None"
994+ "None",
995+ "tools"
996 };
997 EXPECT_CALL(*this, details_callback(fake_details, _)).Times(1);
998 response->replyFinished();
999@@ -389,7 +390,8 @@
1000 {"sshot1", "sshot2"},
1001 177582,
1002 "0.2",
1003- "None"
1004+ "None",
1005+ "tools"
1006 };
1007
1008 EXPECT_CALL(*this, details_callback(fake_details, _)).Times(1);
1009
1010=== modified file 'scope/clickapps/CMakeLists.txt'
1011--- scope/clickapps/CMakeLists.txt 2014-06-04 14:45:48 +0000
1012+++ scope/clickapps/CMakeLists.txt 2014-07-02 12:34:49 +0000
1013@@ -1,6 +1,7 @@
1014 SET (CMAKE_INCLUDE_CURRENT_DIR ON)
1015 SET (CMAKE_AUTOMOC ON)
1016 find_package (Qt5Core REQUIRED)
1017+find_package (Qt5Sql REQUIRED)
1018 pkg_check_modules(JSON_CPP REQUIRED jsoncpp)
1019
1020 add_definitions(
1021@@ -20,7 +21,7 @@
1022 ${JSON_CPP_INCLUDE_DIRS}
1023 )
1024
1025-qt5_use_modules (${APPS_LIB_UNVERSIONED} Network)
1026+qt5_use_modules (${APPS_LIB_UNVERSIONED} Network Sql)
1027
1028 target_link_libraries (${APPS_LIB_UNVERSIONED}
1029 ${SCOPE_LIB_NAME}
1030
1031=== modified file 'scope/clickapps/apps-query.cpp'
1032--- scope/clickapps/apps-query.cpp 2014-06-30 20:06:33 +0000
1033+++ scope/clickapps/apps-query.cpp 2014-07-02 12:34:49 +0000
1034@@ -29,6 +29,7 @@
1035
1036 #include <click/application.h>
1037 #include <click/interface.h>
1038+#include <click/departments-db.h>
1039
1040 #include <click/key_file_locator.h>
1041
1042@@ -37,6 +38,7 @@
1043 #include <unity/scopes/CannedQuery.h>
1044 #include <unity/scopes/SearchReply.h>
1045 #include <unity/scopes/SearchMetadata.h>
1046+#include <unity/scopes/Department.h>
1047
1048 #include <vector>
1049
1050@@ -128,18 +130,21 @@
1051
1052 struct click::Query::Private
1053 {
1054- Private(click::Index& index, const scopes::SearchMetadata& metadata)
1055+ Private(click::Index& index, std::shared_ptr<click::DepartmentsDb> depts_db, const scopes::SearchMetadata& metadata)
1056 : index(index),
1057+ depts_db(depts_db),
1058 meta(metadata)
1059 {
1060 }
1061 click::Index& index;
1062+ std::shared_ptr<click::DepartmentsDb> depts_db;
1063 scopes::SearchMetadata meta;
1064 };
1065
1066-click::Query::Query(unity::scopes::CannedQuery const& query, click::Index& index, scopes::SearchMetadata const& metadata)
1067+click::Query::Query(unity::scopes::CannedQuery const& query, click::Index& index, std::shared_ptr<DepartmentsDb> depts_db,
1068+ scopes::SearchMetadata const& metadata)
1069 : unity::scopes::SearchQueryBase(query, metadata),
1070- impl(new Private(index, metadata))
1071+ impl(new Private(index, depts_db, metadata))
1072 {
1073 }
1074
1075@@ -194,15 +199,85 @@
1076 }
1077 }
1078
1079+void click::Query::push_local_departments(scopes::SearchReplyProxy const& replyProxy)
1080+{
1081+ auto const current_dep_id = query().department_id();
1082+ const std::list<std::string> locales = { search_metadata().locale(), "en_US", "" };
1083+
1084+ unity::scopes::Department::SPtr root;
1085+
1086+ try
1087+ {
1088+ static const std::string all_dept_name = _("All departments");
1089+
1090+ // create node for current department
1091+ auto name = current_dep_id == "" ? all_dept_name : impl->depts_db->get_department_name(current_dep_id, locales);
1092+ unity::scopes::Department::SPtr current = unity::scopes::Department::create(current_dep_id, query(), name);
1093+
1094+ // attach subdepartments to it
1095+ for (auto const& subdep: impl->depts_db->get_children_departments(current_dep_id))
1096+ {
1097+ name = impl->depts_db->get_department_name(subdep.id, locales);
1098+ unity::scopes::Department::SPtr dep = unity::scopes::Department::create(subdep.id, query(), name);
1099+ dep->set_has_subdepartments(subdep.has_children);
1100+ current->add_subdepartment(dep);
1101+ }
1102+
1103+ // if current is not the top, then gets its parent
1104+ if (current_dep_id != "")
1105+ {
1106+ auto const parent_dep_id = impl->depts_db->get_parent_department_id(current_dep_id);
1107+ root = unity::scopes::Department::create(parent_dep_id, query(), parent_dep_id == "" ? all_dept_name :
1108+ impl->depts_db->get_department_name(parent_dep_id, locales));
1109+ root->add_subdepartment(current);
1110+ }
1111+ else
1112+ {
1113+ root = current;
1114+ }
1115+
1116+ replyProxy->register_departments(root);
1117+ }
1118+ catch (const std::exception& e)
1119+ {
1120+ qWarning() << "Failed to push departments: " << QString::fromStdString(e.what());
1121+ }
1122+}
1123+
1124 void click::Query::run(scopes::SearchReplyProxy const& searchReply)
1125 {
1126- auto querystr = query().query_string();
1127+ auto const querystr = query().query_string();
1128+ auto const current_dept = query().department_id();
1129+
1130 std::string categoryTemplate = CATEGORY_APPS_SEARCH;
1131 if (querystr.empty()) {
1132 categoryTemplate = CATEGORY_APPS_DISPLAY;
1133- }
1134- auto localResults = clickInterfaceInstance().find_installed_apps(
1135- querystr);
1136+ if (impl->depts_db)
1137+ {
1138+ push_local_departments(searchReply);
1139+ }
1140+ }
1141+
1142+ //
1143+ // get the set of packages that belong to current deparment;
1144+ // only apply department filtering if not in root of all departments.
1145+ bool apply_department_filter = (querystr.empty() && !current_dept.empty());
1146+ std::unordered_set<std::string> pkgs_in_department;
1147+ if (impl->depts_db && apply_department_filter)
1148+ {
1149+ try
1150+ {
1151+ pkgs_in_department = impl->depts_db->get_packages_for_department(current_dept);
1152+ }
1153+ catch (const std::exception& e)
1154+ {
1155+ qWarning() << "Failed to get packages of department" << QString::fromStdString(current_dept);
1156+ apply_department_filter = false; // disable so that we are not loosing any apps if something goes wrong
1157+ }
1158+ }
1159+
1160+ auto const localResults = clickInterfaceInstance().find_installed_apps(
1161+ querystr, pkgs_in_department, apply_department_filter);
1162
1163 push_local_results(
1164 searchReply,
1165
1166=== modified file 'scope/clickapps/apps-query.h'
1167--- scope/clickapps/apps-query.h 2014-05-28 14:27:28 +0000
1168+++ scope/clickapps/apps-query.h 2014-07-02 12:34:49 +0000
1169@@ -44,6 +44,7 @@
1170
1171 class Application;
1172 class Index;
1173+class DepartmentsDb;
1174
1175 class Query : public scopes::SearchQueryBase
1176 {
1177@@ -60,7 +61,7 @@
1178 constexpr static const char* VERSION{"version"};
1179 };
1180
1181- Query(unity::scopes::CannedQuery const& query, click::Index& index, scopes::SearchMetadata const& metadata);
1182+ Query(unity::scopes::CannedQuery const& query, click::Index& index, std::shared_ptr<DepartmentsDb> depts_db, scopes::SearchMetadata const& metadata);
1183 virtual ~Query();
1184
1185 virtual void cancelled() override;
1186@@ -72,6 +73,7 @@
1187 virtual void push_local_results(scopes::SearchReplyProxy const &replyProxy,
1188 std::vector<click::Application> const &apps,
1189 std::string& categoryTemplate);
1190+ virtual void push_local_departments(scopes::SearchReplyProxy const& replyProxy);
1191
1192 private:
1193 struct Private;
1194
1195=== modified file 'scope/clickapps/apps-scope.cpp'
1196--- scope/clickapps/apps-scope.cpp 2014-06-18 16:23:50 +0000
1197+++ scope/clickapps/apps-scope.cpp 2014-07-02 12:34:49 +0000
1198@@ -31,6 +31,7 @@
1199 #include <click/preview.h>
1200 #include <click/interface.h>
1201 #include <click/scope_activation.h>
1202+#include <click/departments-db.h>
1203
1204 #include <QSharedPointer>
1205
1206@@ -48,6 +49,14 @@
1207 nam.reset(new click::network::AccessManager());
1208 client.reset(new click::web::Client(nam));
1209 index.reset(new click::Index(client));
1210+ try
1211+ {
1212+ depts_db = click::DepartmentsDb::create_db();
1213+ }
1214+ catch (const std::runtime_error& e)
1215+ {
1216+ std::cerr << "Failed to get cache directory" << std::endl;
1217+ }
1218 }
1219
1220 click::Scope::~Scope()
1221@@ -78,14 +87,14 @@
1222
1223 scopes::SearchQueryBase::UPtr click::Scope::search(unity::scopes::CannedQuery const& q, scopes::SearchMetadata const& metadata)
1224 {
1225- return scopes::SearchQueryBase::UPtr(new click::Query(q, *index, metadata));
1226+ return scopes::SearchQueryBase::UPtr(new click::Query(q, *index, depts_db, metadata));
1227 }
1228
1229
1230 unity::scopes::PreviewQueryBase::UPtr click::Scope::preview(const unity::scopes::Result& result,
1231 const unity::scopes::ActionMetadata& metadata) {
1232 qDebug() << "Scope::preview() called.";
1233- return scopes::PreviewQueryBase::UPtr{new click::Preview(result, metadata, client, nam)};
1234+ return scopes::PreviewQueryBase::UPtr{new click::Preview(result, metadata, client, nam, depts_db)};
1235 }
1236
1237
1238
1239=== modified file 'scope/clickapps/apps-scope.h'
1240--- scope/clickapps/apps-scope.h 2014-06-18 14:42:47 +0000
1241+++ scope/clickapps/apps-scope.h 2014-07-02 12:34:49 +0000
1242@@ -43,6 +43,9 @@
1243
1244 namespace click
1245 {
1246+
1247+class DepartmentsDb;
1248+
1249 class Scope : public scopes::ScopeBase
1250 {
1251 public:
1252@@ -64,6 +67,7 @@
1253 QSharedPointer<click::network::AccessManager> nam;
1254 QSharedPointer<click::web::Client> client;
1255 QSharedPointer<click::Index> index;
1256+ std::shared_ptr<click::DepartmentsDb> depts_db;
1257
1258 std::string installApplication(unity::scopes::Result const& result);
1259 };
1260
1261=== modified file 'scope/clickstore/CMakeLists.txt'
1262--- scope/clickstore/CMakeLists.txt 2014-06-23 15:00:28 +0000
1263+++ scope/clickstore/CMakeLists.txt 2014-07-02 12:34:49 +0000
1264@@ -1,6 +1,7 @@
1265 SET (CMAKE_INCLUDE_CURRENT_DIR ON)
1266 SET (CMAKE_AUTOMOC ON)
1267 find_package (Qt5Core REQUIRED)
1268+find_package (Qt5Sql REQUIRED)
1269 pkg_check_modules(JSON_CPP REQUIRED jsoncpp)
1270
1271 add_definitions(
1272@@ -19,7 +20,7 @@
1273 ${JSON_CPP_INCLUDE_DIRS}
1274 )
1275
1276-qt5_use_modules (${STORE_LIB_UNVERSIONED} Network)
1277+qt5_use_modules (${STORE_LIB_UNVERSIONED} Network Sql)
1278
1279 target_link_libraries (${STORE_LIB_UNVERSIONED}
1280 ${SCOPE_LIB_NAME}
1281
1282=== modified file 'scope/clickstore/store-query.cpp'
1283--- scope/clickstore/store-query.cpp 2014-06-26 18:07:51 +0000
1284+++ scope/clickstore/store-query.cpp 2014-07-02 12:34:49 +0000
1285@@ -32,6 +32,7 @@
1286 #include "store-query.h"
1287 #include "store-scope.h"
1288 #include <click/qtbridge.h>
1289+#include <click/departments-db.h>
1290
1291 #include <click/key_file_locator.h>
1292
1293@@ -96,25 +97,29 @@
1294 struct click::Query::Private
1295 {
1296 Private(click::Index& index, click::DepartmentLookup& depts,
1297+ std::shared_ptr<click::DepartmentsDb> depts_db,
1298 click::HighlightList& highlights, const scopes::SearchMetadata& metadata)
1299 : index(index),
1300 department_lookup(depts),
1301+ depts_db(depts_db),
1302 highlights(highlights),
1303 meta(metadata)
1304 {
1305 }
1306 click::Index& index;
1307 click::DepartmentLookup& department_lookup;
1308+ std::shared_ptr<click::DepartmentsDb> depts_db;
1309 click::HighlightList& highlights;
1310 scopes::SearchMetadata meta;
1311 click::web::Cancellable search_operation;
1312 };
1313
1314 click::Query::Query(unity::scopes::CannedQuery const& query, click::Index& index, click::DepartmentLookup& depts,
1315+ std::shared_ptr<click::DepartmentsDb> depts_db,
1316 click::HighlightList& highlights,
1317 scopes::SearchMetadata const& metadata)
1318 : unity::scopes::SearchQueryBase(query, metadata),
1319- impl(new Private(index, depts, highlights, metadata))
1320+ impl(new Private(index, depts, depts_db, highlights, metadata))
1321 {
1322 }
1323
1324@@ -216,6 +221,30 @@
1325 root->set_subdepartments(departments);
1326 }
1327
1328+// recursively store all departments in the departments database
1329+void click::Query::store_departments(const click::DepartmentList& depts)
1330+{
1331+ assert(impl->depts_db);
1332+
1333+ qDebug() << "Storing departments in the database";
1334+ try
1335+ {
1336+ for (auto const& dept: depts)
1337+ {
1338+ impl->depts_db->store_department_name(dept->id(), impl->meta.locale(), dept->name());
1339+ for (auto const& subdep: dept->sub_departments())
1340+ {
1341+ impl->depts_db->store_department_mapping(subdep->id(), dept->id());
1342+ }
1343+ store_departments(dept->sub_departments());
1344+ }
1345+ }
1346+ catch (const std::exception &e)
1347+ {
1348+ qWarning() << "Failed to update database: " << QString::fromStdString(e.what());
1349+ }
1350+}
1351+
1352 void click::Query::push_package(const scopes::SearchReplyProxy& searchReply, scopes::Category::SCPtr category, const PackageSet &installedPackages, const Package& pkg)
1353 {
1354 qDebug() << "pushing result" << QString::fromStdString(pkg.name);
1355@@ -371,6 +400,15 @@
1356 impl->department_lookup.rebuild(rdeps);
1357 impl->highlights = highlights;
1358 qDebug() << "Total number of departments:" << impl->department_lookup.size() << ", highlights:" << highlights.size();
1359+
1360+ if (impl->depts_db)
1361+ {
1362+ store_departments(deps);
1363+ }
1364+ else
1365+ {
1366+ qWarning() << "Departments db not available";
1367+ }
1368 }
1369 else
1370 {
1371
1372=== modified file 'scope/clickstore/store-query.h'
1373--- scope/clickstore/store-query.h 2014-06-18 09:01:20 +0000
1374+++ scope/clickstore/store-query.h 2014-07-02 12:34:49 +0000
1375@@ -50,6 +50,7 @@
1376 class Application;
1377 class Index;
1378 class DepartmentLookup;
1379+class DepartmentsDb;
1380
1381 class Query : public scopes::SearchQueryBase
1382 {
1383@@ -76,7 +77,7 @@
1384 constexpr static const char* VERSION{"version"};
1385 };
1386
1387- Query(unity::scopes::CannedQuery const& query, click::Index& index, click::DepartmentLookup& dept_lookup, click::HighlightList& highlights,
1388+ Query(unity::scopes::CannedQuery const& query, click::Index& index, click::DepartmentLookup& dept_lookup, std::shared_ptr<click::DepartmentsDb> depts_db, click::HighlightList& highlights,
1389 scopes::SearchMetadata const& metadata);
1390 virtual ~Query();
1391
1392@@ -86,6 +87,7 @@
1393
1394 protected:
1395 virtual void populate_departments(const click::DepartmentList& depts, const std::string& current_department_id, unity::scopes::Department::SPtr &root);
1396+ virtual void store_departments(const click::DepartmentList& depts);
1397 virtual void push_departments(const scopes::SearchReplyProxy& searchReply, const scopes::Department::SCPtr& root);
1398 virtual void add_highlights(scopes::SearchReplyProxy const& searchReply, const PackageSet& installedPackages);
1399 virtual void add_available_apps(const scopes::SearchReplyProxy &searchReply, const PackageSet &installedPackages, const std::string &category);
1400
1401=== modified file 'scope/clickstore/store-scope.cpp'
1402--- scope/clickstore/store-scope.cpp 2014-06-23 15:00:28 +0000
1403+++ scope/clickstore/store-scope.cpp 2014-07-02 12:34:49 +0000
1404@@ -28,6 +28,7 @@
1405 */
1406
1407 #include <click/qtbridge.h>
1408+#include <click/departments-db.h>
1409 #include <click/department-lookup.h>
1410 #include "store-scope.h"
1411 #include "store-query.h"
1412@@ -42,6 +43,7 @@
1413 #include <click/click-i18n.h>
1414
1415 #include <logging.h>
1416+#include <iostream>
1417
1418 bool click::Scope::old_api = false;
1419
1420@@ -52,6 +54,14 @@
1421 index.reset(new click::Index(client));
1422 depts.reset(new click::DepartmentLookup());
1423 highlights.reset(new click::HighlightList());
1424+ try
1425+ {
1426+ depts_db = click::DepartmentsDb::create_db();
1427+ }
1428+ catch (const std::runtime_error& e)
1429+ {
1430+ std::cerr << "Failed to get cache directory" << std::endl;
1431+ }
1432 }
1433
1434 click::Scope::~Scope()
1435@@ -94,14 +104,14 @@
1436
1437 scopes::SearchQueryBase::UPtr click::Scope::search(unity::scopes::CannedQuery const& q, scopes::SearchMetadata const& metadata)
1438 {
1439- return scopes::SearchQueryBase::UPtr(new click::Query(q, *index, *depts, *highlights, metadata));
1440+ return scopes::SearchQueryBase::UPtr(new click::Query(q, *index, *depts, depts_db, *highlights, metadata));
1441 }
1442
1443
1444 unity::scopes::PreviewQueryBase::UPtr click::Scope::preview(const unity::scopes::Result& result,
1445 const unity::scopes::ActionMetadata& metadata) {
1446 qDebug() << "Scope::preview() called.";
1447- return scopes::PreviewQueryBase::UPtr{new click::Preview(result, metadata, client, nam)};
1448+ return scopes::PreviewQueryBase::UPtr{new click::Preview(result, metadata, client, nam, depts_db)};
1449 }
1450
1451 unity::scopes::ActivationQueryBase::UPtr click::Scope::perform_action(unity::scopes::Result const& result, unity::scopes::ActionMetadata const& metadata,
1452
1453=== modified file 'scope/clickstore/store-scope.h'
1454--- scope/clickstore/store-scope.h 2014-06-18 18:07:39 +0000
1455+++ scope/clickstore/store-scope.h 2014-07-02 12:34:49 +0000
1456@@ -46,6 +46,7 @@
1457 {
1458
1459 class DepartmentLookup;
1460+class DepartmentsDb;
1461
1462 class Scope : public scopes::ScopeBase
1463 {
1464@@ -73,6 +74,7 @@
1465 QSharedPointer<click::Index> index;
1466 std::shared_ptr<click::DepartmentLookup> depts;
1467 std::shared_ptr<click::HighlightList> highlights;
1468+ std::shared_ptr<click::DepartmentsDb> depts_db;
1469
1470 std::string installApplication(unity::scopes::Result const& result);
1471 static bool old_api;
1472
1473=== modified file 'scope/tests/test_query.cpp'
1474--- scope/tests/test_query.cpp 2014-06-24 17:18:53 +0000
1475+++ scope/tests/test_query.cpp 2014-07-02 12:34:49 +0000
1476@@ -98,8 +98,9 @@
1477 public:
1478 MockQueryBase(const unity::scopes::CannedQuery& query, click::Index& index,
1479 click::DepartmentLookup& depts,
1480+ std::shared_ptr<click::DepartmentsDb> depts_db,
1481 click::HighlightList& highlights,
1482- scopes::SearchMetadata const& metadata) : click::Query(query, index, depts, highlights, metadata)
1483+ scopes::SearchMetadata const& metadata) : click::Query(query, index, depts, depts_db, highlights, metadata)
1484 {
1485
1486 }
1487@@ -115,7 +116,7 @@
1488 MockQuery(const unity::scopes::CannedQuery& query, click::Index& index,
1489 click::DepartmentLookup& depts,
1490 click::HighlightList& highlights,
1491- scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, depts, highlights, metadata)
1492+ scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, depts, nullptr, highlights, metadata)
1493 {
1494
1495 }
1496@@ -140,8 +141,8 @@
1497 public:
1498 MockQueryRun(const unity::scopes::CannedQuery& query, click::Index& index,
1499 click::DepartmentLookup& depts,
1500- click::HighlightList& highlights,
1501- scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, depts, highlights, metadata)
1502+ click::HighlightList& highlights,
1503+ scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, depts, nullptr, highlights, metadata)
1504 {
1505
1506 }

Subscribers

People subscribed via source and target branches

to all changes: