Merge lp:~abreu-alexandre/unity-webapps-qml/new-content-hub-api into lp:unity-webapps-qml

Proposed by Alexandre Abreu
Status: Merged
Approved by: Alberto Mardegan
Approved revision: 101
Merged at revision: 98
Proposed branch: lp:~abreu-alexandre/unity-webapps-qml/new-content-hub-api
Merge into: lp:unity-webapps-qml
Diff against target: 1352 lines (+828/-191)
6 files modified
examples/api-bindings/content-hub/www/js/app.js (+30/-53)
examples/api-bindings/content-peer-picker/main.qml.in (+32/-0)
examples/api-bindings/content-peer-picker/www/index.html (+33/-0)
examples/api-bindings/content-peer-picker/www/js/app.js (+104/-0)
src/Ubuntu/UnityWebApps/UnityWebAppsBackendComponents.js (+342/-66)
src/Ubuntu/UnityWebApps/bindings/content-hub/client/content-hub.js (+287/-72)
To merge this branch: bzr merge lp:~abreu-alexandre/unity-webapps-qml/new-content-hub-api
Reviewer Review Type Date Requested Status
Alberto Mardegan (community) Approve
PS Jenkins bot (community) continuous-integration Approve
WebApps Pending
Review via email: mp+211165@code.launchpad.net

Commit message

Add support for the new content hub api

Description of the change

Add support for the new content hub api.

You need https://code.launchpad.net/~michael-sheldon/content-hub/peer_picker_ui (new implementation for content hub)

- Testing the feature
---------------------

you have the examples/api-bindings/:

- content-hub: example of an import application. It lists the potential sources for images,

- content-hub-exporter: exmaple of an exporter application (exporting pictures). The click package should be installed locally (see the README file in the local folder) and it should be listed as peer by the importer (if not make sure that you killed the content-hub service and registered the new peer w/ by running the content-hub-peer-hook /usr/lib/x86_64-linux-gnu/content-hub/content-hub-peer-hook). What usually happens is that I manually start the app before testing the importers, to avoid upstart issues & crashes on the desktop w/ e.g.:

APP_ID=com.ubuntu.developer.alexandre-abreu.content-hub-html5-exporter_content-hub-html5-exporter_0.6 qmlscene -I `pwd`/../../../src main.qml

- content-hub-picker: example of an application that uses the Content Peer Picker UI,

- Automated/unit tests
----------------------

Another branch will land that adds integration tests for all API components, which is why
no tests have been added/updated,

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alberto Mardegan (mardy) wrote :

L646: + if (this._modelAdaptor) {
I guess you wanted "if (!..."

L670: Isn't it possible to avoid building the code this way, and use backendDelegate.createQmlObject() as you did for the AccountServiceModel? (I guess you had your reasons not to do it this way, please explain :-) )

L673-678 (but also later): I'd put the ";" at the end of the line, rather than at the beginning. Otherwise if the first filter is not set and the second one is, you'd end up with code like "ContentPeerModel { ; handler: ..."

L952: I guess the result of the expression must be a boolean, so maybe write it as
  this._isDefaultPeer = content && content.isDefaultPeer

I didn't test it yet, will do that soon.

review: Needs Fixing
Revision history for this message
Alberto Mardegan (mardy) wrote :

I tested it, and the examples work.

101. By Alexandre Abreu

fixes

Revision history for this message
Alexandre Abreu (abreu-alexandre) wrote :

> L646: + if (this._modelAdaptor) {
> I guess you wanted "if (!..."

right, done

> L670: Isn't it possible to avoid building the code this way, and use
> backendDelegate.createQmlObject() as you did for the AccountServiceModel? (I
> guess you had your reasons not to do it this way, please explain :-) )

sure, the issue is w/ the plugin specific types that are not js builtins.
Things work find w/ the backend.create[..] when you have builtin types but
it is not really meant to specify custom type params like ContentType.Pictures, etc.
Those have to be handled specifically, hence the lousy creation steps,

>
> L673-678 (but also later): I'd put the ";" at the end of the line, rather than
> at the beginning. Otherwise if the first filter is not set and the second one
> is, you'd end up with code like "ContentPeerModel { ; handler: ..."

right, done

> L952: I guess the result of the expression must be a boolean, so maybe write
> it as
> this._isDefaultPeer = content && content.isDefaultPeer

done

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alberto Mardegan (mardy) wrote :

LGTM!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'examples/api-bindings/content-hub/www/js/app.js'
2--- examples/api-bindings/content-hub/www/js/app.js 2014-02-11 09:18:34 +0000
3+++ examples/api-bindings/content-hub/www/js/app.js 2014-03-18 13:59:59 +0000
4@@ -6,32 +6,18 @@
5 var pictureContentType = hub.ContentType.Pictures;
6
7 var sourcePeers = {};
8- hub.knownSourcesForType(
9- pictureContentType
10+ hub.getPeers(
11+ {contentType: hub.ContentType.Pictures}
12 , function (peers) {
13+ if ( ! peers.length)
14+ return;
15+
16 for (var j = 0; j < peers.length; ++j) {
17 addPeerElement(peers[j].appId(), peers[j].name());
18 sourcePeers[peers[j].appId()] = peers[j];
19 }
20-
21- console.log('peers defaultSourceForType: ' + peers)
22-
23- hub.defaultSourceForType(
24- pictureContentType
25- , function(peer) {
26- if (peer) {
27- addPeerElement(peer.appId(), peer.name());
28- sourcePeers[peer.appId()] = peer;
29- }
30-
31- if (Object.keys(sourcePeers).length === 0) {
32- nopeers();
33- return;
34- }
35- document.getElementById('importdiv').style.display = 'block';
36- document.getElementById('lowLevelImportdiv').style.display = 'block';
37- }
38- );
39+ document.getElementById('importdiv').style.display = 'block';
40+ document.getElementById('lowLevelImportdiv').style.display = 'block';
41 });
42
43 document.getElementById('import').addEventListener('click', doSimpleApiImport);
44@@ -132,7 +118,7 @@
45
46 hub.api.importContent(pictureContentType
47 , peer
48- , {importToLocalStore: true}
49+ , {scope: hub.ContentScope.App}
50 , function(items) {
51 for (var i = 0; i < items.length; ++i) {
52 addResult(items[i]);
53@@ -159,40 +145,31 @@
54 return;
55 }
56
57- hub.importContentForPeer(
58- pictureContentType,
59- peer,
60- function(transfer) {
61-
62- hub.defaultStoreForType(pictureContentType, function(store) {
63- transfer.setStore(store, function() {
64-
65- transfer.start(function(state) {
66- if (transferState.Aborted === state) {
67- transfer.finalize();
68- peer.destroy();
69- transfer.destroy();
70- aborted();
71- return;
72- }
73-
74- if (transferState.Charged === state) {
75- transfer.items(function(items) {
76- for (var i = 0; i < items.length; ++i) {
77- addResult(items[i]);
78- }
79- transfer.finalize();
80- peer.destroy();
81- transfer.destroy();
82- });
83- }
84+ hub.getStore(hub.ContentScope.App, function(store) {
85+ peer.requestForStore(store, function(transfer) {
86+ transfer.start(function(state) {
87+ if (transferState.Aborted === state) {
88+ transfer.finalize();
89+ peer.destroy();
90+ transfer.destroy();
91+ aborted();
92+ return;
93+ }
94+
95+ if (transferState.Charged === state) {
96+ transfer.items(function(items) {
97+ for (var i = 0; i < items.length; ++i) {
98+ addResult(items[i]);
99+ }
100+ transfer.finalize();
101+ peer.destroy();
102+ transfer.destroy();
103 });
104- });
105-
106+ }
107 });
108
109- }
110- );
111+ });
112+ });
113 };
114 };
115
116
117=== added directory 'examples/api-bindings/content-peer-picker'
118=== added file 'examples/api-bindings/content-peer-picker/main.qml.in'
119--- examples/api-bindings/content-peer-picker/main.qml.in 1970-01-01 00:00:00 +0000
120+++ examples/api-bindings/content-peer-picker/main.qml.in 2014-03-18 13:59:59 +0000
121@@ -0,0 +1,32 @@
122+import QtQuick 2.0
123+import QtWebKit 3.0
124+import QtWebKit.experimental 1.0
125+import Ubuntu.Components 0.1
126+import Ubuntu.UnityWebApps 0.1
127+
128+MainView {
129+ id: root
130+ focus: true
131+ applicationName: \"helloworld\"
132+
133+ width: units.gu(100)
134+ height: units.gu(100)
135+
136+ WebView {
137+ id: webview
138+ anchors.fill: parent
139+ url: \"file://$$OUT_PWD/content-hub/www/index.html\"
140+
141+ experimental.preferences.navigatorQtObjectEnabled: true
142+ experimental.preferences.developerExtrasEnabled: true
143+
144+ function getUnityWebappsProxies() {
145+ return UnityWebAppsUtils.makeProxiesForQtWebViewBindee(webview);
146+ }
147+
148+ UnityWebApps {
149+ id: webapps
150+ bindee: webview
151+ }
152+ }
153+}
154
155=== added directory 'examples/api-bindings/content-peer-picker/www'
156=== added file 'examples/api-bindings/content-peer-picker/www/index.html'
157--- examples/api-bindings/content-peer-picker/www/index.html 1970-01-01 00:00:00 +0000
158+++ examples/api-bindings/content-peer-picker/www/index.html 2014-03-18 13:59:59 +0000
159@@ -0,0 +1,33 @@
160+<html>
161+
162+ <head>
163+ <title>Content Hub: Content Peer Picker example</title>
164+ <script src="./js/app.js"></script>
165+ </head>
166+
167+ <style>
168+
169+ .selected { background-color: red; }
170+
171+ </style>
172+
173+ <body>
174+ <div>
175+ Start Content Peer picking to import content: <input type="button" id="pick"></input>
176+ </div>
177+
178+ <div>
179+ Selected peer:
180+ <div id="selected-peer">
181+ </div>
182+ </div>
183+
184+ <div>
185+ Imported Image:
186+ <div id="results">
187+ </div>
188+ </div>
189+
190+ </body>
191+
192+</html>
193
194=== added directory 'examples/api-bindings/content-peer-picker/www/js'
195=== added file 'examples/api-bindings/content-peer-picker/www/js/app.js'
196--- examples/api-bindings/content-peer-picker/www/js/app.js 1970-01-01 00:00:00 +0000
197+++ examples/api-bindings/content-peer-picker/www/js/app.js 2014-03-18 13:59:59 +0000
198@@ -0,0 +1,104 @@
199+window.onload = function() {
200+ var api = external.getUnityObject('1.0');
201+ var hub = api.ContentHub;
202+
203+ var transferState = hub.ContentTransfer.State;
204+ var pictureContentType = hub.ContentType.Pictures;
205+
206+ document.getElementById('pick').addEventListener('click', doContentPeerPicking);
207+ function doContentPeerPicking() {
208+ hub.launchContentPeerPicker(
209+ {
210+ contentType: hub.ContentType.Pictures,
211+ handler: hub.ContentHandler.Source,
212+ },
213+ function(peer) {
214+ if ( ! peer) {
215+ nopeers();
216+ return;
217+ }
218+ addPeerElement(peer.appId(), peer.name());
219+ doSimpleApiImport(peer);
220+ },
221+ function() {
222+ aborted();
223+ });
224+ };
225+
226+ function addPeerElement(appId, name) {
227+ var selectedpeer = document.querySelector('#selected-peer');
228+ var span = document.createElement('span');
229+
230+ var text = document.createTextNode('appId: ' + appId + ', name: ' + name)
231+ span.appendChild(text);
232+ selectedpeer.appendChild(span);
233+ };
234+
235+ var results = [];
236+ function addResult(item) {
237+ results.push({name: item.name, url: item.url});
238+ renderResults(results);
239+ };
240+
241+ function displayImages(images) {
242+ var res = document.getElementById('results');
243+ for (var i = 0; i < images.length; ++i) {
244+ var img = document.createElement('img');
245+
246+ img.setAttribute('src', images[i].url);
247+ img.setAttribute('height', '100px');
248+ img.setAttribute('width', '100px');
249+
250+ if (images[i].name && images[i].name.length !== 0)
251+ img.setAttribute('alt', images[i].name);
252+
253+ res.appendChild(img);
254+ }
255+ };
256+
257+ function aborted() {
258+ setResults('Transfer aborted');
259+ };
260+
261+ function nopeers() {
262+ setResults('No peers found');
263+ };
264+
265+ function setResults(results) {
266+ var resultEl = document.getElementById('results');
267+ resultEl.innerHTML = results;
268+ };
269+
270+ function formatResults(results) {
271+ var content = '<ul>';
272+ for (var i = 0; i < results.length; ++i) {
273+ content += '<li>'
274+ + results[i].name
275+ + ', '
276+ + results[i].url
277+ + '</li>';
278+ }
279+ content += '</ul>';
280+ return content;
281+ };
282+
283+ function renderResults(results) {
284+ setResults(formatResults(results));
285+ displayImages(results);
286+ };
287+
288+ function doSimpleApiImport(peer) {
289+ hub.api.importContent(pictureContentType
290+ , peer
291+ , {scope: hub.ContentScope.App}
292+ , function(items) {
293+ for (var i = 0; i < items.length; ++i) {
294+ addResult(items[i]);
295+ }
296+ }
297+ , function() {
298+ aborted();
299+ });
300+ };
301+};
302+
303
304=== modified file 'src/Ubuntu/UnityWebApps/UnityWebAppsBackendComponents.js'
305--- src/Ubuntu/UnityWebApps/UnityWebAppsBackendComponents.js 2014-02-26 13:21:37 +0000
306+++ src/Ubuntu/UnityWebApps/UnityWebAppsBackendComponents.js 2014-03-18 13:59:59 +0000
307@@ -201,6 +201,14 @@
308 return {object: this._objects[id], id: id};
309 },
310
311+ parent: function() {
312+ return this._parent;
313+ },
314+
315+ parentView: function() {
316+ return this._parent ? this._parent.bindee : null;
317+ },
318+
319 isObjectProxyInfo: function(info) {
320 return 'type' in info &&
321 info.type === 'object-proxy' &&
322@@ -1308,14 +1316,32 @@
323 // TODO find a better way
324 function _nameToContentType(name) {
325 var contentTypePerName = {
326+ "All": ContentHubBridge.ContentType.All,
327+ "Unknown": ContentHubBridge.ContentType.Unknown,
328 "Pictures": ContentHubBridge.ContentType.Pictures,
329- "Documents": ContentHubBridge.ContentType.Pictures,
330- "Music": ContentHubBridge.ContentType.Pictures,
331+ "Documents": ContentHubBridge.ContentType.Documents,
332+ "Music": ContentHubBridge.ContentType.Music,
333+ "Contacts": ContentHubBridge.ContentType.Contacts,
334 };
335 return name in contentTypePerName ?
336 contentTypePerName[name]
337 : ContentHubBridge.ContentType.Unknown;
338 };
339+ function _contentTypeToName(state) {
340+ if (state === ContentHubBridge.ContentType.All)
341+ return "All";
342+ else if (state === ContentHubBridge.ContentType.Unknown)
343+ return "Unknown";
344+ else if (state === ContentHubBridge.ContentType.Pictures)
345+ return "Pictures";
346+ else if (state === ContentHubBridge.ContentType.Documents)
347+ return "Documents";
348+ else if (state === ContentHubBridge.ContentType.Music)
349+ return "Music";
350+ else if (state === ContentHubBridge.ContentType.Contacts)
351+ return "Contacts";
352+ return "Unknown";
353+ };
354
355 function _nameToContentTransferSelection(name) {
356 var contentTypePerName = {
357@@ -1334,10 +1360,31 @@
358 return "Single";
359 };
360
361+ function _nameToContentHandler(name) {
362+ var contentHandlerPerName = {
363+ "Source": ContentHubBridge.ContentHandler.Source,
364+ "Destination": ContentHubBridge.ContentHandler.Destination,
365+ "Share": ContentHubBridge.ContentHandler.Share,
366+ };
367+ return name in contentHandlerPerName ?
368+ contentHandlerPerName[name]
369+ : ContentHubBridge.ContentHandler.Source;
370+ };
371+ function _contentHandlerToName(state) {
372+ if (state === ContentHubBridge.ContentHandler.Source)
373+ return "Source";
374+ else if (state === ContentHubBridge.ContentHandler.Destination)
375+ return "Destination";
376+ else if (state === ContentHubBridge.ContentHandler.Share)
377+ return "Share";
378+ return "Source";
379+ };
380+
381 function _nameToContentTransferDirection(name) {
382 var contentTypePerName = {
383 "Import": ContentHubBridge.ContentTransfer.Import,
384 "Export": ContentHubBridge.ContentTransfer.Export,
385+ "Share": ContentHubBridge.ContentTransfer.Share,
386 };
387 return name in contentTypePerName ?
388 contentTypePerName[name]
389@@ -1348,9 +1395,31 @@
390 return "Import";
391 else if (state === ContentHubBridge.ContentTransfer.Export)
392 return "Export";
393+ else if (state === ContentHubBridge.ContentTransfer.Share)
394+ return "Share";
395 return "Import";
396 };
397
398+ function _nameToContentScope(name) {
399+ var contentScopePerName = {
400+ "System": ContentHubBridge.ContentScope.System,
401+ "User": ContentHubBridge.ContentScope.User,
402+ "App": ContentHubBridge.ContentScope.App,
403+ };
404+ return name in contentScopePerName ?
405+ contentScopePerName[name]
406+ : ContentHubBridge.ContentScope.App;
407+ };
408+ function _contentScopeToName(state) {
409+ if (state === ContentHubBridge.ContentScope.System)
410+ return "System";
411+ else if (state === ContentHubBridge.ContentScope.User)
412+ return "User";
413+ else if (state === ContentHubBridge.ContentScope.App)
414+ return "App";
415+ return "App";
416+ };
417+
418 function _nameToContentTransferState(name) {
419 var contentTransferStatePerName = {
420 "Created": ContentHubBridge.ContentTransfer.Created,
421@@ -1426,6 +1495,8 @@
422 content: {
423 store: self._object.store,
424 state: self._object.state,
425+ selectionType: self._object.selectionType,
426+ direction: self._object.direction,
427 }
428 }
429 },
430@@ -1461,6 +1532,15 @@
431 if (callback && typeof(callback) === 'function')
432 callback();
433 },
434+ onStateChanged: function(callback) {
435+ if (!callback || typeof(callback) !== 'function')
436+ return;
437+ this._validate();
438+ var self = this;
439+ this._object.onStateChanged.connect(function() {
440+ callback(_contentTransferStateToName(self._object.state));
441+ });
442+ },
443
444 selectionType: function(callback) {
445 this._validate();
446@@ -1504,16 +1584,11 @@
447 item.object.name = items[i].name;
448 item.object.url = items[i].url;
449
450- console.debug('setItems: adding item ' + item.object.name.toString()
451- + ', ' + item.object.url.toString());
452-
453 contentItems.push(item.object);
454 }
455
456 this._object.items = contentItems;
457
458- console.debug('setItems: grand total of ' + this._object.items.length + ' added');
459-
460 if (callback && typeof(callback) === 'function')
461 callback();
462 },
463@@ -1593,12 +1668,24 @@
464
465 content: {
466 uri: self._object.uri,
467+ scope: _contentScopeToName(self._object.scope),
468 }
469 }
470 },
471
472 // properties
473
474+ scope: function(callback) {
475+ this._validate();
476+ callback(_contentScopeToName(this._object.scope));
477+ },
478+ setScope: function(scope, callback) {
479+ this._validate();
480+ this._object.scope = _nameToContentScope(scope);
481+ if (callback && typeof(callback) === 'function')
482+ callback();
483+ },
484+
485 //immutable
486 uri: function(callback) {
487 this._validate();
488@@ -1648,23 +1735,165 @@
489 content: {
490 appId: self._object.appId,
491 name: self._object.name,
492+ handler: self._object.handler,
493+ contentType: self._object.contentType,
494+ selectionType: self._object.selectionType,
495+ isDefaultPeer: self._object.isDefaultPeer,
496 },
497 }
498 },
499
500 // properties
501
502- // immutable
503 appId: function(callback) {
504 this._validate();
505 callback(this._object.appId);
506 },
507+ setAppId: function(appId, callback) {
508+ this._validate();
509+ this._object.appId = appId;
510+ if (callback && typeof(callback) === 'function')
511+ callback();
512+ },
513+
514+ handler: function(callback) {
515+ this._validate();
516+ callback(_contentHandlerToName(this._object.handler));
517+ },
518+ setHandler: function(handler, callback) {
519+ this._validate();
520+ this._object.handler = _nameToContentHandler(handler);
521+ if (callback && typeof(callback) === 'function')
522+ callback();
523+ },
524+
525+ contentType: function(callback) {
526+ this._validate();
527+ callback(_contentTypeToName(this._object.contentType));
528+ },
529+ setContentType: function(contentType, callback) {
530+ this._validate();
531+ this._object.contentType = _nameToContentType(contentType);
532+ if (callback && typeof(callback) === 'function')
533+ callback();
534+ },
535+
536+ selectionType: function(callback) {
537+ this._validate();
538+ callback(_contentTransferSelectionToName(this._object.selectionType));
539+ },
540+ setSelectionType: function(selectionType, callback) {
541+ this._validate();
542+ this._object.selectionType = _nameToContentTransferSelection(selectionType);
543+ if (callback && typeof(callback) === 'function')
544+ callback();
545+ },
546
547 // immutable
548 name: function(callback) {
549 this._validate();
550 callback(this._object.name);
551 },
552+
553+ isDefaultPeer: function(callback) {
554+ this._validate();
555+ callback(this._object.isDefaultPeer);
556+ },
557+
558+ // methods
559+
560+ request: function(callback) {
561+ this._validate();
562+ var transfer = new ContentTransfer(this._object.request());
563+
564+ if (callback && typeof(callback) === 'function')
565+ callback(transfer.serialize());
566+ },
567+
568+ requestForStore: function(store, callback) {
569+ if ( ! store) {
570+ callback(null);
571+ return;
572+ }
573+
574+ if (! backendDelegate.isObjectProxyInfo(store)) {
575+ console.debug('requestForStore: invalid store object proxy')
576+ callback("Invalid store");
577+ return;
578+ }
579+
580+ var _store = backendDelegate.objectFromId(store.objectid);
581+ if ( ! _store) {
582+ callback("Invalid store object (NULL)");
583+ return;
584+ }
585+ this._validate();
586+
587+ var transfer = new ContentTransfer(this._object.request(_store));
588+ if (callback && typeof(callback) === 'function')
589+ callback(transfer.serialize());
590+ },
591+
592+ // internal
593+
594+ internal: {
595+ request: function(self) {
596+ return self._object.request();
597+ }
598+ }
599+ };
600+
601+ function ContentPeerModel(filterParams) {
602+ var result = backendDelegate.createQmlObject(
603+ PLUGIN_URI, VERSION, 'ContentPeerModel', filterParams);
604+ this._id = result.id;
605+ this._object = result.object;
606+
607+ this._modelAdaptor = backendDelegate.createModelAdaptorFor(this._object);
608+ this._roles = this._modelAdaptor.roles();
609+ };
610+ ContentPeerModel.prototype = {
611+ _validate: function() {
612+ if (! this._object)
613+ throw new TypeError("Invalid object null");
614+ },
615+
616+ destroy: function() {
617+ if (! this._object)
618+ return;
619+ this._object.destroy();
620+ this._modelAdaptor.destroy();
621+ backendDelegate.deleteId(this._id);
622+ },
623+
624+ // properties
625+ setContentType: function(contentType, callback) {
626+ this._validate();
627+ this._object.contentType = contentType;
628+ if (callback)
629+ callback();
630+ },
631+
632+ setHandler: function(handler, callback) {
633+ this._validate();
634+ this._object.handler = handler;
635+ if (callback)
636+ callback();
637+ },
638+
639+ peers: function() {
640+ this._validate();
641+ return this._object.peers;
642+ },
643+
644+ // QAbtractListModel prototype
645+ count: function(callback) {
646+ if (!this._modelAdaptor) {
647+ callback(-1);
648+ return;
649+ }
650+ callback(this._modelAdaptor.rowCount());
651+ },
652 };
653
654 function _constructorFromName(className) {
655@@ -1679,33 +1908,95 @@
656 }
657
658 return {
659- defaultSourceForType: function(type, callback) {
660- var _type = _nameToContentType(type);
661- var peer = _contenthub.defaultSourceForType(_type)
662- var source = new ContentPeer(peer);
663- callback(source.serialize());
664+ getPeers: function(filters, callback) {
665+ if ( ! filters){
666+ callback(null);
667+ return;
668+ }
669+
670+ var statement = "import QtQuick 2.0; import Ubuntu.Content 0.1; ContentPeerModel {";
671+ var filterParams = {};
672+ if (filters.contentType) {
673+ statement += " contentType: ContentType." + filters.contentType + ";";
674+ }
675+ if (filters.handler) {
676+ statement += " handler: ContentHandler." + filters.handler + ";";
677+ }
678+ statement += " }";
679+
680+ var peerModel = Qt.createQmlObject(statement, backendDelegate.parent());
681+ var onPeersFound = function() {
682+ var peers = peerModel.peers;
683+
684+ var wrappedPeers = [];
685+ for (var i = 0; i < peers.length; ++i) {
686+ var wrappedPeer = new ContentPeer(peers[i]);
687+ wrappedPeers.push(wrappedPeer.serialize());
688+ }
689+ peerModel.onFindPeersCompleted.disconnect(onPeersFound);
690+ callback(wrappedPeers);
691+ };
692+ peerModel.onFindPeersCompleted.connect(onPeersFound);
693 },
694
695- defaultStoreForType: function(type, callback) {
696- var store = new ContentStore(_contenthub.defaultStoreForType(_nameToContentType(type)));
697+ getStore: function(scope, callback) {
698+ if ( ! scope){
699+ callback(null);
700+ return;
701+ }
702+ var store = new ContentStore();
703+ store.setScope(scope);
704 callback(store.serialize());
705 },
706
707- importContent: function(type, callback) {
708- var transfer = new ContentTransfer(_contenthub.importContent(_nameToContentType(type)));
709- callback(transfer.serialize());
710- },
711-
712- knownSourcesForType: function(type, callback) {
713- var peers = _contenthub.knownSourcesForType(_nameToContentType(type));
714- var wrappedPeers = [];
715-
716- for (var i = 0; i < peers.length; ++i) {
717- var wrappedPeer = new ContentPeer(peers[i]);
718- wrappedPeers.push(wrappedPeer.serialize());
719- }
720-
721- callback(wrappedPeers);
722+ launchContentPeerPicker: function(filters, onPeerSelected, onCancelPressed) {
723+ if ( ! filters){
724+ callback(null);
725+ return;
726+ }
727+
728+ var parentItem = backendDelegate.parentView();
729+ if ( ! parentItem || ! parentItem.visible || ! parentItem.height || ! parentItem.width) {
730+ console.debug("Cannot launch the content peer picker UI, invalid parent item: " + parentItem);
731+ onCancelPressed();
732+ return;
733+ }
734+
735+ var statement = "import QtQuick 2.0; import Ubuntu.Content 0.1; ContentPeerPicker {";
736+ var filterParams = {};
737+ if (filters.contentType) {
738+ statement += " contentType: ContentType." + filters.contentType + "";
739+ }
740+ if (filters.handler) {
741+ statement += "; handler: ContentHandler." + filters.handler + "";
742+ }
743+ if (filters.showTitle) {
744+ statement += "; showTitle: " + filters.showTitle === false ? "false" : "true";
745+ }
746+ statement += "; visible: true; }";
747+
748+ if (parentItem.parent)
749+ parentItem.visible = false;
750+ var contentPeerPicker = Qt.createQmlObject(statement,
751+ parentItem.parent ? parentItem.parent : parentItem);
752+ function _onPeerSelected() {
753+ var peer = new ContentPeer(contentPeerPicker.peer);
754+ contentPeerPicker.visible = false;
755+ parentItem.visible = true;
756+ onPeerSelected(peer.serialize());
757+ contentPeerPicker.onPeerSelected.disconnect(_onPeerSelected);
758+ contentPeerPicker.destroy();
759+ }
760+ function _onCancelPressed() {
761+ contentPeerPicker.visible = false;
762+ parentItem.visible = true;
763+ onCancelPressed();
764+ contentPeerPicker.onPeerSelected.disconnect(_onCancelPressed);
765+ contentPeerPicker.destroy();
766+ }
767+
768+ contentPeerPicker.onPeerSelected.connect(_onPeerSelected);
769+ contentPeerPicker.onCancelPressed.connect(_onCancelPressed);
770 },
771
772 apiImportContent: function(type, peer, transferOptions, onSuccess, onFailure) {
773@@ -1714,59 +2005,44 @@
774 onError("Invalid peer");
775 return;
776 }
777+
778 var _type = _nameToContentType(type);
779 var _peer = backendDelegate.objectFromId(peer.objectid);
780 if ( ! _peer) {
781 onError("Invalid peer object (NULL)");
782 return;
783 }
784+ var _transfer = null;
785+ if (transferOptions.scope) {
786+ var store = new ContentStore();
787+ store.setScope(transferOptions.scope);
788+ _transfer = _peer.request(store._object);
789+ }
790+ else {
791+ _transfer = _peer.request();
792+ }
793
794- var transfer = _contenthub.importContent(_type, _peer);
795- console.log('*** Transfer object: ' + transfer + '')
796 if (transferOptions.multipleFiles) {
797- transfer.selectionType = ContentHubBridge.ContentTransfer.Multiple;
798+ _transfer.selectionType = ContentHubBridge.ContentTransfer.Multiple;
799 }
800 else {
801- transfer.selectionType = ContentHubBridge.ContentTransfer.Single;
802- }
803-
804- if (transferOptions.importToLocalStore) {
805- var store = _contenthub.defaultStoreForType(_type);
806- transfer.setStore(store);
807- }
808-
809- var _transfer = new ContentTransfer(transfer)
810- transfer.stateChanged.connect(function() {
811- console.log('** Transfer state change: ' + transfer + ', state: ' + _contentTransferStateToName(transfer.state));
812- if (transfer.state === ContentHubBridge.ContentTransfer.Aborted) {
813+ _transfer.selectionType = ContentHubBridge.ContentTransfer.Single;
814+ }
815+
816+ var transfer = new ContentTransfer(_transfer)
817+ _transfer.stateChanged.connect(function() {
818+ if (_transfer.state === ContentHubBridge.ContentTransfer.Aborted) {
819 onFailure("Aborted");
820 return;
821 }
822- else if (transfer.state === ContentHubBridge.ContentTransfer.Charged) {
823- console.log('*** Transfer complete: got: ' + transfer.items.length + ' items (' + transfer + ')')
824- for (var i = 0; i < transfer.items.length; ++i) {
825- console.log('** item ' + i + ' : ' + transfer.items[i].url);
826- }
827-
828- var d = _transfer.internal.serializeItems(transfer);
829+ else if (_transfer.state === ContentHubBridge.ContentTransfer.Charged) {
830+ var d = transfer.internal.serializeItems(_transfer);
831 onSuccess(d);
832- transfer.finalize();
833+ _transfer.finalize();
834 return;
835 }
836 });
837- transfer.start();
838- },
839-
840- importContentForPeer: function(type, peerProxy, callback) {
841- if (! backendDelegate.isObjectProxyInfo(peerProxy)) {
842- console.debug('importContentForPeer: invalid peer object proxy')
843- callback();
844- return;
845- }
846- var peer = backendDelegate.objectFromId(peerProxy.objectid);
847- var transfer = new ContentTransfer(_contenthub.importContent(_nameToContentType(type), peer));
848-
849- callback(transfer.serialize());
850+ _transfer.start();
851 },
852
853 onExportRequested: function(callback) {
854
855=== modified file 'src/Ubuntu/UnityWebApps/bindings/content-hub/client/content-hub.js'
856--- src/Ubuntu/UnityWebApps/bindings/content-hub/client/content-hub.js 2014-03-04 20:40:16 +0000
857+++ src/Ubuntu/UnityWebApps/bindings/content-hub/client/content-hub.js 2014-03-18 13:59:59 +0000
858@@ -42,6 +42,10 @@
859 ? content.store : null;
860 this._state = content && content.state
861 ? content.state : null;
862+ this._selectionType = content && content.selectionType
863+ ? content.selectionType : null;
864+ this._direction = content && content.direction
865+ ? content.direction : null;
866 };
867 ContentTransfer.prototype = {
868 // object methods
869@@ -77,7 +81,7 @@
870 *
871 * @method setStore
872 * @param store {ContentStore}
873- * @param callback (optional) {Function()}
874+ * @param callback (optional) {Function()} called when the store has been updated
875 */
876 setStore: function(store, callback) {
877 this._proxy.call('setStore', [store.serialize(), callback]);
878@@ -108,6 +112,15 @@
879 setState: function(state, callback) {
880 this._proxy.call('setState', [state, callback]);
881 },
882+ /**
883+ * Notifies the listener when the state of the transfer changes.
884+ *
885+ * @method onStateChanged
886+ * @param callback {Function(ContentTransfer.State)}
887+ */
888+ onStateChanged: function(callback) {
889+ this._proxy.call('onStateChanged', [callback]);
890+ },
891
892 /**
893 * Retrieves the current selection type.
894@@ -116,7 +129,11 @@
895 * @param callback {Function(ContentTransfer.SelectionType)}
896 */
897 selectionType: function(callback) {
898- this._proxy.call('selectionType', [], callback);
899+ if (callback && typeof(callback) === 'function') {
900+ this._proxy.call('selectionType', [], callback);
901+ return;
902+ }
903+ return this._selectionType;
904 },
905 /**
906 * Sets the selection type (single or multiple).
907@@ -126,17 +143,24 @@
908 * @param callback {Function()} called when the state has been updated
909 */
910 setSelectionType: function(selectionType, callback) {
911+ this._selectionType = selectionType;
912 this._proxy.call('setSelectionType', [selectionType, callback]);
913 },
914
915 /**
916 * Retrieves the current transfer direction.
917 *
918+ * If the callback parameter is not set, the current "local" value is retrieved.
919+ *
920 * @method direction
921- * @param callback {Function(ContentTransfer.Direction)}
922+ * @param callback (optional) {Function(ContentTransfer.Direction)}
923 */
924 direction: function(callback) {
925- this._proxy.call('direction', [], callback);
926+ if (callback && typeof(callback) === 'function') {
927+ this._proxy.call('direction', [], callback);
928+ return;
929+ }
930+ return this._direction;
931 },
932 /**
933 * Sets the transfer direction (import or export).
934@@ -146,6 +170,7 @@
935 * @param callback {Function()} called when the state has been updated
936 */
937 setDirection: function(direction, callback) {
938+ this._direction = direction;
939 this._proxy.call('setDirection', [direction, callback]);
940 },
941
942@@ -231,6 +256,13 @@
943 ? content.appId : null;
944 this._name = content && content.name
945 ? content.name : null;
946+ this._handler = content && content.handler
947+ ? content.handler : null;
948+ this._contentType = content && content.contentType
949+ ? content.contentType : null;
950+ this._selectionType = content && content.selectionType
951+ ? content.selectionType : null;
952+ this._isDefaultPeer = content && content.isDefaultPeer;
953 };
954 ContentPeer.prototype = {
955 // object methods
956@@ -246,14 +278,13 @@
957
958 // properties
959
960- // immutable
961-
962 /**
963 * Retrieves the app Id of the associated peer.
964 *
965 * If the callback parameter is not set, the current "local" value is retrieved.
966 *
967 * @method appId
968+ * @return {String} Application Id for this peer
969 * @param callback (optional) {Function(String)}
970 */
971 appId: function(callback) {
972@@ -263,8 +294,97 @@
973 }
974 return this._appId;
975 },
976-
977- // immutable
978+ /**
979+ * Sets the app Id of the associated peer.
980+ *
981+ * @method setAppId
982+ * @param appId {String}
983+ * @param callback {Function()} called when the appId has been updated
984+ */
985+ setAppId: function(appId, callback) {
986+ this._proxy.call('setAppId', [appId, callback]);
987+ },
988+
989+ /**
990+ * Retrieves the specific ContentHandler for this peer.
991+ *
992+ * If the callback parameter is not set, the current "local" value is retrieved.
993+ *
994+ * @method handler
995+ * @return {String} ContentHandler for this peer
996+ * @param callback (optional) {Function(String)}
997+ */
998+ handler: function(callback) {
999+ if (callback && typeof(callback) === 'function') {
1000+ this._proxy.call('handler', [], callback);
1001+ return;
1002+ }
1003+ return this._handler;
1004+ },
1005+ /**
1006+ * Sets specific ContentHandler for this peer.
1007+ *
1008+ * @method setHandler
1009+ * @param handler {ContentHandler}
1010+ * @param callback {Function()} called when the appId has been updated
1011+ */
1012+ setHandler: function(handler, callback) {
1013+ this._proxy.call('setHandler', [handler, callback]);
1014+ },
1015+
1016+ /**
1017+ * Retrieves the specific ContentType for this peer.
1018+ *
1019+ * If the callback parameter is not set, the current "local" value is retrieved.
1020+ *
1021+ * @method contentType
1022+ * @return {String} ContentType for this peer
1023+ * @param callback (optional) {Function(String)}
1024+ */
1025+ contentType: function(callback) {
1026+ if (callback && typeof(callback) === 'function') {
1027+ this._proxy.call('contentType', [], callback);
1028+ return;
1029+ }
1030+ return this._contentType;
1031+ },
1032+ /**
1033+ * Sets specific ContentType for this peer.
1034+ *
1035+ * @method setContentType
1036+ * @param contentType {ContentType}
1037+ * @param callback {Function()} called when the content type has been updated
1038+ */
1039+ setContentType: function(contentType, callback) {
1040+ this._proxy.call('setContentType', [contentType, callback]);
1041+ },
1042+
1043+ /**
1044+ * Retrieves the specific SelectionType for this peer.
1045+ *
1046+ * If the callback parameter is not set, the current "local" value is retrieved.
1047+ *
1048+ * @method selectionType
1049+ * @return {String} ContentTransfer.SelectionType for this peer
1050+ * @param callback (optional) {Function(String)}
1051+ */
1052+ selectionType: function(callback) {
1053+ if (callback && typeof(callback) === 'function') {
1054+ this._proxy.call('selectionType', [], callback);
1055+ return;
1056+ }
1057+ return this._selectionType;
1058+ },
1059+ /**
1060+ * Sets specific SelectionType for this peer.
1061+ *
1062+ * @method setSelectionType
1063+ * @param selectionType {ContentTransfer.SelectionType}
1064+ * @param callback {Function()} called when the content type has been updated
1065+ */
1066+ setSelectionType: function(selectionType, callback) {
1067+ this._proxy.call('setSelectionType', [selectionType, callback]);
1068+ },
1069
1070 /**
1071 * Retrieves the name of the associated peer.
1072@@ -282,6 +402,45 @@
1073 return this._name;
1074 },
1075
1076+ /**
1077+ * Returns true if the peer is a default one, false otherwise.
1078+ *
1079+ * If the callback parameter is not set, the current "local" value is retrieved.
1080+ *
1081+ * @method isDefaultPeer
1082+ * @param callback (optional) {Function(Bool)}
1083+ */
1084+ isDefaultPeer: function(callback) {
1085+ if (callback && typeof(callback) === 'function') {
1086+ this._proxy.call('isDefaultPeer', [], callback);
1087+ return;
1088+ }
1089+ return this._isDefaultPeer;
1090+ },
1091+
1092+ // methods
1093+
1094+ /**
1095+ * Request to import data from this ContentPeer.
1096+ *
1097+ * @method request
1098+ * @param callback {Function(ContentTransfer)} Called with the resulting content transfer
1099+ */
1100+ request: function(callback) {
1101+ this._proxy.call('request', [], callback);
1102+ },
1103+
1104+ /**
1105+ * Request to import data from this ContentPeer and use a ContentStore for permanent storage.
1106+ *
1107+ * @method requestForStore
1108+ * @param store {ContentStore} Store used as a permanent storage
1109+ * @param callback {Function(ContentTransfer)} Called with the resulting content transfer
1110+ */
1111+ requestForStore: function(store, callback) {
1112+ this._proxy.call('requestForStore', [store.serialize()], callback);
1113+ },
1114+
1115 // extras
1116
1117 /**
1118@@ -321,6 +480,8 @@
1119
1120 this._uri = content && content.uri
1121 ? content.uri : null;
1122+ this._scope = content && content.scope
1123+ ? content.scope : null;
1124 };
1125 ContentStore.prototype = {
1126 // object methods
1127@@ -343,6 +504,7 @@
1128 * If the callback parameter is not set, the current "local" value is retrieved.
1129 *
1130 * @method uri
1131+ * @return {String} current uri
1132 * @param callback (optional) {Function(String)}
1133 */
1134 uri: function(callback) {
1135@@ -353,6 +515,33 @@
1136 return this._uri;
1137 },
1138
1139+ /**
1140+ * Retrieves the current scope.
1141+ *
1142+ * If the callback parameter is not set, the current "local" value is retrieved.
1143+ *
1144+ * @method scope
1145+ * @return {ContentScope} current scope
1146+ * @param callback (optional) {Function(ContentScope)}
1147+ */
1148+ scope: function(callback) {
1149+ if (callback && typeof(callback) === 'function') {
1150+ this._proxy.call('scope', [], callback);
1151+ return;
1152+ }
1153+ return this._scope;
1154+ },
1155+ /**
1156+ * Sets the current scope.
1157+ *
1158+ * @method setScope
1159+ * @param scope {ContentScope}
1160+ * @param callback {Function()} called when the scope has been updated
1161+ */
1162+ setScope: function(scope, callback) {
1163+ this._proxy.call('setScope', [scope, callback]);
1164+ },
1165+
1166 // extras
1167
1168 /**
1169@@ -394,7 +583,9 @@
1170 Documents
1171
1172 Music
1173-
1174+
1175+ Contacts
1176+
1177 @static
1178 @property ContentType {String}
1179
1180@@ -406,9 +597,52 @@
1181 var pictureContentType = hub.ContentType.Pictures;
1182 */
1183 ContentType: {
1184+ All: "All",
1185+ Unknown: "Unknown",
1186 Pictures: "Pictures",
1187 Documents: "Documents",
1188- Music: "Music"
1189+ Music: "Music",
1190+ Contacts: "Contacts",
1191+ },
1192+
1193+ /**
1194+ ContentHandler is an enumeration of well known content handlers.
1195+
1196+ Values:
1197+
1198+ Source
1199+
1200+ Destination
1201+
1202+ Share
1203+
1204+ @static
1205+ @property ContentHandler {String}
1206+ */
1207+ ContentHandler: {
1208+ Source: "Source",
1209+ Destination: "Destination",
1210+ Share: "Share",
1211+ },
1212+
1213+ /**
1214+ ContentScope is an enumeration of well known scope types.
1215+
1216+ Values:
1217+
1218+ System
1219+
1220+ User
1221+
1222+ App
1223+
1224+ @static
1225+ @property ContentScope {String}
1226+ */
1227+ ContentScope: {
1228+ System: "System",
1229+ User: "User",
1230+ App: "App",
1231 },
1232
1233 ContentTransfer: {
1234@@ -527,67 +761,46 @@
1235 /**
1236 * Creates a ContentPeer object for the given source type.
1237 *
1238- * @method defaultSourceForType
1239- * @param type {ContentType} Content type.
1240- * @param callback {Function (ContentPeer)} Function called with the created ContentPeer.
1241- */
1242- defaultSourceForType: function(type, callback) {
1243- backendBridge.call('ContentHub.defaultSourceForType',
1244- [type],
1245- callback);
1246- },
1247-
1248- /**
1249- * Creates a ContentStore object for the given content type.
1250- *
1251- * @method defaultStoreForType
1252- * @param type {ContentType} Content type.
1253- * @param callback {Function (ContentStore)} Function called with the created ContentStore.
1254- */
1255- defaultStoreForType: function(type, callback) {
1256- backendBridge.call('ContentHub.defaultStoreForType',
1257- [type],
1258- callback);
1259- },
1260-
1261- /**
1262- * Returns all possible peers for the given ContentType.
1263- *
1264- * @method knownSourcesForType
1265- * @param type {ContentType} Content type.
1266- * @param callback {Function (Array of ContentPeer)} Function called with the possible ContentPeers.
1267- */
1268- knownSourcesForType: function(type, callback) {
1269- backendBridge.call('ContentHub.knownSourcesForType',
1270- [type],
1271- callback);
1272- },
1273-
1274- /**
1275- * Creates a ContentTransfer object for the given content type.
1276- *
1277- * @method importContent
1278- * @param type {ContentType} Content type.
1279- * @param callback {Function(ContentTransfer)} Function called with the created ContentTransfer.
1280- */
1281- importContent: function(type, callback) {
1282- backendBridge.call('ContentHub.importContent',
1283- [type],
1284- callback);
1285- },
1286-
1287- /**
1288- * Creates a ContentTransfer object for the given ContentPeer.
1289- *
1290- * @method importContentForPeer
1291- * @param type {ContentType} Content type.
1292- * @param peer {ContentPeer} Content peer.
1293- * @param callback {Function(ContentTransfer)} Function called with the created ContentTransfer.
1294- */
1295- importContentForPeer: function(type, peer, callback) {
1296- backendBridge.call('ContentHub.importContentForPeer',
1297- [type, peer.serialize()],
1298- callback);
1299+ * @method getPeers
1300+ * @param filters {Object} A dictionary of parameters to filter the result. The filtering keys are:
1301+ * - contentType: desired ContentType
1302+ * - handler: desired ContentHandler
1303+ *
1304+ * @param callback {Function(List of ContentPeer objects)} Callback that receives the result or null
1305+ */
1306+ getPeers: function(filter, callback) {
1307+ backendBridge.call('ContentHub.getPeers',
1308+ [filter],
1309+ callback);
1310+ },
1311+
1312+ /**
1313+ * Creates a ContentStore object for the given scope type.
1314+ *
1315+ * @method getStore
1316+ * @param scope {ContentScope} The content scope for the store
1317+ * @param callback {Function(ContentStore)} Callback that receives the result or null
1318+ */
1319+ getStore: function(scope, callback) {
1320+ backendBridge.call('ContentHub.getStore',
1321+ [scope],
1322+ callback);
1323+ },
1324+
1325+ /**
1326+ * Launches the content peer picker ui that allows the user to select a peer.
1327+ *
1328+ * @method launchContentPeerPicker
1329+ * @param filters {Object} A dictionary of parameters to filter the result. The filtering keys are:
1330+ * - contentType: desired ContentType
1331+ * - handler: desired ContentHandler
1332+ * - showTitle: boolean value indicating if the title should be visible
1333+ * @param onPeerSelected {Function(ContentPeer)} Called when the user has selected a peer
1334+ * @param onCancelPressed {Function()} Called when the user has pressed cancel
1335+ */
1336+ launchContentPeerPicker: function(filters, onPeerSelected, onCancelPressed) {
1337+ backendBridge.call('ContentHub.launchContentPeerPicker',
1338+ [filters, onPeerSelected, onCancelPressed]);
1339 },
1340
1341 /**
1342@@ -631,7 +844,9 @@
1343 * @method api.importContent
1344 * @param type {ContentType} type of the content to import
1345 * @param peer {ContentPeer} peer whos content should be imported
1346- * @param transferOptions {Object {multipleFiles: {Bool}, importToLocalStore: {Bool}} } the set of options for the transfer
1347+ * @param transferOptions {Object} a dictionary of transfer options. The options are the following:
1348+ * - multipleFiles {Bool}: specified if a transfer should involve multiple files or not
1349+ * - scope {ContentScope}: specifies the location where the transferred files should be copied to
1350 * @param onError {Function(reason:)} called when the transfer has failed
1351 * @param onSuccess {Function(Array of {ContentItem})} called when the transfer has been a success and items are available
1352 */

Subscribers

People subscribed via source and target branches

to all changes: