Merge lp:~gary-wzl77/unity-scope-soundcloud/unity-scope-soundcloud into lp:unity-scope-soundcloud

Proposed by Gary.Wang
Status: Merged
Merged at revision: 40
Proposed branch: lp:~gary-wzl77/unity-scope-soundcloud/unity-scope-soundcloud
Merge into: lp:unity-scope-soundcloud
Diff against target: 2654 lines (+1539/-260)
26 files modified
CMakeLists.txt (+1/-1)
click/manifest.json.in (+1/-1)
click/soundcloud.apparmor (+1/-1)
click/soundcloud.provider.in (+1/-0)
data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in (+2/-0)
include/api/client.h (+28/-0)
include/api/comment.h (+70/-0)
include/api/resource.h (+2/-1)
include/api/track.h (+21/-0)
include/api/user.h (+20/-0)
include/scope/activation.h (+64/-0)
include/scope/preview.h (+9/-1)
include/scope/query.h (+6/-0)
include/scope/scope.h (+9/-0)
po/POTFILES.in (+16/-12)
po/en_GB.po (+147/-63)
po/unity-scope-soundcloud.pot (+149/-73)
src/CMakeLists.txt (+2/-0)
src/api/client.cpp (+323/-9)
src/api/comment.cpp (+71/-0)
src/api/track.cpp (+39/-1)
src/api/user.cpp (+30/-0)
src/scope/activation.cpp (+91/-0)
src/scope/preview.cpp (+245/-73)
src/scope/query.cpp (+181/-23)
src/scope/scope.cpp (+10/-1)
To merge this branch: bzr merge lp:~gary-wzl77/unity-scope-soundcloud/unity-scope-soundcloud
Reviewer Review Type Date Requested Status
Unity API Team Pending
Review via email: mp+283759@code.launchpad.net

Commit message

Add new features offered by soundcloud which are missing in this scope.

Description of the change

Add new features offered by soundcloud which are missing in this scope.
1.Add detailed track info in card view
2.Load high resolution track picture
3.Support to retrieve comment list
4.Support to post a comment
5.Waveform image url fixed when loading auth user favorite streams
6.Support to mark a "like" for tracks
7.Support to follow an artist
8.Bump framework to ubuntu-sdk-15.04
9.Fix login failed

To post a comment you must log in.
41. By Gary.Wang

1.Show user info after login
2.Add "My favorites" department
3.Get specific user's track list

42. By Gary.Wang

Use deparments instead of string querying for navigation

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-12-04 15:01:39 +0000
3+++ CMakeLists.txt 2016-02-02 11:41:52 +0000
4@@ -55,7 +55,7 @@
5 set(UBUNTU_PROJECT_TYPE "Scope" CACHE INTERNAL "Tells QtCreator this is a Scope project")
6
7 # Important project paths
8-set(SCOPE_VERSION "1.0.1-${BZR_REVNO}")
9+set(SCOPE_VERSION "1.6.0-${BZR_REVNO}")
10 set(CMAKE_INSTALL_PREFIX /)
11 set(SCOPE_INSTALL_DIR "/soundcloud")
12 set(GETTEXT_PACKAGE "unity-scope-soundcloud")
13
14=== modified file 'click/manifest.json.in'
15--- click/manifest.json.in 2014-11-12 13:18:58 +0000
16+++ click/manifest.json.in 2016-02-02 11:41:52 +0000
17@@ -1,7 +1,7 @@
18 {
19 "architecture": "@CLICK_ARCH@",
20 "description": "Listen to your favourite tracks on SoundCloud",
21- "framework": "ubuntu-sdk-14.10-dev2",
22+ "framework": "ubuntu-sdk-15.04",
23 "hooks": {
24 "soundcloud": {
25 "apparmor": "soundcloud.apparmor",
26
27=== modified file 'click/soundcloud.apparmor'
28--- click/soundcloud.apparmor 2014-11-11 06:54:01 +0000
29+++ click/soundcloud.apparmor 2016-02-02 11:41:52 +0000
30@@ -3,7 +3,7 @@
31 "policy_groups": [
32 "accounts"
33 ],
34- "policy_version": 1.2
35+ "policy_version": 1.3
36 }
37
38
39
40=== modified file 'click/soundcloud.provider.in'
41--- click/soundcloud.provider.in 2014-11-17 09:44:59 +0000
42+++ click/soundcloud.provider.in 2016-02-02 11:41:52 +0000
43@@ -21,6 +21,7 @@
44 <setting type="as" name="Scope">['non-expiring']</setting>
45 <setting name="ClientId">eadbbc8380aa72be1412e2abe5f8e4ca</setting>
46 <setting type="as" name="AllowedSchemes">['https','http']</setting>
47+ <setting name="DisableStateParameter" type="b">true</setting>
48 </group>
49 </group>
50 </group>
51
52=== modified file 'data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in'
53--- data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in 2014-11-18 15:08:53 +0000
54+++ data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in 2016-02-02 11:41:52 +0000
55@@ -13,3 +13,5 @@
56 PageHeader.ForegroundColor = #ffffffff
57 PageHeader.Logo = logo.png
58 PageHeader.NavigationBackground = color:///#ddffffff
59+PreviewButtonColor=#FF5500
60+
61
62=== modified file 'include/api/client.h'
63--- include/api/client.h 2014-12-04 13:39:07 +0000
64+++ include/api/client.h 2016-02-02 11:41:52 +0000
65@@ -14,6 +14,7 @@
66 * along with this program. If not, see <http://www.gnu.org/licenses/>.
67 *
68 * Author: Pete Woods <pete.woods@canonical.com>
69+ * Gary Wang <gary.wang@canonical.com>
70 */
71
72 #ifndef API_CLIENT_H_
73@@ -21,6 +22,7 @@
74
75 #include <api/config.h>
76 #include <api/track.h>
77+#include <api/comment.h>
78
79 #include <unity/scopes/OnlineAccountClient.h>
80
81@@ -62,6 +64,32 @@
82
83 virtual std::future<std::deque<Track>> stream_tracks(int limit=0);
84
85+ virtual std::future<std::deque<Comment>> track_comments(const std::string &trackid);
86+
87+ virtual std::future<bool> post_comment(const std::string &trackid,
88+ const std::string &postmsg);
89+
90+ virtual std::future<std::deque<Track>> favorite_tracks();
91+
92+ virtual std::future<std::deque<Track>> get_user_tracks(const std::string &userid,
93+ int limit = 0);
94+
95+ virtual std::future<bool> is_fav_track(const std::string &trackid);
96+
97+ virtual std::future<bool> like_track(const std::string &trackid);
98+
99+ virtual std::future<bool> delete_like_track(const std::string &trackid);
100+
101+ virtual std::future<bool> is_user_follower(const std::string &userid);
102+
103+ virtual std::future<bool> follow_user(const std::string &userid);
104+
105+ virtual std::future<bool> unfollow_user(const std::string &userid);
106+
107+ virtual std::future<User> get_authuser_info();
108+
109+ virtual std::future<User> get_user_info(const std::string &userid);
110+
111 /**
112 * Cancel any pending queries (this method can be called from a different thread)
113 */
114
115=== added file 'include/api/comment.h'
116--- include/api/comment.h 1970-01-01 00:00:00 +0000
117+++ include/api/comment.h 2016-02-02 11:41:52 +0000
118@@ -0,0 +1,70 @@
119+/*
120+ * Copyright (C) 2014 Canonical, Ltd.
121+ *
122+ * This library is free software; you can redistribute it and/or modify it under
123+ * the terms of version 3 of the GNU Lesser General Public License as published
124+ * by the Free Software Foundation.
125+ *
126+ * This library is distributed in the hope that it will be useful, but WITHOUT
127+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
128+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
129+ * details.
130+ *
131+ * You should have received a copy of the GNU Lesser General Public License
132+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
133+ *
134+ * Author: Pete Woods <pete.woods@canonical.com>
135+ * Gary Wang <gary.wang@canonical.com>
136+ */
137+
138+#ifndef API_COMMENT_H_
139+#define API_COMMENT_H_
140+
141+#include <api/user.h>
142+
143+#include <memory>
144+#include <string>
145+
146+namespace Json {
147+class Value;
148+}
149+
150+namespace api {
151+
152+class Comment: public Resource {
153+public:
154+ typedef std::shared_ptr<Comment> Ptr;
155+
156+ Comment(const Json::Value &data);
157+
158+ virtual ~Comment() = default;
159+
160+ const unsigned int & id() const override;
161+
162+ const std::string & title() const override;
163+
164+ const std::string & artwork() const override;
165+
166+ const std::string & body() const;
167+
168+ const std::string & created_at() const;
169+
170+ const User & user() const;
171+
172+ Kind kind() const override;
173+
174+ std::string kind_str() const override;
175+
176+protected:
177+ std::string body_;
178+
179+ std::string created_at_;
180+
181+ unsigned int id_;
182+
183+ User user_;
184+};
185+
186+}
187+
188+#endif // API_COMMENT_H_
189
190=== modified file 'include/api/resource.h'
191--- include/api/resource.h 2014-11-05 16:17:51 +0000
192+++ include/api/resource.h 2016-02-02 11:41:52 +0000
193@@ -14,6 +14,7 @@
194 * along with this program. If not, see <http://www.gnu.org/licenses/>.
195 *
196 * Author: Pete Woods <pete.woods@canonical.com>
197+ * Gary Wang <gary.wang@canonical.com>
198 */
199
200 #ifndef API_RESOURCE_H_
201@@ -27,7 +28,7 @@
202 class Resource {
203 public:
204 enum class Kind {
205- track, user
206+ track, user, comment,
207 };
208
209 typedef std::shared_ptr<Resource> Ptr;
210
211=== modified file 'include/api/track.h'
212--- include/api/track.h 2014-11-06 11:40:45 +0000
213+++ include/api/track.h 2016-02-02 11:41:52 +0000
214@@ -14,6 +14,7 @@
215 * along with this program. If not, see <http://www.gnu.org/licenses/>.
216 *
217 * Author: Pete Woods <pete.woods@canonical.com>
218+ * Gary Wang <gary.wang@canonical.com>
219 */
220
221 #ifndef API_TRACK_H_
222@@ -76,6 +77,16 @@
223
224 unsigned int favoritings_count() const;
225
226+ unsigned int comment_count() const;
227+
228+ unsigned int repost_count() const;
229+
230+ unsigned int likes_count() const;
231+
232+ const std::string & genre() const;
233+
234+ const std::string & original_format() const;
235+
236 const User & user() const;
237
238 Kind kind() const override;
239@@ -121,6 +132,16 @@
240
241 unsigned int favoritings_count_;
242
243+ unsigned int comment_count_;
244+
245+ unsigned int repost_count_;
246+
247+ unsigned int likes_count_;
248+
249+ std::string genre_;
250+
251+ std::string original_format_;
252+
253 User user_;
254 };
255
256
257=== modified file 'include/api/user.h'
258--- include/api/user.h 2014-11-05 16:17:51 +0000
259+++ include/api/user.h 2016-02-02 11:41:52 +0000
260@@ -44,6 +44,16 @@
261
262 const unsigned int & id() const override;
263
264+ const std::string & permalink_url() const;
265+
266+ const unsigned int & track_count() const;
267+
268+ const unsigned int & followers_count() const;
269+
270+ const unsigned int & followings_count() const;
271+
272+ const std::string & bio() const;
273+
274 Kind kind() const override;
275
276 std::string kind_str() const override;
277@@ -54,6 +64,16 @@
278 unsigned int id_;
279
280 std::string artwork_;
281+
282+ std::string permalink_;
283+
284+ unsigned int track_count_;
285+
286+ unsigned int followers_count_;
287+
288+ unsigned int followings_count_;
289+
290+ std::string bio_;
291 };
292
293 }
294
295=== added file 'include/scope/activation.h'
296--- include/scope/activation.h 1970-01-01 00:00:00 +0000
297+++ include/scope/activation.h 2016-02-02 11:41:52 +0000
298@@ -0,0 +1,64 @@
299+/*
300+ * Copyright (C) 2014 Canonical, Ltd.
301+ *
302+ * This library is free software; you can redistribute it and/or modify it under
303+ * the terms of version 3 of the GNU Lesser General Public License as published
304+ * by the Free Software Foundation.
305+ *
306+ * This library is distributed in the hope that it will be useful, but WITHOUT
307+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
308+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
309+ * details.
310+ *
311+ * You should have received a copy of the GNU Lesser General Public License
312+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
313+ *
314+ * Author: Pete Woods <pete.woods@canonical.com>
315+ * Gary Wang <gary.wang@canonical.com>
316+ */
317+
318+#ifndef SCOPE_ACTIVATIOIN_H_
319+#define SCOPE_ACTIVATIOIN_H_
320+
321+#include <api/client.h>
322+
323+#include <unity/scopes/ActivationQueryBase.h>
324+
325+namespace unity {
326+namespace scopes {
327+class Result;
328+}
329+}
330+
331+namespace scope {
332+
333+/**
334+ * Represents an individual action request.
335+ *
336+ * Each time a action is performed in the UI a new Action
337+ * object is created.
338+ */
339+class Activation : public unity::scopes::ActivationQueryBase
340+{
341+public:
342+ Activation(const unity::scopes::Result &result,
343+ const unity::scopes::ActionMetadata & metadata,
344+ std::string const& action_id,
345+ std::shared_ptr<unity::scopes::OnlineAccountClient> oa_client);
346+
347+ ~Activation() = default;
348+
349+ /**
350+ * Trigger the action object with action id.
351+ */
352+ virtual unity::scopes::ActivationResponse activate() override;
353+
354+private:
355+ std::string const action_id_;
356+
357+ api::Client client_;
358+};
359+
360+}
361+
362+#endif // SCOPE_ACTIVATION_H_
363
364=== modified file 'include/scope/preview.h'
365--- include/scope/preview.h 2014-11-10 11:54:32 +0000
366+++ include/scope/preview.h 2016-02-02 11:41:52 +0000
367@@ -14,12 +14,16 @@
368 * along with this program. If not, see <http://www.gnu.org/licenses/>.
369 *
370 * Author: Pete Woods <pete.woods@canonical.com>
371+ * Gary Wang <gary.wang@canonical.com>
372 */
373
374 #ifndef SCOPE_PREVIEW_H_
375 #define SCOPE_PREVIEW_H_
376
377+#include <api/client.h>
378+
379 #include <unity/scopes/PreviewQueryBase.h>
380+#include <unity/scopes/OnlineAccountClient.h>
381
382 namespace unity {
383 namespace scopes {
384@@ -38,7 +42,8 @@
385 class Preview: public unity::scopes::PreviewQueryBase {
386 public:
387 Preview(const unity::scopes::Result &result,
388- const unity::scopes::ActionMetadata &metadata);
389+ const unity::scopes::ActionMetadata &metadata,
390+ std::shared_ptr<unity::scopes::OnlineAccountClient> oa_client);
391
392 ~Preview() = default;
393
394@@ -48,6 +53,9 @@
395 * Populates the reply object with preview information.
396 */
397 void run(unity::scopes::PreviewReplyProxy const& reply) override;
398+
399+private:
400+ api::Client client_;
401 };
402
403 }
404
405=== modified file 'include/scope/query.h'
406--- include/scope/query.h 2014-12-04 13:22:43 +0000
407+++ include/scope/query.h 2016-02-02 11:41:52 +0000
408@@ -51,6 +51,12 @@
409 const unity::scopes::Category::SCPtr &category,
410 const api::Track &track);
411
412+ bool push_user_info(const unity::scopes::SearchReplyProxy &reply,
413+ const unity::scopes::Category::SCPtr &category,
414+ const api::User &user);
415+
416+ bool show_empty_tip(const unity::scopes::SearchReplyProxy &reply);
417+
418 api::Client client_;
419 };
420
421
422=== modified file 'include/scope/scope.h'
423--- include/scope/scope.h 2014-12-04 13:24:10 +0000
424+++ include/scope/scope.h 2016-02-02 11:41:52 +0000
425@@ -61,6 +61,15 @@
426 unity::scopes::CannedQuery const& q,
427 unity::scopes::SearchMetadata const&) override;
428
429+ /**
430+ * Called each time a new action is requested
431+ */
432+ unity::scopes::ActivationQueryBase::UPtr perform_action(
433+ const unity::scopes::Result &result,
434+ const unity::scopes::ActionMetadata &metadata,
435+ std::string const& widget_id,
436+ std::string const& action_id) override;
437+
438 protected:
439 std::shared_ptr<unity::scopes::OnlineAccountClient> oa_client_;
440 };
441
442=== modified file 'po/POTFILES.in'
443--- po/POTFILES.in 2014-11-18 15:08:53 +0000
444+++ po/POTFILES.in 2016-02-02 11:41:52 +0000
445@@ -1,16 +1,20 @@
446-[type: gettext/ini] data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in
447-include/api/resource.h
448-include/api/user.h
449-include/api/config.h
450-include/api/track.h
451-include/api/client.h
452+tests/unit/scope/test-scope.cpp
453+src/scope/preview.cpp
454+src/scope/activation.cpp
455+src/scope/scope.cpp
456+src/scope/query.cpp
457+src/api/user.cpp
458+src/api/client.cpp
459+src/api/track.cpp
460+src/api/comment.cpp
461 include/scope/preview.h
462 include/scope/localization.h
463+include/scope/activation.h
464 include/scope/query.h
465 include/scope/scope.h
466-src/api/client.cpp
467-src/api/track.cpp
468-src/api/user.cpp
469-src/scope/query.cpp
470-src/scope/scope.cpp
471-src/scope/preview.cpp
472\ No newline at end of file
473+include/api/resource.h
474+include/api/track.h
475+include/api/comment.h
476+include/api/client.h
477+include/api/config.h
478+include/api/user.h
479
480=== modified file 'po/en_GB.po'
481--- po/en_GB.po 2014-11-18 16:42:10 +0000
482+++ po/en_GB.po 2016-02-02 11:41:52 +0000
483@@ -8,7 +8,7 @@
484 msgstr ""
485 "Project-Id-Version: unity-scope-soundcloud\n"
486 "Report-Msgid-Bugs-To: \n"
487-"POT-Creation-Date: 2014-11-18 14:00+0000\n"
488+"POT-Creation-Date: 2016-01-26 16:58+0800\n"
489 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
490 "Last-Translator: Pete Woods <EMAIL@ADDRESS>\n"
491 "Language-Team: LANGUAGE <LL@li.org>\n"
492@@ -17,226 +17,310 @@
493 "Content-Type: text/plain; charset=UTF-8\n"
494 "Content-Transfer-Encoding: 8bit\n"
495
496-#: ../src/scope/query.cpp:112
497+#: ../src/scope/query.cpp:416
498+msgid "</b> followers"
499+msgstr ""
500+
501+#: ../src/scope/query.cpp:417
502+msgid "</b> followings"
503+msgstr ""
504+
505+#: ../src/scope/query.cpp:415
506+msgid "</b> tracks"
507+msgstr ""
508+
509+#: ../src/scope/query.cpp:148
510 msgid "Alternative Rock"
511 msgstr "Alternative Rock"
512
513-#: ../src/scope/query.cpp:112
514+#: ../src/scope/query.cpp:148
515 msgid "Ambient"
516 msgstr "Ambient"
517
518-#: ../src/scope/query.cpp:97
519+#: ../src/scope/query.cpp:133
520 msgid "Audiobooks"
521 msgstr "Audiobooks"
522
523-#: ../src/scope/query.cpp:98
524+#: ../src/scope/query.cpp:134
525 msgid "Business"
526 msgstr "Business"
527
528-#: ../src/scope/preview.cpp:84
529+#: ../src/scope/preview.cpp:198
530 msgid "Buy"
531 msgstr "Buy"
532
533-#: ../src/scope/query.cpp:112
534+#: ../src/scope/query.cpp:148
535 msgid "Classical"
536 msgstr "Classical"
537
538-#: ../src/scope/query.cpp:98
539+#: ../src/scope/query.cpp:134
540 msgid "Comedy"
541 msgstr "Comedy"
542
543-#: ../src/scope/query.cpp:112
544+#: ../src/scope/query.cpp:148
545 msgid "Country"
546 msgstr "Country"
547
548-#: ../src/scope/query.cpp:113
549+#: ../src/scope/query.cpp:149
550 msgid "Dance"
551 msgstr "Dance"
552
553-#: ../src/scope/query.cpp:113
554+#: ../src/scope/query.cpp:149
555 msgid "Deep House"
556 msgstr "Deep House"
557
558-#: ../src/scope/query.cpp:113
559+#: ../src/scope/query.cpp:149
560 msgid "Disco"
561 msgstr "Disco"
562
563-#: ../src/scope/query.cpp:113
564+#: ../src/scope/query.cpp:149
565 msgid "Drum & Bass"
566 msgstr "Drum & Bass"
567
568-#: ../src/scope/query.cpp:113
569+#: ../src/scope/query.cpp:149
570 msgid "Dubstep"
571 msgstr "Dubstep"
572
573-#: ../src/scope/query.cpp:114
574+#: ../src/scope/query.cpp:150
575 msgid "Electro"
576 msgstr "Electro"
577
578-#: ../src/scope/query.cpp:114
579+#: ../src/scope/query.cpp:150
580 msgid "Electronic"
581 msgstr "Electronic"
582
583-#: ../src/scope/query.cpp:98
584+#: ../src/scope/query.cpp:134
585 msgid "Entertainment"
586 msgstr "Entertainment"
587
588-#: ../src/scope/query.cpp:220
589+#: ../src/scope/query.cpp:273
590 msgid "Explore"
591 msgstr "Explore"
592
593-#: ../src/scope/query.cpp:114
594+#: ../src/scope/query.cpp:150
595 msgid "Folk"
596 msgstr "Folk"
597
598-#: ../src/scope/query.cpp:114
599+#: ../src/scope/preview.cpp:249
600+msgid "Follow"
601+msgstr ""
602+
603+#: ../src/scope/preview.cpp:105
604+msgid "Get my tracks"
605+msgstr ""
606+
607+#: ../src/scope/preview.cpp:219
608+msgid "Get user tracks"
609+msgstr ""
610+
611+#: ../src/scope/query.cpp:150
612 msgid "Hardcore Techno"
613 msgstr "Hardcore Techno"
614
615-#: ../src/scope/query.cpp:115
616+#: ../src/scope/query.cpp:151
617 msgid "Hip Hop"
618 msgstr "Hip Hop"
619
620-#: ../src/scope/query.cpp:111
621+#: ../src/scope/query.cpp:147
622 msgid "Home"
623 msgstr "Home"
624
625-#: ../src/scope/query.cpp:115
626+#: ../src/scope/query.cpp:151
627 msgid "House"
628 msgstr "House"
629
630-#: ../src/scope/query.cpp:115
631+#: ../src/scope/query.cpp:151
632 msgid "Indie Rock"
633 msgstr "Indie Rock"
634
635-#: ../src/scope/query.cpp:115
636+#: ../src/scope/query.cpp:151
637 msgid "Jazz"
638 msgstr "Jazz"
639
640-#: ../src/scope/query.cpp:115
641+#: ../src/scope/query.cpp:151
642 msgid "Latin"
643 msgstr "Latin"
644
645-#: ../src/scope/query.cpp:98
646+#: ../src/scope/query.cpp:134
647 msgid "Learning"
648 msgstr "Learning"
649
650-#: ../src/scope/query.cpp:306
651+#: ../src/scope/preview.cpp:234
652+msgid "Like"
653+msgstr ""
654+
655+#: ../src/scope/query.cpp:459
656 msgid "Log-in to SoundCloud"
657 msgstr "Log in to SoundCloud"
658
659-#: ../src/scope/query.cpp:116
660+#: ../src/scope/preview.cpp:163
661+#, fuzzy
662+msgid "Login to soundcloud"
663+msgstr "Log in to SoundCloud"
664+
665+#: ../src/scope/query.cpp:152
666 msgid "Metal"
667 msgstr "Metal"
668
669-#: ../src/scope/query.cpp:116
670+#: ../src/scope/query.cpp:152
671 msgid "Minimal Techno"
672 msgstr "Minimal Techno"
673
674-#: ../src/scope/query.cpp:99
675+#: ../src/scope/query.cpp:172
676+msgid "My favorites"
677+msgstr ""
678+
679+#: ../src/scope/query.cpp:135
680 msgid "News & Politics"
681 msgstr "News & Politics"
682
683-#: ../src/scope/query.cpp:116
684+#: ../src/scope/query.cpp:445
685+msgid "No tracks can be found"
686+msgstr ""
687+
688+#: ../src/scope/query.cpp:152
689 msgid "Piano"
690 msgstr "Piano"
691
692-#: ../src/scope/preview.cpp:103
693+#: ../src/scope/preview.cpp:213
694 msgid "Play in browser"
695 msgstr "Play in browser"
696
697-#: ../src/scope/query.cpp:116
698+#: ../src/scope/preview.cpp:155
699+msgid "Please login to post a comment "
700+msgstr ""
701+
702+#: ../src/scope/query.cpp:152
703 msgid "Pop"
704 msgstr "Pop"
705
706-#: ../src/scope/query.cpp:117
707+#: ../src/scope/preview.cpp:187
708+msgid "Post"
709+msgstr ""
710+
711+#: ../src/scope/query.cpp:153
712 msgid "Progressive House"
713 msgstr "Progressive House"
714
715-#: ../src/scope/query.cpp:117
716+#: ../src/scope/query.cpp:153
717 msgid "Punk"
718 msgstr "Punk"
719
720-#: ../src/scope/query.cpp:117
721+#: ../src/scope/query.cpp:153
722 msgid "R&B"
723 msgstr "R&B"
724
725-#: ../src/scope/query.cpp:117
726+#: ../src/scope/query.cpp:153
727 msgid "Rap"
728 msgstr "Rap"
729
730-#: ../src/scope/query.cpp:118
731+#: ../src/scope/query.cpp:154
732 msgid "Reggae"
733 msgstr "Reggae"
734
735-#: ../src/scope/query.cpp:99
736+#: ../src/scope/query.cpp:135
737 msgid "Religion & Spirituality"
738 msgstr "Religion & Spirituality"
739
740-#: ../src/scope/query.cpp:118
741+#: ../src/scope/preview.cpp:229
742+msgid "Remove 'Like'"
743+msgstr ""
744+
745+#: ../src/scope/query.cpp:154
746 msgid "Rock"
747 msgstr "Rock"
748
749-#: ../src/scope/query.cpp:99
750+#: ../src/scope/query.cpp:135
751 msgid "Science"
752 msgstr "Science"
753
754-#: ../src/scope/query.cpp:118
755+#: ../src/scope/query.cpp:154
756 msgid "Singer-Songwriter"
757 msgstr "Singer-Songwriter"
758
759-#: ../src/scope/query.cpp:118
760+#: ../src/scope/query.cpp:154
761 msgid "Soul"
762 msgstr "Soul"
763
764-#: ../data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in.h:1
765-msgid "SoundCloud"
766-msgstr "SoundCloud"
767-
768-#: ../data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in.h:2
769-msgid "SoundCloud scope for Unity8"
770-msgstr "SoundCloud scope for Unity8"
771-
772-#: ../src/scope/query.cpp:100
773+#: ../src/scope/query.cpp:136
774 msgid "Sports"
775 msgstr "Sports"
776
777-#: ../src/scope/query.cpp:100
778+#: ../src/scope/query.cpp:136
779 msgid "Storytelling"
780 msgstr "Storytelling"
781
782-#: ../src/scope/query.cpp:208
783+#: ../src/scope/query.cpp:260
784 msgid "Stream"
785 msgstr "Stream"
786
787-#: ../src/scope/query.cpp:119
788+#: ../src/scope/query.cpp:155
789 msgid "Tech House"
790 msgstr "Tech House"
791
792-#: ../src/scope/query.cpp:119
793+#: ../src/scope/query.cpp:155
794 msgid "Techno"
795 msgstr "Techno"
796
797-#: ../src/scope/query.cpp:100
798+#: ../src/scope/query.cpp:136
799 msgid "Technology"
800 msgstr "Technology"
801
802-#: ../src/scope/query.cpp:119
803+#: ../src/scope/query.cpp:155
804 msgid "Trance"
805 msgstr "Trance"
806
807-#: ../src/scope/query.cpp:119
808+#: ../src/scope/query.cpp:155
809 msgid "Trap"
810 msgstr "Trap"
811
812-#: ../src/scope/query.cpp:120
813+#: ../src/scope/query.cpp:156
814 msgid "Trip Hop"
815 msgstr "Trip Hop"
816
817-#: ../src/scope/preview.cpp:94
818+#: ../src/scope/preview.cpp:244
819+msgid "Unfollow"
820+msgstr ""
821+
822+#: ../src/scope/preview.cpp:100
823+#, fuzzy
824+msgid "View in browser"
825+msgstr "Play in browser"
826+
827+#: ../src/scope/preview.cpp:206
828 msgid "Watch video"
829 msgstr "Watch video"
830
831-#: ../src/scope/query.cpp:120
832+#: ../src/scope/query.cpp:156
833 msgid "World"
834 msgstr "World"
835+
836+#: ../src/scope/query.cpp:380
837+msgid "create time"
838+msgstr ""
839+
840+#: ../src/scope/query.cpp:381
841+msgid "genre"
842+msgstr ""
843+
844+#: ../src/scope/query.cpp:382
845+msgid "license"
846+msgstr ""
847+
848+#: ../src/scope/preview.cpp:162
849+msgid "open"
850+msgstr ""
851+
852+#: ../src/scope/query.cpp:399
853+msgid "track"
854+msgstr ""
855+
856+#: ../src/scope/preview.cpp:68 ../src/scope/query.cpp:431
857+msgid "user"
858+msgstr ""
859+
860+#~ msgid "SoundCloud"
861+#~ msgstr "SoundCloud"
862+
863+#~ msgid "SoundCloud scope for Unity8"
864+#~ msgstr "SoundCloud scope for Unity8"
865
866=== modified file 'po/unity-scope-soundcloud.pot'
867--- po/unity-scope-soundcloud.pot 2014-11-18 15:08:53 +0000
868+++ po/unity-scope-soundcloud.pot 2016-02-02 11:41:52 +0000
869@@ -6,9 +6,9 @@
870 #, fuzzy
871 msgid ""
872 msgstr ""
873-"Project-Id-Version: unity-scope-soundcloud\n"
874+"Project-Id-Version: PACKAGE VERSION\n"
875 "Report-Msgid-Bugs-To: \n"
876-"POT-Creation-Date: 2014-11-18 15:06+0000\n"
877+"POT-Creation-Date: 2016-01-26 16:59+0800\n"
878 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
879 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
880 "Language-Team: LANGUAGE <LL@li.org>\n"
881@@ -17,226 +17,302 @@
882 "Content-Type: text/plain; charset=CHARSET\n"
883 "Content-Transfer-Encoding: 8bit\n"
884
885-#: ../data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in.h:1
886-msgid "SoundCloud"
887-msgstr ""
888-
889-#: ../data/com.ubuntu.scopes.soundcloud_soundcloud.ini.in.h:2
890-msgid "SoundCloud scope for Unity8"
891-msgstr ""
892-
893-#: ../src/scope/query.cpp:97
894+#: ../src/scope/preview.cpp:68 ../src/scope/query.cpp:431
895+msgid "user"
896+msgstr ""
897+
898+#: ../src/scope/preview.cpp:100
899+msgid "View in browser"
900+msgstr ""
901+
902+#: ../src/scope/preview.cpp:105
903+msgid "Get my tracks"
904+msgstr ""
905+
906+#: ../src/scope/preview.cpp:155
907+msgid "Please login to post a comment "
908+msgstr ""
909+
910+#: ../src/scope/preview.cpp:162
911+msgid "open"
912+msgstr ""
913+
914+#: ../src/scope/preview.cpp:163
915+msgid "Login to soundcloud"
916+msgstr ""
917+
918+#: ../src/scope/preview.cpp:187
919+msgid "Post"
920+msgstr ""
921+
922+#: ../src/scope/preview.cpp:198
923+msgid "Buy"
924+msgstr ""
925+
926+#: ../src/scope/preview.cpp:206
927+msgid "Watch video"
928+msgstr ""
929+
930+#: ../src/scope/preview.cpp:213
931+msgid "Play in browser"
932+msgstr ""
933+
934+#: ../src/scope/preview.cpp:219
935+msgid "Get user tracks"
936+msgstr ""
937+
938+#: ../src/scope/preview.cpp:229
939+msgid "Remove 'Like'"
940+msgstr ""
941+
942+#: ../src/scope/preview.cpp:234
943+msgid "Like"
944+msgstr ""
945+
946+#: ../src/scope/preview.cpp:244
947+msgid "Unfollow"
948+msgstr ""
949+
950+#: ../src/scope/preview.cpp:249
951+msgid "Follow"
952+msgstr ""
953+
954+#: ../src/scope/query.cpp:133
955 msgid "Audiobooks"
956 msgstr ""
957
958-#: ../src/scope/query.cpp:98
959+#: ../src/scope/query.cpp:134
960 msgid "Business"
961 msgstr ""
962
963-#: ../src/scope/query.cpp:98
964+#: ../src/scope/query.cpp:134
965 msgid "Comedy"
966 msgstr ""
967
968-#: ../src/scope/query.cpp:98
969+#: ../src/scope/query.cpp:134
970 msgid "Entertainment"
971 msgstr ""
972
973-#: ../src/scope/query.cpp:98
974+#: ../src/scope/query.cpp:134
975 msgid "Learning"
976 msgstr ""
977
978-#: ../src/scope/query.cpp:99
979+#: ../src/scope/query.cpp:135
980 msgid "News & Politics"
981 msgstr ""
982
983-#: ../src/scope/query.cpp:99
984+#: ../src/scope/query.cpp:135
985 msgid "Religion & Spirituality"
986 msgstr ""
987
988-#: ../src/scope/query.cpp:99
989+#: ../src/scope/query.cpp:135
990 msgid "Science"
991 msgstr ""
992
993-#: ../src/scope/query.cpp:100
994+#: ../src/scope/query.cpp:136
995 msgid "Sports"
996 msgstr ""
997
998-#: ../src/scope/query.cpp:100
999+#: ../src/scope/query.cpp:136
1000 msgid "Storytelling"
1001 msgstr ""
1002
1003-#: ../src/scope/query.cpp:100
1004+#: ../src/scope/query.cpp:136
1005 msgid "Technology"
1006 msgstr ""
1007
1008-#: ../src/scope/query.cpp:111
1009+#: ../src/scope/query.cpp:147
1010 msgid "Home"
1011 msgstr ""
1012
1013-#: ../src/scope/query.cpp:112
1014+#: ../src/scope/query.cpp:148
1015 msgid "Alternative Rock"
1016 msgstr ""
1017
1018-#: ../src/scope/query.cpp:112
1019+#: ../src/scope/query.cpp:148
1020 msgid "Ambient"
1021 msgstr ""
1022
1023-#: ../src/scope/query.cpp:112
1024+#: ../src/scope/query.cpp:148
1025 msgid "Classical"
1026 msgstr ""
1027
1028-#: ../src/scope/query.cpp:112
1029+#: ../src/scope/query.cpp:148
1030 msgid "Country"
1031 msgstr ""
1032
1033-#: ../src/scope/query.cpp:113
1034+#: ../src/scope/query.cpp:149
1035 msgid "Dance"
1036 msgstr ""
1037
1038-#: ../src/scope/query.cpp:113
1039+#: ../src/scope/query.cpp:149
1040 msgid "Deep House"
1041 msgstr ""
1042
1043-#: ../src/scope/query.cpp:113
1044+#: ../src/scope/query.cpp:149
1045 msgid "Disco"
1046 msgstr ""
1047
1048-#: ../src/scope/query.cpp:113
1049+#: ../src/scope/query.cpp:149
1050 msgid "Drum & Bass"
1051 msgstr ""
1052
1053-#: ../src/scope/query.cpp:113
1054+#: ../src/scope/query.cpp:149
1055 msgid "Dubstep"
1056 msgstr ""
1057
1058-#: ../src/scope/query.cpp:114
1059+#: ../src/scope/query.cpp:150
1060 msgid "Electro"
1061 msgstr ""
1062
1063-#: ../src/scope/query.cpp:114
1064+#: ../src/scope/query.cpp:150
1065 msgid "Electronic"
1066 msgstr ""
1067
1068-#: ../src/scope/query.cpp:114
1069+#: ../src/scope/query.cpp:150
1070 msgid "Folk"
1071 msgstr ""
1072
1073-#: ../src/scope/query.cpp:114
1074+#: ../src/scope/query.cpp:150
1075 msgid "Hardcore Techno"
1076 msgstr ""
1077
1078-#: ../src/scope/query.cpp:115
1079+#: ../src/scope/query.cpp:151
1080 msgid "Hip Hop"
1081 msgstr ""
1082
1083-#: ../src/scope/query.cpp:115
1084+#: ../src/scope/query.cpp:151
1085 msgid "House"
1086 msgstr ""
1087
1088-#: ../src/scope/query.cpp:115
1089+#: ../src/scope/query.cpp:151
1090 msgid "Indie Rock"
1091 msgstr ""
1092
1093-#: ../src/scope/query.cpp:115
1094+#: ../src/scope/query.cpp:151
1095 msgid "Jazz"
1096 msgstr ""
1097
1098-#: ../src/scope/query.cpp:115
1099+#: ../src/scope/query.cpp:151
1100 msgid "Latin"
1101 msgstr ""
1102
1103-#: ../src/scope/query.cpp:116
1104+#: ../src/scope/query.cpp:152
1105 msgid "Metal"
1106 msgstr ""
1107
1108-#: ../src/scope/query.cpp:116
1109+#: ../src/scope/query.cpp:152
1110 msgid "Minimal Techno"
1111 msgstr ""
1112
1113-#: ../src/scope/query.cpp:116
1114+#: ../src/scope/query.cpp:152
1115 msgid "Piano"
1116 msgstr ""
1117
1118-#: ../src/scope/query.cpp:116
1119+#: ../src/scope/query.cpp:152
1120 msgid "Pop"
1121 msgstr ""
1122
1123-#: ../src/scope/query.cpp:117
1124+#: ../src/scope/query.cpp:153
1125 msgid "Progressive House"
1126 msgstr ""
1127
1128-#: ../src/scope/query.cpp:117
1129+#: ../src/scope/query.cpp:153
1130 msgid "Punk"
1131 msgstr ""
1132
1133-#: ../src/scope/query.cpp:117
1134+#: ../src/scope/query.cpp:153
1135 msgid "R&B"
1136 msgstr ""
1137
1138-#: ../src/scope/query.cpp:117
1139+#: ../src/scope/query.cpp:153
1140 msgid "Rap"
1141 msgstr ""
1142
1143-#: ../src/scope/query.cpp:118
1144+#: ../src/scope/query.cpp:154
1145 msgid "Reggae"
1146 msgstr ""
1147
1148-#: ../src/scope/query.cpp:118
1149+#: ../src/scope/query.cpp:154
1150 msgid "Rock"
1151 msgstr ""
1152
1153-#: ../src/scope/query.cpp:118
1154+#: ../src/scope/query.cpp:154
1155 msgid "Singer-Songwriter"
1156 msgstr ""
1157
1158-#: ../src/scope/query.cpp:118
1159+#: ../src/scope/query.cpp:154
1160 msgid "Soul"
1161 msgstr ""
1162
1163-#: ../src/scope/query.cpp:119
1164+#: ../src/scope/query.cpp:155
1165 msgid "Tech House"
1166 msgstr ""
1167
1168-#: ../src/scope/query.cpp:119
1169+#: ../src/scope/query.cpp:155
1170 msgid "Techno"
1171 msgstr ""
1172
1173-#: ../src/scope/query.cpp:119
1174+#: ../src/scope/query.cpp:155
1175 msgid "Trance"
1176 msgstr ""
1177
1178-#: ../src/scope/query.cpp:119
1179+#: ../src/scope/query.cpp:155
1180 msgid "Trap"
1181 msgstr ""
1182
1183-#: ../src/scope/query.cpp:120
1184+#: ../src/scope/query.cpp:156
1185 msgid "Trip Hop"
1186 msgstr ""
1187
1188-#: ../src/scope/query.cpp:120
1189+#: ../src/scope/query.cpp:156
1190 msgid "World"
1191 msgstr ""
1192
1193-#: ../src/scope/query.cpp:208
1194+#: ../src/scope/query.cpp:172
1195+msgid "My favorites"
1196+msgstr ""
1197+
1198+#: ../src/scope/query.cpp:260
1199 msgid "Stream"
1200 msgstr ""
1201
1202-#: ../src/scope/query.cpp:220
1203+#: ../src/scope/query.cpp:273
1204 msgid "Explore"
1205 msgstr ""
1206
1207-#: ../src/scope/query.cpp:306
1208+#: ../src/scope/query.cpp:380
1209+msgid "create time"
1210+msgstr ""
1211+
1212+#: ../src/scope/query.cpp:381
1213+msgid "genre"
1214+msgstr ""
1215+
1216+#: ../src/scope/query.cpp:382
1217+msgid "license"
1218+msgstr ""
1219+
1220+#: ../src/scope/query.cpp:399
1221+msgid "track"
1222+msgstr ""
1223+
1224+#: ../src/scope/query.cpp:415
1225+msgid "</b> tracks"
1226+msgstr ""
1227+
1228+#: ../src/scope/query.cpp:416
1229+msgid "</b> followers"
1230+msgstr ""
1231+
1232+#: ../src/scope/query.cpp:417
1233+msgid "</b> followings"
1234+msgstr ""
1235+
1236+#: ../src/scope/query.cpp:445
1237+msgid "No tracks can be found"
1238+msgstr ""
1239+
1240+#: ../src/scope/query.cpp:459
1241 msgid "Log-in to SoundCloud"
1242 msgstr ""
1243-
1244-#: ../src/scope/preview.cpp:84
1245-msgid "Buy"
1246-msgstr ""
1247-
1248-#: ../src/scope/preview.cpp:94
1249-msgid "Watch video"
1250-msgstr ""
1251-
1252-#: ../src/scope/preview.cpp:103
1253-msgid "Play in browser"
1254-msgstr ""
1255
1256=== modified file 'src/CMakeLists.txt'
1257--- src/CMakeLists.txt 2014-11-18 15:08:53 +0000
1258+++ src/CMakeLists.txt 2016-02-02 11:41:52 +0000
1259@@ -32,9 +32,11 @@
1260 api/client.cpp
1261 api/track.cpp
1262 api/user.cpp
1263+ api/comment.cpp
1264 scope/preview.cpp
1265 scope/query.cpp
1266 scope/scope.cpp
1267+ scope/activation.cpp
1268 )
1269
1270 # Find all the headers
1271
1272=== modified file 'src/api/client.cpp'
1273--- src/api/client.cpp 2014-12-04 14:09:22 +0000
1274+++ src/api/client.cpp 2016-02-02 11:41:52 +0000
1275@@ -14,13 +14,16 @@
1276 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1277 *
1278 * Author: Pete Woods <pete.woods@canonical.com>
1279+ * Gary Wang <gary.wang@canonical.com>
1280 */
1281
1282 #include <api/client.h>
1283 #include <api/track.h>
1284+#include <api/comment.h>
1285
1286 #include <boost/iostreams/filtering_stream.hpp>
1287 #include <boost/iostreams/filter/gzip.hpp>
1288+#include <boost/algorithm/string.hpp>
1289 #include <core/net/error.h>
1290 #include <core/net/http/client.h>
1291 #include <core/net/http/content_type.h>
1292@@ -70,6 +73,26 @@
1293 return results;
1294 }
1295
1296+template<typename T>
1297+static T get_typed_authuser_info(const string &filter, const json::Value &root) {
1298+ T results((json::Value()));
1299+
1300+ string kind = root["kind"].asString();
1301+
1302+ if (kind == filter) {
1303+ return T(root);
1304+ }
1305+ return results;
1306+}
1307+
1308+template<typename T>
1309+static T is_successful(const json::Value &root) {
1310+ T results = (boost::algorithm::contains(root["status"].asString(), "201")
1311+ || boost::algorithm::contains(root["status"].asString(), "200")
1312+ || root["id"].asUInt() > 0);
1313+ return results;
1314+}
1315+
1316 }
1317
1318 class Client::Priv {
1319@@ -101,6 +124,55 @@
1320 const net::Uri::QueryParameters &parameters,
1321 http::Request::Handler &handler) {
1322 std::lock_guard<std::mutex> lock(config_mutex_);
1323+ http::Request::Configuration configuration = net_config(path, parameters);
1324+ configuration.header.add("User-Agent", config_.user_agent + " (gzip)");
1325+ configuration.header.add("Accept-Encoding", "gzip");
1326+
1327+ auto request = client_->head(configuration);
1328+ request->async_execute(handler);
1329+ }
1330+
1331+ void post(const net::Uri::Path &path,
1332+ const net::Uri::QueryParameters &parameters,
1333+ const std::string &postmsg,
1334+ const std::string &content_type,
1335+ http::Request::Handler &handler) {
1336+ std::lock_guard<std::mutex> lock(config_mutex_);
1337+ http::Request::Configuration configuration = net_config(path, parameters);
1338+ configuration.header.add("User-Agent", config_.user_agent);
1339+ configuration.header.add("Content-Type", content_type);
1340+
1341+ auto request = client_->post(configuration, postmsg, content_type);
1342+ request->async_execute(handler);
1343+ }
1344+
1345+ void put(const net::Uri::Path &path,
1346+ const net::Uri::QueryParameters &parameters,
1347+ const std::string &postmsg,
1348+ http::Request::Handler &handler) {
1349+ std::lock_guard<std::mutex> lock(config_mutex_);
1350+ http::Request::Configuration configuration = net_config(path, parameters);
1351+ configuration.header.add("User-Agent", config_.user_agent);
1352+ std::istringstream is(postmsg);
1353+
1354+ auto request = client_->put(configuration, is, postmsg.length());
1355+ request->async_execute(handler);
1356+ }
1357+
1358+ void del(const net::Uri::Path &path,
1359+ const net::Uri::QueryParameters &parameters,
1360+ http::Request::Handler &handler) {
1361+ std::lock_guard<std::mutex> lock(config_mutex_);
1362+ http::Request::Configuration configuration = net_config(path, parameters);
1363+ configuration.header.add("User-Agent", config_.user_agent);
1364+ configuration.header.add("X-HTTP-Method-Override", "DELETE");
1365+
1366+ auto request = client_->post(configuration, "", "");
1367+ request->async_execute(handler);
1368+ }
1369+
1370+ http::Request::Configuration net_config(const net::Uri::Path &path,
1371+ const net::Uri::QueryParameters &parameters) {
1372 update_config();
1373
1374 http::Request::Configuration configuration;
1375@@ -115,12 +187,9 @@
1376 net::Uri uri = net::make_uri(config_.apiroot, path,
1377 complete_parameters);
1378 configuration.uri = client_->uri_to_string(uri);
1379- configuration.header.add("User-Agent", config_.user_agent + " (gzip)");
1380- configuration.header.add("Accept-Encoding", "gzip");
1381
1382- auto request = client_->head(configuration);
1383- request->async_execute(handler);
1384- }
1385+ return configuration;
1386+ }
1387
1388 http::Request::Progress::Next progress_report(
1389 const http::Request::Progress&) {
1390@@ -164,11 +233,13 @@
1391 json::Reader reader;
1392 reader.parse(decompressed, root);
1393
1394- if (response.status != http::Status::ok) {
1395- prom->set_exception(make_exception_ptr(domain_error(root["error"].asString())));
1396- } else {
1397+ //Soundcloud api return 404 if track is not in auth user's favorite list
1398+ //or auth user is not following one certain user.
1399+// if (response.status != http::Status::ok) {
1400+// prom->set_exception(make_exception_ptr(domain_error(root["error"].asString())));
1401+// } else {
1402 prom->set_value(func(root));
1403- }
1404+// }
1405 });
1406
1407 get(path, parameters, handler);
1408@@ -176,6 +247,108 @@
1409 return prom->get_future();
1410 }
1411
1412+ template<typename T>
1413+ future<T> async_post(const net::Uri::Path &path,
1414+ const net::Uri::QueryParameters &parameters,
1415+ const std::string &postmsg,
1416+ const std::string &content_type,
1417+ const function<T(const json::Value &root)> &func) {
1418+ auto prom = make_shared<promise<T>>();
1419+
1420+ http::Request::Handler handler;
1421+ handler.on_progress(
1422+ bind(&Client::Priv::progress_report, this, placeholders::_1));
1423+ handler.on_error([prom](const net::Error& e)
1424+ {
1425+ prom->set_exception(make_exception_ptr(e));
1426+ });
1427+ handler.on_response(
1428+ [prom,func](const http::Response& response)
1429+ {
1430+ json::Value root;
1431+ json::Reader reader;
1432+ reader.parse(response.body, root);
1433+
1434+ if (response.status != http::Status::ok &&
1435+ response.status != http::Status::created) {
1436+ prom->set_exception(make_exception_ptr(domain_error(root["error"].asString())));
1437+ } else {
1438+ prom->set_value(func(root));
1439+ }
1440+ });
1441+
1442+ post(path, parameters, postmsg, content_type, handler);
1443+
1444+ return prom->get_future();
1445+ }
1446+
1447+ template<typename T>
1448+ future<T> async_put(const net::Uri::Path &path,
1449+ const net::Uri::QueryParameters &parameters,
1450+ const std::string &msg,
1451+ const function<T(const json::Value &root)> &func) {
1452+ auto prom = make_shared<promise<T>>();
1453+
1454+ http::Request::Handler handler;
1455+ handler.on_progress(
1456+ bind(&Client::Priv::progress_report, this, placeholders::_1));
1457+ handler.on_error([prom](const net::Error& e)
1458+ {
1459+ prom->set_exception(make_exception_ptr(e));
1460+ });
1461+ handler.on_response(
1462+ [prom,func](const http::Response& response)
1463+ {
1464+ json::Value root;
1465+ json::Reader reader;
1466+ reader.parse(response.body, root);
1467+
1468+ if (response.status != http::Status::created &&
1469+ response.status != http::Status::ok) {
1470+ prom->set_exception(make_exception_ptr(domain_error(root["error"].asString())));
1471+ } else {
1472+ prom->set_value(func(root));
1473+ }
1474+ });
1475+
1476+ put(path, parameters, msg, handler);
1477+
1478+ return prom->get_future();
1479+ }
1480+
1481+ template<typename T>
1482+ future<T> async_del(const net::Uri::Path &path,
1483+ const net::Uri::QueryParameters &parameters,
1484+ const function<T(const json::Value &root)> &func) {
1485+ auto prom = make_shared<promise<T>>();
1486+
1487+ http::Request::Handler handler;
1488+ handler.on_progress(
1489+ bind(&Client::Priv::progress_report, this, placeholders::_1));
1490+ handler.on_error([prom](const net::Error& e)
1491+ {
1492+ prom->set_exception(make_exception_ptr(e));
1493+ });
1494+ handler.on_response(
1495+ [prom,func](const http::Response& response)
1496+ {
1497+ json::Value root;
1498+ json::Reader reader;
1499+ reader.parse(response.body, root);
1500+
1501+ if (response.status != http::Status::created &&
1502+ response.status != http::Status::ok) {
1503+ prom->set_exception(make_exception_ptr(domain_error(root["error"].asString())));
1504+ } else {
1505+ prom->set_value(func(root));
1506+ }
1507+ });
1508+
1509+ del(path, parameters, handler);
1510+
1511+ return prom->get_future();
1512+ }
1513+
1514 std::string client_id() {
1515 std::lock_guard<std::mutex> lock(config_mutex_);
1516 update_config();
1517@@ -277,6 +450,147 @@
1518 });
1519 }
1520
1521+future<deque<Comment>> Client::track_comments(const std::string &trackid) {
1522+ net::Uri::QueryParameters params;
1523+
1524+ return p->async_get<deque<Comment>>(
1525+ { "tracks", trackid, "comments.json"}, params,
1526+ [](const json::Value &root) {
1527+ auto results = get_typed_list<Comment>("comment", root);
1528+ return results;
1529+ });
1530+}
1531+
1532+future<bool> Client::post_comment(const std::string &trackid,
1533+ const std::string &postmsg) {
1534+ net::Uri::QueryParameters params;
1535+
1536+ string postbody = "<comment><body>"+ postmsg + "</body></comment>";
1537+ std::string content_type = "application/xml";
1538+ return p->async_post<bool>(
1539+ { "tracks", trackid, "comments.json"}, params, postbody, content_type,
1540+ [](const json::Value &root) {
1541+ auto results = is_successful<bool>(root);
1542+ return results;
1543+ });
1544+}
1545+
1546+std::future<std::deque<Track> > Client::favorite_tracks()
1547+{
1548+ net::Uri::QueryParameters params;
1549+
1550+ return p->async_get<deque<Track>>(
1551+ { "me", "favorites.json"}, params,
1552+ [](const json::Value &root) {
1553+ return get_typed_list<Track>("track", root);
1554+ });
1555+}
1556+
1557+std::future<std::deque<Track> > Client::get_user_tracks(const string &userid,
1558+ int limit)
1559+{
1560+ net::Uri::QueryParameters params;
1561+ if (limit > 0) {
1562+ params.emplace_back("limit", std::to_string(limit));
1563+ }
1564+ return p->async_get<deque<Track>>(
1565+ { "users", userid, "tracks.json"}, params,
1566+ [](const json::Value &root) {
1567+ return get_typed_list<Track>("track", root);
1568+ });
1569+}
1570+
1571+future<bool> Client::is_fav_track(const std::string &trackid) {
1572+ net::Uri::QueryParameters params;
1573+
1574+ return p->async_get<bool>(
1575+ { "me", "favorites", trackid}, params,
1576+ [](const json::Value &root) {
1577+ auto results = is_successful<bool>(root);
1578+ return results;
1579+ });
1580+}
1581+
1582+future<bool> Client::like_track(const std::string &trackid) {
1583+ net::Uri::QueryParameters params;
1584+
1585+ return p->async_put<bool>(
1586+ { "me", "favorites", trackid}, params, "",
1587+ [](const json::Value &root) {
1588+ auto results = is_successful<bool>(root);
1589+ return results;
1590+ });
1591+}
1592+
1593+future<bool> Client::delete_like_track(const std::string &trackid) {
1594+ net::Uri::QueryParameters params;
1595+
1596+ return p->async_del<bool>(
1597+ { "me", "favorites", trackid}, params,
1598+ [](const json::Value &root) {
1599+ auto results = is_successful<bool>(root);
1600+ return results;
1601+ });
1602+}
1603+
1604+std::future<bool> Client::is_user_follower(const string &userid) {
1605+ net::Uri::QueryParameters params;
1606+
1607+ return p->async_get<bool>(
1608+ { "me", "followings", userid}, params,
1609+ [](const json::Value &root) {
1610+ auto results = is_successful<bool>(root);
1611+ return results;
1612+ });
1613+}
1614+
1615+future<bool> Client::follow_user(const std::string &userid) {
1616+ net::Uri::QueryParameters params;
1617+
1618+ return p->async_put<bool>(
1619+ { "me", "followings", userid}, params, "",
1620+ [](const json::Value &root) {
1621+ auto results = is_successful<bool>(root);
1622+ return results;
1623+ });
1624+}
1625+
1626+std::future<bool> Client::unfollow_user(const string &userid)
1627+{
1628+ net::Uri::QueryParameters params;
1629+
1630+ return p->async_del<bool>(
1631+ { "me", "followings", userid}, params,
1632+ [](const json::Value &root) {
1633+ auto results = is_successful<bool>(root);
1634+ return results;
1635+ });
1636+}
1637+
1638+std::future<User> Client::get_authuser_info()
1639+{
1640+ net::Uri::QueryParameters params;
1641+
1642+ return p->async_get<User>(
1643+ { "me" }, params,
1644+ [](const json::Value &root) {
1645+ auto results = get_typed_authuser_info<User>("user", root);
1646+ return results;
1647+ });
1648+}
1649+
1650+std::future<User> Client::get_user_info(const string &userid)
1651+{
1652+ net::Uri::QueryParameters params;
1653+
1654+ return p->async_get<User>(
1655+ { "users", userid}, params,
1656+ [](const json::Value &root) {
1657+ auto results = get_typed_authuser_info<User>("user", root);
1658+ return results;
1659+ });
1660+}
1661+
1662 void Client::cancel() {
1663 p->cancelled_ = true;
1664 }
1665
1666=== added file 'src/api/comment.cpp'
1667--- src/api/comment.cpp 1970-01-01 00:00:00 +0000
1668+++ src/api/comment.cpp 2016-02-02 11:41:52 +0000
1669@@ -0,0 +1,71 @@
1670+/*
1671+ * Copyright (C) 2014 Canonical, Ltd.
1672+ *
1673+ * This library is free software; you can redistribute it and/or modify it under
1674+ * the terms of version 3 of the GNU Lesser General Public License as published
1675+ * by the Free Software Foundation.
1676+ *
1677+ * This library is distributed in the hope that it will be useful, but WITHOUT
1678+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1679+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
1680+ * details.
1681+ *
1682+ * You should have received a copy of the GNU Lesser General Public License
1683+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1684+ *
1685+ * Author: Pete Woods <pete.woods@canonical.com>
1686+ * Gary Wang <gary.wang@canonical.com>
1687+ */
1688+
1689+#include <boost/algorithm/string.hpp>
1690+#include <api/comment.h>
1691+
1692+#include <json/json.h>
1693+
1694+namespace json = Json;
1695+using namespace api;
1696+using namespace std;
1697+
1698+Comment::Comment(const json::Value &data) :
1699+ user_(data["user"]) {
1700+ body_ = data["body"].asString();
1701+ created_at_ = data["created_at"].asString();
1702+
1703+ std::vector<std::string> created_time;
1704+ boost::split(created_time, created_at_, boost::is_any_of(" "));
1705+ created_at_ = created_time[0];
1706+
1707+ id_ = data["id"].asUInt();
1708+}
1709+
1710+const unsigned int & Comment::id() const {
1711+ return id_;
1712+}
1713+
1714+const string & Comment::body() const {
1715+ return body_;
1716+}
1717+
1718+const string & Comment::title() const {
1719+ return user_.title();
1720+}
1721+
1722+const string & Comment::artwork() const {
1723+ return user_.artwork();
1724+}
1725+
1726+const string & Comment::created_at() const {
1727+ return created_at_;
1728+}
1729+
1730+const User & Comment::user() const {
1731+ return user_;
1732+}
1733+
1734+Resource::Kind Comment::kind() const {
1735+ return Resource::Kind::comment;
1736+}
1737+
1738+std::string Comment::kind_str() const {
1739+ return "comment";
1740+}
1741
1742=== modified file 'src/api/track.cpp'
1743--- src/api/track.cpp 2014-11-06 11:40:45 +0000
1744+++ src/api/track.cpp 2016-02-02 11:41:52 +0000
1745@@ -14,8 +14,10 @@
1746 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1747 *
1748 * Author: Pete Woods <pete.woods@canonical.com>
1749+ * Gary Wang <gary.wang@canonical.com>
1750 */
1751
1752+#include <boost/algorithm/string.hpp>
1753 #include <api/track.h>
1754
1755 #include <json/json.h>
1756@@ -33,13 +35,26 @@
1757 duration_ = data["duration"].asUInt();
1758 license_ = data["license"].asString();
1759 created_at_ = data["created_at"].asString();
1760+
1761+ std::vector<std::string> created_time;
1762+ boost::split(created_time, created_at_, boost::is_any_of(" "));
1763+ created_at_ = created_time[0];
1764
1765 playback_count_ = data["playback_count"].asUInt();
1766 favoritings_count_ = data["favoritings_count"].asUInt();
1767+ comment_count_ = data["comment_count"].asUInt();
1768+ repost_count_ = data["reposts_count"].asUInt();
1769+ likes_count_ = data["likes_count"].asUInt();
1770
1771 artwork_ = data["artwork_url"].asString();
1772 waveform_ = data["waveform_url"].asString();
1773-
1774+ //when loading login user stream, server gives waveform
1775+ //sample json file instread of image
1776+ if (boost::algorithm::ends_with(waveform_, "json")) {
1777+ boost::replace_all(waveform_, "json", "png");
1778+ boost::replace_all(waveform_, "is.", "1.");
1779+ }
1780+
1781 streamable_ = data["streamable"].asBool();
1782 downloadable_ = data["downloadable"].asBool();
1783
1784@@ -48,6 +63,9 @@
1785 stream_url_ = data["stream_url"].asString();
1786 download_url_ = data["download_url"].asString();
1787 video_url_ = data["video_url"].asString();
1788+
1789+ genre_ = data["genre"].asString();
1790+ original_format_ = data["original_format"].asString();
1791 }
1792
1793 const string & Track::title() const {
1794@@ -126,6 +144,26 @@
1795 return favoritings_count_;
1796 }
1797
1798+unsigned int Track::comment_count() const {
1799+ return comment_count_;
1800+}
1801+
1802+unsigned int Track::repost_count() const {
1803+ return repost_count_;
1804+}
1805+
1806+unsigned int Track::likes_count() const {
1807+ return likes_count_;
1808+}
1809+
1810+const string & Track::genre() const {
1811+ return genre_;
1812+}
1813+
1814+const string & Track::original_format() const {
1815+ return original_format_;
1816+}
1817+
1818 const User & Track::user() const {
1819 return user_;
1820 }
1821
1822=== modified file 'src/api/user.cpp'
1823--- src/api/user.cpp 2014-11-05 16:17:51 +0000
1824+++ src/api/user.cpp 2016-02-02 11:41:52 +0000
1825@@ -28,6 +28,11 @@
1826 title_ = data["username"].asString();
1827 id_ = data["id"].asUInt();
1828 artwork_ = data["avatar_url"].asString();
1829+ permalink_ = data["permalink_url"].asString();
1830+ track_count_ = data["track_count"].asUInt();
1831+ followers_count_ = data["followers_count"].asUInt();
1832+ followings_count_ = data["followings_count"].asUInt();
1833+ bio_ = data["description"].asString();
1834 }
1835
1836 const string & User::title() const {
1837@@ -38,6 +43,31 @@
1838 return id_;
1839 }
1840
1841+const string &User::permalink_url() const
1842+{
1843+ return permalink_;
1844+}
1845+
1846+const unsigned int &User::track_count() const
1847+{
1848+ return track_count_;
1849+}
1850+
1851+const unsigned int &User::followers_count() const
1852+{
1853+ return followers_count_;
1854+}
1855+
1856+const unsigned int &User::followings_count() const
1857+{
1858+ return followings_count_;
1859+}
1860+
1861+const string &User::bio() const
1862+{
1863+ return bio_;
1864+}
1865+
1866 const string & User::artwork() const {
1867 return artwork_;
1868 }
1869
1870=== added file 'src/scope/activation.cpp'
1871--- src/scope/activation.cpp 1970-01-01 00:00:00 +0000
1872+++ src/scope/activation.cpp 2016-02-02 11:41:52 +0000
1873@@ -0,0 +1,91 @@
1874+/*
1875+ * Copyright (C) 2014 Canonical, Ltd.
1876+ *
1877+ * This library is free software; you can redistribute it and/or modify it under
1878+ * the terms of version 3 of the GNU Lesser General Public License as published
1879+ * by the Free Software Foundation.
1880+ *
1881+ * This library is distributed in the hope that it will be useful, but WITHOUT
1882+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1883+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
1884+ * details.
1885+ *
1886+ * You should have received a copy of the GNU Lesser General Public License
1887+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1888+ *
1889+ * Author: Pete Woods <pete.woods@canonical.com>
1890+ * Gary Wang <gary.wang@canonical.com>
1891+ */
1892+
1893+#include <scope/activation.h>
1894+#include <unity/scopes/ActivationResponse.h>
1895+#include <unity/scopes/ActionMetadata.h>
1896+
1897+#include <iostream>
1898+
1899+namespace sc = unity::scopes;
1900+
1901+using namespace std;
1902+using namespace scope;
1903+using namespace api;
1904+
1905+template<typename T>
1906+static T get_or_throw(future<T> &f) {
1907+ if (f.wait_for(std::chrono::seconds(10)) != future_status::ready) {
1908+ throw domain_error("HTTP request timeout");
1909+ }
1910+ return f.get();
1911+}
1912+
1913+Activation::Activation(const sc::Result &result,
1914+ const sc::ActionMetadata &metadata,
1915+ std::string const& action_id,
1916+ std::shared_ptr<sc::OnlineAccountClient> oa_client) :
1917+ sc::ActivationQueryBase(result, metadata),
1918+ action_id_(action_id),
1919+ client_(oa_client) {
1920+}
1921+
1922+sc::ActivationResponse Activation::activate() {
1923+ try {
1924+ string trackid = result()["id"].get_string();
1925+ string userid = result()["userid"].get_string();
1926+
1927+ if (action_id_ == "commented") {
1928+ string comments = action_metadata().scope_data().get_dict()["comment"].get_string();
1929+ future<bool> post_future = client_.post_comment(trackid, comments);
1930+ auto status = get_or_throw(post_future);
1931+ cout<< "auth user post a comment: " << status << endl;
1932+
1933+ return sc::ActivationResponse(sc::ActivationResponse::Status::ShowPreview);
1934+ } else if (action_id_ == "like") {
1935+ future<bool> like_future = client_.like_track(trackid);
1936+ auto status = get_or_throw(like_future);
1937+ cout<< "auth user likes track: " << status << endl;
1938+
1939+ return sc::ActivationResponse(sc::ActivationResponse::Status::ShowPreview);
1940+ } else if (action_id_ == "deletelike") {
1941+ future<bool> ret_future = client_.delete_like_track(trackid);
1942+ auto status = get_or_throw(ret_future);
1943+ cout<< "auth user delete a like track: " << status << endl;
1944+
1945+ return sc::ActivationResponse(sc::ActivationResponse::Status::ShowPreview);
1946+ } else if (action_id_ == "follow") {
1947+ future<bool> follow_future = client_.follow_user(userid);
1948+ auto status = get_or_throw(follow_future);
1949+ cout<< "auth user follow user: " << status << endl;
1950+
1951+ return sc::ActivationResponse(sc::ActivationResponse::Status::ShowPreview);
1952+ } else if (action_id_ == "unfollow") {
1953+ future<bool> unfollow_future = client_.unfollow_user(userid);
1954+ auto status = get_or_throw(unfollow_future);
1955+ cout<< "auth user unfollow user: " << status << endl;
1956+
1957+ return sc::ActivationResponse(sc::ActivationResponse::Status::ShowPreview);
1958+ }
1959+ }catch (domain_error &e) {
1960+ cerr << e.what() << endl;
1961+ return sc::ActivationResponse(sc::ActivationResponse::Status::ShowPreview);
1962+ }
1963+ return sc::ActivationResponse(sc::ActivationResponse::Status::NotHandled);
1964+}
1965
1966=== modified file 'src/scope/preview.cpp'
1967--- src/scope/preview.cpp 2014-11-10 11:54:32 +0000
1968+++ src/scope/preview.cpp 2016-02-02 11:41:52 +0000
1969@@ -14,10 +14,15 @@
1970 * along with this program. If not, see <http://www.gnu.org/licenses/>.
1971 *
1972 * Author: Pete Woods <pete.woods@canonical.com>
1973+ * Gary Wang <gary.wang@canonical.com>
1974 */
1975
1976+#include <boost/algorithm/string.hpp>
1977+
1978 #include <scope/localization.h>
1979 #include <scope/preview.h>
1980+#include <api/client.h>
1981+#include <api/comment.h>
1982
1983 #include <unity/scopes/ColumnLayout.h>
1984 #include <unity/scopes/PreviewWidget.h>
1985@@ -31,83 +36,250 @@
1986
1987 using namespace std;
1988 using namespace scope;
1989-
1990-Preview::Preview(const sc::Result &result, const sc::ActionMetadata &metadata) :
1991- sc::PreviewQueryBase(result, metadata) {
1992+using namespace api;
1993+
1994+template<typename T>
1995+static T get_or_throw(future<T> &f) {
1996+ if (f.wait_for(std::chrono::seconds(10)) != future_status::ready) {
1997+ throw domain_error("HTTP request timeout");
1998+ }
1999+ return f.get();
2000+}
2001+
2002+Preview::Preview(const sc::Result &result, const sc::ActionMetadata &metadata,
2003+ std::shared_ptr<sc::OnlineAccountClient> oa_client) :
2004+ sc::PreviewQueryBase(result, metadata),
2005+ client_(oa_client) {
2006 }
2007
2008 void Preview::cancelled() {
2009 }
2010
2011 void Preview::run(sc::PreviewReplyProxy const& reply) {
2012- auto const res = result();
2013-
2014- // Support three different column layouts
2015- sc::ColumnLayout layout1col(1), layout2col(2), layout3col(3);
2016-
2017- // Single column layout
2018- layout1col.add_column( { "header", "art", "statistics", "tracks", "actions", "description" });
2019-
2020- // Register the layouts we just created
2021- reply->register_layout( { layout1col }); //, layout2col, layout3col
2022-
2023- sc::PreviewWidget header("header", "header");
2024- header.add_attribute_mapping("title", "title");
2025- header.add_attribute_mapping("subtitle", "username");
2026-
2027- sc::PreviewWidget art("art", "image");
2028- art.add_attribute_mapping("source", "art");
2029-
2030- sc::PreviewWidget statistics("statistics", "header");
2031- statistics.add_attribute_value("title", sc::Variant(res["playback-count"].get_string() + " " + res["favoritings-count"].get_string()));
2032-
2033- sc::PreviewWidget tracks("tracks", "audio");
2034- {
2035- if (res["streamable"].get_bool()) {
2036- sc::VariantBuilder builder;
2037- builder.add_tuple({
2038- {"title", sc::Variant(res.title())},
2039- {"source", sc::Variant(res["stream-url"])},
2040- {"length", res["duration"]}
2041- });
2042- tracks.add_attribute_value("tracks", builder.end());
2043- }
2044- }
2045-
2046- sc::PreviewWidget actions("actions", "actions");
2047- {
2048- string purchase_url = res["purchase-url"].get_string();
2049- if (!purchase_url.empty()) {
2050- sc::VariantBuilder builder;
2051- builder.add_tuple({
2052- {"id", sc::Variant("buy")},
2053- {"label", sc::Variant(_("Buy"))},
2054- {"uri", sc::Variant(purchase_url)}
2055- });
2056- actions.add_attribute_value("actions", builder.end());
2057- }
2058- string video_url = res["video-url"].get_string();
2059- if (!video_url.empty()) {
2060- sc::VariantBuilder builder;
2061- builder.add_tuple({
2062- {"id", sc::Variant("video")},
2063- {"label", sc::Variant(_("Watch video"))},
2064- {"uri", sc::Variant(video_url)}
2065- });
2066- actions.add_attribute_value("actions", builder.end());
2067- }
2068- {
2069- sc::VariantBuilder builder;
2070- builder.add_tuple({
2071- {"id", sc::Variant("play")},
2072- {"label", sc::Variant(_("Play in browser"))}
2073- });
2074- actions.add_attribute_value("actions", builder.end());
2075- }
2076- }
2077-
2078- sc::PreviewWidget description("description", "text");
2079- description.add_attribute_mapping("text", "description");
2080-
2081- reply->push( { header, art, statistics, tracks, actions, description });
2082+ try {
2083+ auto const res = result();
2084+
2085+ sc::PreviewWidgetList widgets;
2086+ std::vector<std::string> ids;
2087+ // Support three different column layouts
2088+ sc::ColumnLayout layout1col(1), layout2col(2), layout3col(3);
2089+
2090+ string mode = res["mode"].get_string();
2091+ if (mode == _("user")) {
2092+ ids = std::vector<std::string>{ "header", "art", "statistics", "description", "actions"};
2093+ sc::PreviewWidget header("header", "header");
2094+ header.add_attribute_mapping("title", "title");
2095+ widgets.emplace_back(header);
2096+
2097+ string artwork_url= res["art"].get_string();
2098+ boost::replace_all(artwork_url, "large", "t500x500");
2099+ sc::PreviewWidget art("art", "image");
2100+ art.add_attribute_value("source", sc::Variant(artwork_url));
2101+ widgets.emplace_back(art);
2102+
2103+ sc::PreviewWidget statistics("statistics", "header");
2104+ statistics.add_attribute_value("title", sc::Variant(
2105+ res["track-count"].get_string() + " " +
2106+ res["followers-count"].get_string() + " " +
2107+ res["followings-count"].get_string()));
2108+ widgets.emplace_back(statistics);
2109+
2110+ sc::PreviewWidget description("description", "text");
2111+ description.add_attribute_value("text", sc::Variant(
2112+ res["permalink-url"].get_string() + "<br>" +
2113+ res["bio"].get_string()));
2114+ widgets.emplace_back(description);
2115+
2116+ std::string userid = res["id"].get_string();
2117+ sc::VariantBuilder builder;
2118+ sc::PreviewWidget actions("actions", "actions");
2119+ {
2120+ string permalink_url = res["permalink-url"].get_string();
2121+ builder.add_tuple({
2122+ {"id", sc::Variant("view")},
2123+ {"label", sc::Variant(_("View in browser"))},
2124+ {"uri", sc::Variant(permalink_url)}
2125+ });
2126+ sc::CannedQuery new_query(SCOPE_NAME);
2127+ new_query.set_department_id("userid:" + userid);
2128+ builder.add_tuple({
2129+ {"id", sc::Variant("usertracks")},
2130+ {"label", sc::Variant(_("Get user tracks"))},
2131+ {"uri", sc::Variant(new_query.to_uri())}
2132+ });
2133+ }
2134+ actions.add_attribute_value("actions", builder.end());
2135+ widgets.emplace_back(actions);
2136+ } else {
2137+ ids = std::vector<std::string> { "header", "art", "statistics", "trackinfo", "tracks", "description", "actions"};
2138+
2139+ sc::PreviewWidget header("header", "header");
2140+ header.add_attribute_mapping("title", "title");
2141+ header.add_attribute_mapping("subtitle", "username");
2142+ widgets.emplace_back(header);
2143+
2144+ //load big track thubmail
2145+ string artwork_url= res["art"].get_string();
2146+ boost::replace_all(artwork_url, "large", "t500x500");
2147+ sc::PreviewWidget art("art", "image");
2148+ art.add_attribute_value("source", sc::Variant(artwork_url));
2149+ widgets.emplace_back(art);
2150+
2151+ sc::PreviewWidget statistics("statistics", "header");
2152+ statistics.add_attribute_value("title", sc::Variant(
2153+ res["playback-count"].get_string() + " " +
2154+ res["likes-count"].get_string() + " " +
2155+ res["repost-count"].get_string() + " " +
2156+ res["favoritings-count"].get_string() + " " +
2157+ res["comment-count"].get_string()));
2158+ widgets.emplace_back(statistics);
2159+
2160+ std::string trackid = res["id"].get_string();
2161+ std::string userid = res["userid"].get_string();
2162+
2163+ sc::PreviewWidget tracks("tracks", "audio");
2164+ {
2165+ if (res["streamable"].get_bool()) {
2166+ sc::VariantBuilder builder;
2167+ builder.add_tuple({
2168+ {"title", sc::Variant(res.title())},
2169+ {"source", sc::Variant(res["stream-url"])},
2170+ {"length", res["duration"]}
2171+ });
2172+ tracks.add_attribute_value("tracks", builder.end());
2173+ widgets.emplace_back(tracks);
2174+ }
2175+ }
2176+
2177+ if (!client_.authenticated()) {
2178+ ids.emplace_back("tips-headerid");
2179+ sc::PreviewWidget w_tips(ids.at(ids.size() - 1), "text");
2180+ w_tips.add_attribute_value("text", sc::Variant(_("Please login to post a comment ")));
2181+ widgets.emplace_back(w_tips);
2182+
2183+ ids.emplace_back("login-actionId");
2184+ sc::PreviewWidget w_action(ids.at(ids.size() - 1), "actions");
2185+ sc::VariantBuilder builder;
2186+ builder.add_tuple({
2187+ {"id", sc::Variant(_("open"))},
2188+ {"label", sc::Variant(_("Login to soundcloud"))},
2189+ });
2190+
2191+ sc::OnlineAccountClient oa_client(SCOPE_NAME, "sharing", SCOPE_ACCOUNTS_NAME);
2192+
2193+ oa_client.register_account_login_item(w_action,
2194+ sc::OnlineAccountClient::InvalidateResults,
2195+ sc::OnlineAccountClient::DoNothing);
2196+
2197+ w_action.add_attribute_value("actions", builder.end());
2198+ widgets.emplace_back(w_action);
2199+ }
2200+
2201+ sc::PreviewWidget trackinfo("trackinfo", "table");
2202+ trackinfo.add_attribute_mapping("values", "trackinfo");
2203+ widgets.emplace_back(trackinfo);
2204+
2205+ sc::PreviewWidget description("description", "text");
2206+ description.add_attribute_mapping("text", "description");
2207+ widgets.emplace_back(description);
2208+
2209+ if (client_.authenticated()) {
2210+ ids.emplace_back("comment-inputid");
2211+ sc::PreviewWidget w_commentInput(ids.at(ids.size() - 1), "comment-input");
2212+ w_commentInput.add_attribute_value("submit-label", sc::Variant(_("Post")));
2213+ widgets.emplace_back(w_commentInput);
2214+ }
2215+
2216+ sc::VariantBuilder builder;
2217+ sc::PreviewWidget actions("actions", "actions");
2218+ {
2219+ string purchase_url = res["purchase-url"].get_string();
2220+ if (!purchase_url.empty()) {
2221+ builder.add_tuple({
2222+ {"id", sc::Variant("buy")},
2223+ {"label", sc::Variant(_("Buy"))},
2224+ {"uri", sc::Variant(purchase_url)}
2225+ });
2226+ }
2227+ string video_url = res["video-url"].get_string();
2228+ if (!video_url.empty()) {
2229+ builder.add_tuple({
2230+ {"id", sc::Variant("video")},
2231+ {"label", sc::Variant(_("Watch video"))},
2232+ {"uri", sc::Variant(video_url)}
2233+ });
2234+ }
2235+ {
2236+ builder.add_tuple({
2237+ {"id", sc::Variant("play")},
2238+ {"label", sc::Variant(_("Play in browser"))}
2239+ });
2240+ }
2241+ if (client_.authenticated()) {
2242+ sc::CannedQuery new_query(SCOPE_NAME);
2243+ new_query.set_department_id("userid:" + userid);
2244+ builder.add_tuple({
2245+ {"id", sc::Variant("usertracks")},
2246+ {"label", sc::Variant(_("Get user tracks"))},
2247+ {"uri", sc::Variant(new_query.to_uri())}
2248+ });
2249+
2250+ future<bool> like_future = client_.is_fav_track(trackid);
2251+ auto status = get_or_throw(like_future);
2252+ cout << "is fav stats: " << status << endl;
2253+ if (status == true) {
2254+ builder.add_tuple({
2255+ {"id", sc::Variant("deletelike")},
2256+ {"label", sc::Variant(_("Remove 'Like'"))}
2257+ });
2258+ } else {
2259+ builder.add_tuple({
2260+ {"id", sc::Variant("like")},
2261+ {"label", sc::Variant(_("Like"))}
2262+ });
2263+ }
2264+
2265+ future<bool> follow_future = client_.is_user_follower(userid);
2266+ status = get_or_throw(follow_future);
2267+ cout << "is users follower: " << status << endl;
2268+ if (status == true) {
2269+ builder.add_tuple({
2270+ {"id", sc::Variant("unfollow")},
2271+ {"label", sc::Variant(_("Unfollow"))}
2272+ });
2273+ } else {
2274+ builder.add_tuple({
2275+ {"id", sc::Variant("follow")},
2276+ {"label", sc::Variant(_("Follow"))}
2277+ });
2278+ }
2279+ }
2280+ }
2281+ actions.add_attribute_value("actions", builder.end());
2282+ widgets.emplace_back(actions);
2283+
2284+ future<deque<Comment>> comment_future;
2285+ comment_future = client_.track_comments(trackid);
2286+
2287+ int index = 0;
2288+ for (const auto &comment : get_or_throw(comment_future)) {
2289+ std::string id = "commentId_"+ std::to_string(index++);
2290+ ids.emplace_back(id);
2291+
2292+ sc::PreviewWidget w_comment(id, "comment");
2293+ w_comment.add_attribute_value("comment", sc::Variant(comment.body()));
2294+ w_comment.add_attribute_value("author", sc::Variant(comment.title()));
2295+ w_comment.add_attribute_value("source", sc::Variant(comment.artwork()));
2296+ w_comment.add_attribute_value("subtitle", sc::Variant(comment.created_at()));
2297+ widgets.emplace_back(w_comment);
2298+ }
2299+ }
2300+
2301+ layout1col.add_column(ids);
2302+ reply->register_layout( { layout1col }); //, layout2col, layout3col
2303+ reply->push(widgets);
2304+ }catch (domain_error &e) {
2305+ cerr << e.what() << endl;
2306+ reply->error(current_exception());
2307+ }
2308 }
2309
2310=== modified file 'src/scope/query.cpp'
2311--- src/scope/query.cpp 2014-12-04 13:39:07 +0000
2312+++ src/scope/query.cpp 2016-02-02 11:41:52 +0000
2313@@ -14,8 +14,10 @@
2314 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2315 *
2316 * Author: Pete Woods <pete.woods@canonical.com>
2317+ * Gary Wang <gary.wang@canonical.com>
2318 */
2319
2320+#include <boost/algorithm/string/predicate.hpp>
2321 #include <boost/algorithm/string/trim.hpp>
2322
2323 #include <scope/localization.h>
2324@@ -74,19 +76,53 @@
2325 {
2326 "schema-version": 1,
2327 "template": {
2328- "category-layout": "grid",
2329- "card-size": "large",
2330- "card-background": "color:///#ff4200"
2331+ "category-layout": "vertical-journal",
2332+ "card-size": "large",
2333+ "card-background": "color:///#ff5500"
2334+ },
2335+ "components": {
2336+ "title": "title"
2337+ }
2338+}
2339+)";
2340+
2341+const static string SHOW_EMPTY_TRACK_TIPS = R"(
2342+{
2343+ "schema-version": 1,
2344+ "template": {
2345+ "category-layout": "grid",
2346+ "card-size": "large",
2347+ "card-layout": "horizontal"
2348+ },
2349+ "components": {
2350+ "title": "title"
2351+ }
2352+}
2353+)";
2354+
2355+const static string USER_INFO_TEMPLATE = R"(
2356+{
2357+ "schema-version": 1,
2358+ "template": {
2359+ "category-layout": "grid",
2360+ "card-background": "color:///#FFFFFF",
2361+ "card-size": "medium",
2362+ "card-layout": "horizontal"
2363 },
2364 "components": {
2365 "title": "title",
2366- "background": "background",
2367 "art" : {
2368- "aspect-ratio": 100.0
2369+ "field": "art"
2370+ },
2371+ "subtitle": "subtitle",
2372+ "attributes": {
2373+ "field": "attributes",
2374+ "max-count": 3
2375 }
2376 }
2377 }
2378 )";
2379+
2380 // unconfuse emacs: "
2381
2382 static const vector<string> AUDIO_DEPARTMENT_IDS { "Audiobooks", "Business",
2383@@ -127,9 +163,15 @@
2384 return f.get();
2385 }
2386
2387-static sc::Department::SPtr create_departments(const sc::CannedQuery &query) {
2388+static sc::Department::SPtr create_departments(const sc::CannedQuery &query,
2389+ bool contains_fav) {
2390 sc::Department::SPtr root_department = sc::Department::create("", query,
2391 MUSIC_DEPARTMENT_NAMES.front());
2392+ if (contains_fav) {
2393+ sc::Department::SPtr dept = sc::Department::create(
2394+ "my_fav", query, _("My favorites"));
2395+ root_department->add_subdepartment(dept);
2396+ }
2397 for (size_t i = 1; i < MUSIC_DEPARTMENT_IDS.size(); ++i) {
2398 sc::Department::SPtr dept = sc::Department::create(
2399 MUSIC_DEPARTMENT_IDS[i], query, MUSIC_DEPARTMENT_NAMES[i]);
2400@@ -195,21 +237,40 @@
2401 try {
2402 const sc::CannedQuery &query(sc::SearchQueryBase::query());
2403 string query_string = alg::trim_copy(query.query_string());
2404-
2405- reply->register_departments(create_departments(query));
2406+ string department_id = query.department_id();
2407+
2408+ bool authenticated = client_.authenticated();
2409+ sc::Department::SPtr root_depts = create_departments(query, authenticated);
2410+
2411+ bool is_dummy_depts = alg::starts_with(department_id, "userid:");
2412+ if (is_dummy_depts) {
2413+ sc::Department::SPtr dummy = sc::Department::create(
2414+ department_id, query, " ");
2415+ root_depts->add_subdepartment(dummy);
2416+ }
2417+
2418+ reply->register_departments(root_depts);
2419
2420 // Avoid blocking on HTTP requests at this point
2421
2422 sc::Category::SCPtr first_cat;
2423+ sc::Category::SCPtr user_cat;
2424 future<deque<Track>> stream_future;
2425+ future<User> user_future;
2426 bool reading_stream = false;
2427- if (query_string.empty() && query.department_id().empty()) {
2428- if (client_.authenticated()) {
2429+ bool reading_user_info = false;
2430+ if (query_string.empty() && department_id.empty()) {
2431+ if (authenticated) {
2432+ user_cat = reply->register_category("user", "", "",
2433+ sc::CategoryRenderer(USER_INFO_TEMPLATE));
2434+ user_future = client_.get_authuser_info();
2435+
2436 first_cat = reply->register_category(
2437 "stream", _("Stream"), "",
2438 sc::CategoryRenderer(SEARCH_CATEGORY_TEMPLATE));
2439 stream_future = client_.stream_tracks(30);
2440 reading_stream = true;
2441+ reading_user_info = true;
2442 } else {
2443 add_login_nag(reply);
2444 }
2445@@ -220,22 +281,42 @@
2446 if (query_string.empty()) {
2447 second_cat = reply->register_category("explore", _("Explore"), "",
2448 sc::CategoryRenderer(SEARCH_CATEGORY_TEMPLATE));
2449- tracks_future = client_.search_tracks({
2450- { SP::query, query_string },
2451- { SP::limit, "15" },
2452- { SP::genre, department_to_category(query.department_id()) },
2453- { SP::order, "hotness" }
2454- });
2455+ if (department_id == "my_fav") {
2456+ tracks_future = client_.favorite_tracks();
2457+ } else if (is_dummy_depts) {
2458+ //create dummy department to pass the validation check
2459+ user_cat = reply->register_category("user", "", "",
2460+ sc::CategoryRenderer(USER_INFO_TEMPLATE));
2461+
2462+ string userId = department_id.substr(department_id.find(':') + 1);
2463+ user_future = client_.get_user_info(userId);
2464+ tracks_future = client_.get_user_tracks(userId, 15);
2465+ reading_user_info = true;
2466+ } else {
2467+ tracks_future = client_.search_tracks({
2468+ { SP::query, query_string },
2469+ { SP::limit, "15" },
2470+ { SP::genre, department_to_category(department_id) },
2471+ { SP::order, "hotness" }
2472+ });
2473+ }
2474 } else {
2475 second_cat = reply->register_category("search", "", "",
2476 sc::CategoryRenderer(SEARCH_CATEGORY_TEMPLATE));
2477+
2478 tracks_future = client_.search_tracks( {
2479- { SP::query, query_string },
2480- { SP::limit, "30" }
2481+ { SP::query, query_string },
2482+ { SP::limit, "30" }
2483 });
2484 }
2485
2486 // Now we come to wait for the results
2487+ if (reading_user_info) {
2488+ User user = get_or_throw(user_future);
2489+ if (!push_user_info(reply, user_cat, user)) {
2490+ return;
2491+ }
2492+ }
2493
2494 if (reading_stream) {
2495 for (const auto &track : get_or_throw(stream_future)) {
2496@@ -245,12 +326,19 @@
2497 }
2498 }
2499
2500- for (const auto &track : get_or_throw(tracks_future)) {
2501+ deque<Track> tracklist = get_or_throw(tracks_future);
2502+ for (const auto &track : tracklist) {
2503 if (!push_track(reply, second_cat, track)) {
2504 return;
2505 }
2506 }
2507
2508+ if (tracklist.size() == 0) {
2509+ if (!show_empty_tip(reply)) {
2510+ return;
2511+ }
2512+ }
2513+
2514 } catch (domain_error &e) {
2515 cerr << e.what() << endl;
2516 reply->error(current_exception());
2517@@ -269,7 +357,8 @@
2518 } else {
2519 res.set_art(track.user().artwork());
2520 }
2521-
2522+
2523+ res["id"] = std::to_string(track.id());
2524 res["label"] = track.label_name();
2525 res["streamable"] = track.streamable();
2526 res["stream-url"] = track.stream_url() + "?client_id=" + client_.client_id();
2527@@ -277,20 +366,89 @@
2528 res["video-url"] = track.video_url();
2529 res["waveform"] = track.waveform();
2530 res["username"] = track.user().title();
2531+ res["userid"] = std::to_string(track.user().id());
2532 res["description"] = track.description();
2533
2534 string duration = format_time(track.duration());
2535 string playback_count = u8"\u25B6 " + format_fixed(track.playback_count());
2536- string favoritings_count = u8"\u2665 " + format_fixed(track.favoritings_count());
2537+ string favoritings_count = u8"\u263B " + format_fixed(track.favoritings_count());
2538+ string likes_count = u8"\u2665 " + format_fixed(track.likes_count());
2539+ string repost_count = u8"\u2B94 " + format_fixed(track.repost_count());
2540+ string comment_count = u8"\u270E " + format_fixed(track.comment_count());
2541 res["duration"] = (int) (track.duration() / 1000);
2542 res["playback-count"] = playback_count;
2543 res["favoritings-count"] = favoritings_count;
2544+ res["likes-count"] = likes_count;
2545+ res["repost-count"] = repost_count;
2546+ res["comment-count"] = comment_count;
2547+
2548+ sc::VariantArray trackinfo {
2549+ sc::Variant(sc::VariantArray{sc::Variant(_("create time")), sc::Variant(track.created_at())}),
2550+ sc::Variant(sc::VariantArray{sc::Variant(_("genre")), sc::Variant(track.genre())}),
2551+ sc::Variant(sc::VariantArray{sc::Variant(_("license")), sc::Variant(track.license())})
2552+ };
2553+ res["trackinfo"] = sc::Variant(trackinfo);
2554
2555 sc::VariantBuilder builder;
2556 builder.add_tuple({{"value", sc::Variant(duration)}});
2557 builder.add_tuple({{"value", sc::Variant(playback_count)}});
2558- builder.add_tuple({{"value", sc::Variant(favoritings_count)}});
2559- res["attributes"] = builder.end();
2560+
2561+ //favorite api doesn't contains likes_count field when retrieving auth user favorites list
2562+ //activity api doesn't contains favoritings_count field when retrieving stream list
2563+ if (track.likes_count() > 0)
2564+ builder.add_tuple({{"value", sc::Variant(likes_count)}});
2565+ else
2566+ builder.add_tuple({{"value", sc::Variant(favoritings_count)}});
2567+
2568+ res["attributes"] = builder.end();
2569+
2570+ res["mode"] = _("track");
2571+
2572+ return reply->push(res);
2573+}
2574+
2575+bool Query::push_user_info(const sc::SearchReplyProxy &reply,
2576+ const sc::Category::SCPtr &category,
2577+ const User &user) {
2578+
2579+ sc::CategorisedResult res(category);
2580+
2581+ res.set_uri(user.permalink_url());
2582+ res.set_title(user.title());
2583+ res.set_art(user.artwork());
2584+ res["subtitle"] = user.permalink_url() + " "+ user.bio();
2585+
2586+ string track_count = "<b> "+ format_fixed(user.track_count()) + _("</b> tracks");
2587+ string followers_count = "<b> "+ format_fixed(user.followers_count()) + _("</b> followers");
2588+ string followings_count = "<b> "+ format_fixed(user.followings_count()) + _("</b> followings");
2589+ res["track-count"] = track_count;
2590+ res["followers-count"] = followers_count;
2591+ res["followings-count"] = followings_count;
2592+ res["permalink-url"] = user.permalink_url();
2593+ res["bio"] = user.bio();
2594+ res["id"] = std::to_string(user.id());
2595+
2596+ sc::VariantBuilder builder;
2597+ builder.add_tuple({{"value", sc::Variant(track_count)}});
2598+ builder.add_tuple({{"value", sc::Variant(followers_count)}});
2599+ builder.add_tuple({{"value", sc::Variant(followings_count)}});
2600+
2601+ res["attributes"] = builder.end();
2602+ res["mode"] = _("user");
2603+
2604+ return reply->push(res);
2605+}
2606+
2607+bool Query::show_empty_tip(const unity::scopes::SearchReplyProxy &reply)
2608+{
2609+ //Stay on surface and avoid user to enter card view if no tracks are found
2610+ const sc::CannedQuery &query(sc::SearchQueryBase::query());
2611+ sc::CategoryRenderer rdr(SHOW_EMPTY_TRACK_TIPS);
2612+ auto cat = reply->register_category("show_empty_tips", "", "", rdr);
2613+
2614+ sc::CategorisedResult res(cat);
2615+ res.set_uri(query.to_uri());
2616+ res.set_title(_("No tracks can be found"));
2617
2618 return reply->push(res);
2619 }
2620
2621=== modified file 'src/scope/scope.cpp'
2622--- src/scope/scope.cpp 2014-12-04 13:39:07 +0000
2623+++ src/scope/scope.cpp 2016-02-02 11:41:52 +0000
2624@@ -14,12 +14,14 @@
2625 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2626 *
2627 * Author: Pete Woods <pete.woods@canonical.com>
2628+ * Gary Wang <gary.wang@canonical.com>
2629 */
2630
2631 #include <scope/localization.h>
2632 #include <scope/preview.h>
2633 #include <scope/query.h>
2634 #include <scope/scope.h>
2635+#include <scope/activation.h>
2636
2637 namespace sc = unity::scopes;
2638 using namespace std;
2639@@ -48,7 +50,14 @@
2640
2641 sc::PreviewQueryBase::UPtr Scope::preview(sc::Result const& result,
2642 sc::ActionMetadata const& metadata) {
2643- return sc::PreviewQueryBase::UPtr(new Preview(result, metadata));
2644+ return sc::PreviewQueryBase::UPtr(new Preview(result, metadata, oa_client_));
2645+}
2646+
2647+sc::ActivationQueryBase::UPtr Scope::perform_action(const sc::Result &result,
2648+ const sc::ActionMetadata &metadata,
2649+ const std::string &widget_id,
2650+ const std::string &action_id) {
2651+ return sc::ActivationQueryBase::UPtr(new Activation(result, metadata, action_id, oa_client_));
2652 }
2653
2654 #define EXPORT __attribute__ ((visibility ("default")))

Subscribers

People subscribed via source and target branches

to all changes: