unity-scope-home-6.8.2+16.04.20160212.1/0000755000015600001650000000000012657434047017445 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/autogen.sh0000755000015600001650000000027712657432773021460 0ustar pbuserpbgroup00000000000000#!/bin/sh srcdir=`dirname $0` PKG_NAME="unity-scope-home" which gnome-autogen.sh || { echo "You need gnome-common from GNOME SVN" exit 1 } USE_GNOME2_MACROS=1 \ . gnome-autogen.sh "$@" unity-scope-home-6.8.2+16.04.20160212.1/Makefile.am.coverage0000644000015600001650000000250512657432773023301 0ustar pbuserpbgroup00000000000000 # Coverage targets .PHONY: clean-gcno clean-gcda \ coverage-html generate-coverage-html clean-coverage-html \ coverage-gcovr generate-coverage-gcovr clean-coverage-gcovr clean-local: clean-gcno clean-coverage-html clean-coverage-gcovr if HAVE_GCOV clean-gcno: @echo Removing old coverage instrumentation -find -name '*.gcno' -print | xargs -r rm clean-gcda: @echo Removing old coverage results -find -name '*.gcda' -print | xargs -r rm coverage-html: clean-gcda -$(MAKE) $(AM_MAKEFLAGS) -k check $(MAKE) $(AM_MAKEFLAGS) generate-coverage-html generate-coverage-html: @echo Collecting coverage data $(LCOV) --directory $(top_builddir) --capture --output-file coverage.info --no-checksum --compat-libtool LANG=C $(GENHTML) --prefix $(top_builddir) --output-directory coveragereport --title "Code Coverage" --legend --show-details coverage.info clean-coverage-html: clean-gcda -$(LCOV) --directory $(top_builddir) -z -rm -rf coverage.info coveragereport if HAVE_GCOVR coverage-gcovr: clean-gcda -$(MAKE) $(AM_MAKEFLAGS) -k check $(MAKE) $(AM_MAKEFLAGS) generate-coverage-gcovr generate-coverage-gcovr: @echo Generating coverage GCOVR report $(GCOVR) -x -r $(top_builddir) -o $(top_builddir)/coverage.xml clean-coverage-gcovr: clean-gcda -rm -rf $(top_builddir)/coverage.xml endif # HAVE_GCOVR endif # HAVE_GCOV unity-scope-home-6.8.2+16.04.20160212.1/tests/0000755000015600001650000000000012657434047020607 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/0000755000015600001650000000000012657434047023021 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/fake-sss-server.py0000755000015600001650000001331712657432773026427 0ustar pbuserpbgroup00000000000000#!/usr/bin/python # # Copyright (C) 2013 Canonical Ltd # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Authored by Pawel Stolowski # # from wsgiref.simple_server import make_server import cgi, sys, json import argparse import os, signal # # Simulates: # /smartscopes/v1/remote-scopes # /smartscopes/v1/search? # /smartscopes/v1/feedback # requests. # # Search request handler returns content of predefined json files in a round-robin manner # Both /search and /feedback handler can optionally dump some data useful for testing # The server runs forver or serves a limited number of requests (with --requests .. argument) and then exits. # # Usage example: # ./server.py --port 8888 --scopes remote_scopes.txt --search dump1.txt --feedback dump2.txt --requests 5 search_results1.txt search_results2.txt # # To test unity-home-scope with it, set SMART_SCOPES_SERVER=127.0.0.1:8888 environment variable before running unity-home-scope. # class FakeServerApp: def __init__ (self, remote_scopes, search_results, search_req_dump_file, feedback_req_dump_file): self._current_result = 0 self._remote_scopes_file = remote_scopes self._search_results_files = search_results self._search_dump_file = search_req_dump_file self._feedback_dump_file = feedback_req_dump_file # truncate dump files if search_req_dump_file != None: f = open (search_req_dump_file, "w+") f.close () if feedback_req_dump_file != None: f = open (feedback_req_dump_file, "w+") f.close() def __call__ (self, environ, start_response): method = environ.get ("PATH_INFO").rstrip ('/') routes = { "/smartscopes/v1/search": self.search, "/smartscopes/v1/feedback": self.feedback, "/smartscopes/v1/remote-scopes": self.remote_scopes } if routes.has_key (method): return routes[method](environ, start_response) return self.error ('404 NOT FOUND', 'Unknown method', environ, start_response) def error (self, status, message, environ, start_response): start_response (status, [('Content-Type', 'text/plain'), ('Content-Length', str (len (message)))]) return [message] def search (self, environ, start_response): url = environ.get ("QUERY_STRING", "") params = dict (cgi.parse_qsl (url, keep_blank_values = True)) self.dump_search_request (url) results = open (self._search_results_files[self._current_result]) response_body = results.read () results.close () # advance to next results file self._current_result += 1 if self._current_result >= len (self._search_results_files): self._current_result = 0 response_headers = [('Content-Type', 'application/json'), ('Content-Length', str(len(response_body)))] start_response ('200 OK', response_headers) return [response_body] def remote_scopes (self, environ, start_response): results = open (self._remote_scopes_file) response_body = results.read () results.close () response_headers = [('Content-Type', 'application/json'), ('Content-Length', str(len(response_body)))] start_response ('200 OK', response_headers) return [response_body] def feedback (self, environ, start_response): if environ['REQUEST_METHOD'] != 'POST' and 'CONTENT_LENGTH' not in environ: return self.error('400 Bad Request', 'Bad Request', environ, start_response) response_headers = [('Content-Type', 'application/json'), ('Content-Length', str(len("")))] self.dump_feedback (environ) start_response ('200 OK', response_headers) return [""] def dump_feedback (self, environ): if self._feedback_dump_file != None: f = open (self._feedback_dump_file, 'a+') content_length = int(environ['CONTENT_LENGTH']) content = environ['wsgi.input'].read(content_length) f.write (content.replace ("\n", "") + "\n") f.close () def dump_search_request (self, url): if self._search_dump_file != None: f = open (self._search_dump_file, 'a+') f.write (url + "\n") f.close () def timeout_handler (signum, frame): sys.exit (0) def main (): parser = argparse.ArgumentParser () parser.add_argument ('--requests', type=int, default=0, help='Number of requests to serve') parser.add_argument ('--port', metavar='N', type=int, default=8888, help='Port number') parser.add_argument ('--scopes', metavar='REMOTE_SCOPES', type=str, default=None, required=True, help='Results file for remote-scopes call') parser.add_argument ('result_files', metavar='RESULT_FILE', nargs='+', type=str) parser.add_argument ('--feedback', type=str, default=None, help='Dump feedback responses to a file') parser.add_argument ('--search', type=str, default=None, help='Dump search requests to a file') parser.add_argument ('--timeout', type=int, default=0, help='Quit after reaching timeout (in seconds)') args = parser.parse_args () app = FakeServerApp (args.scopes, args.result_files, args.search, args.feedback) srv = make_server('', args.port, app) if args.timeout > 0: signal.signal (signal.SIGALRM, timeout_handler) signal.alarm (args.timeout) if args.requests > 0: for i in range (0, args.requests): srv.handle_request() else: srv.serve_forever () if __name__ == '__main__': main () unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/samples/0000755000015600001650000000000012657434047024465 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/samples/search_results_with_query.txt0000644000015600001650000051165712657432773032557 0ustar pbuserpbgroup00000000000000{"scopes": [["music-audacious.scope", "client"], ["music-clementine.scope", "client"], ["music-gmusicbrowser.scope", "client"], ["music-guayadeque.scope", "client"], ["music-musique.scope", "client"], ["music-soundcloud.scope", "client"], ["reference-stackexchange.scope", "server"], ["news-googlenews.scope", "server"], ["code-devhelp.scope", "client"], ["code-github.scope", "client"], ["graphics-colourlovers.scope", "client"], ["graphics-deviantart.scope", "client"], ["help-manpages.scope", "client"], ["help-texdoc.scope", "client"], ["help-yelp.scope", "client"], ["recipes-gourmet.scope", "client"], ["reference-zotero.scope", "client"], ["more_suggestions-amazon.scope", "server"], ["more_suggestions-skimlinks.scope", "server"], ["more_suggestions-u1ms.scope", "server"], ["reference-wikipedia.scope", "server"], ["reference-dictionary.scope", "server"], ["info-ddg_related.scope", "server"]], "type": "recommendations"} {"info": {"more_suggestions-skimlinks.scope": [{"comment": "The dual authors of this corporate culture commentary gradually introduce the principles of an African philosophy that views everyone as one human family and considers the success of the group above that of the individual, and narrate a fictional story of workplace transformation based on the principles. Annotation 2010 Book News, Inc., Portland, OR (booknews.com)", "metadata": {"category": "electronics", "currency": "USD", "formatted_price": "$19,98", "images": {"null": ["http://s3.amazonaws.com/images.api-product.skimlinks.com/image/c345ef259589fdfa74ff365e823f236a_original"]}, "price": "19.98", "id": "skimlinks:20732|12323464"}, "icon_hint": "http://s3.amazonaws.com/images.api-product.skimlinks.com/image/c345ef259589fdfa74ff365e823f236a_original", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=iRwdjTdNfy-KNFG85C8wQZXF88aWeyt7WG2Ebg%3D%3D&u=http%3A%2F%2Fwww.target.com%2Fp%2Fubuntu-hardcover%2F-%2FA-12323464%3FLNM%257C12323464%3D%26CPNG%3D", "title": "Ubuntu (Hardcover)"},{"comment": "", "metadata": {"images": {"null": ["http://s3.amazonaws.com/images.api-product.skimlinks.com/image/c345ef259589fdfa74ff365e823f236a_original"]}, "id": "skimlinks:20732|12323464"}, "icon_hint": "http://s3.amazonaws.com/images.api-product.skimlinks.com/image/c345ef259589fdfa74ff365e823f236a_original", "uri": "scopes-query://Ubuntu", "title": "Did you mean 'Ubuntu'?"}, {"comment": "ARC has a habit of taking their stable of musicians into territories they may not have originally intended -- they're a modern world music label,...", "metadata": {"category": "electronics", "currency": "USD", "formatted_price": "$13,19", "images": {"null": ["http://s3.amazonaws.com/images.api-product.skimlinks.com/image/1511e84eb0fa57db63d4171ceef16a4d_original"]}, "price": "13.19", "id": "skimlinks:20732|12709456"}, "icon_hint": "http://s3.amazonaws.com/images.api-product.skimlinks.com/image/1511e84eb0fa57db63d4171ceef16a4d_original", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=g5vNyIM_dSJxNXDbofo4bMshMIX7Em3gGPn02w%3D%3D&u=http%3A%2F%2Fwww.target.com%2Fp%2Fmaster-drummers-of-africa-vol-2-ubuntu%2F-%2FA-12709456%3FLNM%257C12709456%3D%26CPNG%3D", "title": "Master Drummers of Africa, Vol. 2: Ubuntu"}, {"comment": "Ubuntu Server is a complete, free server operating system that just works, with the extra Ubuntu polish, innovation, and simplicity that administrators love. Now, there's a definitive, authoritative guide to getting up-and-running quickly with the newest, most powerful versions of Ubuntu Server. Written by leading members of the Ubuntu community, The Official Ubuntu Server Book, Second Edition, covers all you need to know to make the most of Ubuntu Server, whether you're a beginner or a battle-hardened senior system administrator. The authors cover Ubuntu Server from start to finish: installation, basic administration and monitoring,...", "metadata": {"category": "electronics", "currency": "USD", "formatted_price": "$33,99", "images": {"null": ["http://s3.amazonaws.com/images.api-product.skimlinks.com/image/1f755d327cb35b30f918b36e0c127cae_original"]}, "price": "33.99", "id": "skimlinks:20732|12289160"}, "icon_hint": "http://s3.amazonaws.com/images.api-product.skimlinks.com/image/1f755d327cb35b30f918b36e0c127cae_original", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=eWLHqnaJqiJGJuQCX9Zym4EJg6e06sfi8FO41A%3D%3D&u=http%3A%2F%2Fwww.target.com%2Fp%2Fthe-official-ubuntu-server-book-mixed-media-product%2F-%2FA-12289160%3FLNM%257C12289160%3D%26CPNG%3D", "title": "The Official Ubuntu Server Book (Mixed media product)"}]}, "type": "results"} {"info": {"reference-dictionary.scope": [{"comment": "A South African ideology focusing on people's allegiances and relations with each other", "metadata": {"category": "dictionary", "images": {"null": ["file:///usr/share/icons/unity-icon-theme/places/svg/service-wordnik.svg"]}, "id": "DICTIONARY:ubuntu"}, "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wordnik.svg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=dXDvZXbjBOBD4wvWVsuCJhgL-iPcgqNAP-nIUg%3D%3D&u=http%3A%2F%2Fwordnik.com%2Fword%2Fubuntu", "title": "ubuntu: definition"}]}, "type": "results"} {"info": {"news-googlenews.scope": [{"category": 0, "comment": "
\"\"
Telegraph.co.uk

\"\"
Ubuntu Edge raises record-breaking $10 million, enters Guinness Book of Almost
Engadget
16 August 2013 \u2013 Canonical's campaign to develop the Ubuntu Edge Smartphone has smashed the record for the largest ever amount to be sourced from crowdfunding. The long-standing record, which stood at $10,266,844 and was previously held by the ...
Ubuntu Edge smartphone sets crowdfunding record, but still $20m short of targetZDNet (blog)
FWIW, the Ubuntu Edge has become a crowdfunding record-breakerGigaOM
Ubuntu Edge Beats Pebble As Biggest Crowdfunding Campaign EverThe Next Web
Telegraph.co.uk -Fast Company -Wall Street Journal (blog)
all 15 news articles »
", "title": "Ubuntu Edge raises record-breaking $10 million, enters Guinness Book of Almost - Engadget", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNESqjZPnQK9UBZDKg08TYKYz6M25w&url=http://www.engadget.com/2013/08/16/ubuntu-edge-raises-10-million-dollars/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcQiOMnDRVkgj6013s35njtVHLefjF2xEb450UDf6AN1294HEz2O6phB9AmTAGhgpZMMuH6uPxw", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNESqjZPnQK9UBZDKg08TYKYz6M25w&url=http://www.engadget.com/2013/08/16/ubuntu-edge-raises-10-million-dollars/", "result_type": 0, "metadata": {"id": "googlenews:d00e7f6999673504da5edfa44d841b365e50f6b1", "language": "us", "published": "Fri, 16 Aug 2013 09:58:04 GMT"}}, {"category": 0, "comment": "
\"\"
SlashGear

\"\"
Glorious failure: Ubuntu Edge passes $10M on Indiegogo
VentureBeat
The goal is to create a novel device that dual-boots mobile Android and desktop Ubuntu, creating a smartphone that, with the help of a monitor and a keyboard, is also your heavy-duty, hardcore desktop computer. To make it happen, the Ubuntu Edge team ...
Ubuntu Edge appears \u201cabsolutely anti-ergonomic\u201d in the wild [UPDATE]SlashGear
Ubuntu 13.10 Release Date, Features: Overshadowed by Ubuntu Edge?The VAR Guy

all 6 news articles »
", "title": "Glorious failure: Ubuntu Edge passes $10M on Indiegogo - VentureBeat", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEUk6UIVyklb_FSxXYE_m0tXMcy4w&url=http://venturebeat.com/2013/08/14/glorious-failure-ubuntu-edge-passes-10m-on-indiegogo/", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcQ4D4bjkOuyLUoKADIxo-tTP00jgShwDC-5tSvc_uJQj1MPtTk9H6VZTWIcV0QfJTDiVoNPQBM", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEUk6UIVyklb_FSxXYE_m0tXMcy4w&url=http://venturebeat.com/2013/08/14/glorious-failure-ubuntu-edge-passes-10m-on-indiegogo/", "result_type": 0, "metadata": {"id": "googlenews:3237d68565b601a6267fd3a26ba141458ebd0447", "language": "us", "published": "Wed, 14 Aug 2013 20:32:31 GMT"}}, {"category": 0, "comment": "
\"\"
Ars Technica

\"\"
Why Ubuntu's creator still invests his fortune in an unprofitable company
Ars Technica
And although Shuttleworth wanted Canonical to be self-sustaining, he didn't threaten to abandon Ubuntu if it lost money. "When we started, I told the team two years," he recently told Ars. "I didn't say, 'till it's profitable.' I said, 'You can count ...
Canonical will win even if Ubuntu Edge doesn't make its $32 millionZDNet (blog)
Canonical On Why They Chose Indiegogo Over Kickstarter For The Ubuntu EdgeCrowdfund Insider

all 7 news articles »
", "title": "Why Ubuntu's creator still invests his fortune in an unprofitable company - Ars Technica", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGhhtaMlSt_jknKiEQT6sQ0QV4obQ&url=http://arstechnica.com/information-technology/2013/08/why-ubuntus-creator-still-invests-his-fortune-in-an-unprofitable-company/", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcTahT44weOAvDDW2cUEzPdLg70ERDj8lY4PFnn4sUP1_MhpaAaTDJ1K4L6Q8j_aiAM8t5He7nk", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGhhtaMlSt_jknKiEQT6sQ0QV4obQ&url=http://arstechnica.com/information-technology/2013/08/why-ubuntus-creator-still-invests-his-fortune-in-an-unprofitable-company/", "result_type": 0, "metadata": {"id": "googlenews:fb1aa4e74bcd8426f9415be30dc73eabf4261d41", "language": "us", "published": "Wed, 14 Aug 2013 13:30:52 GMT"}}, {"category": 0, "comment": "
\"\"
San Francisco Chronicle (blog)

\"\"
Sean O'Toole claims Napa's former Ubuntu, will turn it into Torc
San Francisco Chronicle (blog)
As Sean O'Toole puts it, the running joke with his chef friends is how an Irishman like himself learned to cook. So, as O'Toole \u2014 who has cooked at Bardessono, Quince, the Mina Group and most recently Hopper Creek Kitchen \u2014 ramps up his first solo ...
Ubuntu Space In Napa Will Reopen As Torc This FallSFist

all 3 news articles »
", "title": "Sean O'Toole claims Napa's former Ubuntu, will turn it into Torc - San Francisco Chronicle (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGCOJqCV_4lg4mjJnbKNsTLVu75Og&url=http://insidescoopsf.sfgate.com/blog/2013/08/14/sean-otoole-claims-napas-former-ubuntu-will-turn-it-into-torc/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcRhkFIqDtblqPNEQUpHPoEuT5DZXNjhkbrEcLn6JF8oOT6XHjZG82ZiYWh8xux6QDCezUqd8A9d", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGCOJqCV_4lg4mjJnbKNsTLVu75Og&url=http://insidescoopsf.sfgate.com/blog/2013/08/14/sean-otoole-claims-napas-former-ubuntu-will-turn-it-into-torc/", "result_type": 0, "metadata": {"id": "googlenews:69bd0cf9a5dfe84dc0bb5d7277fe763e7e14c9d7", "language": "us", "published": "Wed, 14 Aug 2013 19:48:53 GMT"}}, {"category": 0, "comment": "
\"\"
Technology Zimbabwe

\"\"
You should give Ubuntu and Steam a chance: Evaluation and Howto.
Technology Zimbabwe
ubuntu-steam Linux has over the years consistently proven itself to be a worthwhile alternative on the desktop and has even become the champion in mobile space in the form of Android. For a long time though its inability to bring about the much ...

", "title": "You should give Ubuntu and Steam a chance: Evaluation and Howto. - Technology Zimbabwe", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFUD-j42bkux9fhgwxUsG7C-c1rog&url=http://www.techzim.co.zw/2013/08/you-should-give-ubuntu-and-steam-a-chance-evaluation-and-howto/", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcS3jgPpfKAx-ZauufFp6Owlh82AooW_cd8jLPAfhB3a2H1cQg6ia84OfbcuxTu_ArRL790hWdlF", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFUD-j42bkux9fhgwxUsG7C-c1rog&url=http://www.techzim.co.zw/2013/08/you-should-give-ubuntu-and-steam-a-chance-evaluation-and-howto/", "result_type": 0, "metadata": {"id": "googlenews:47ae12ee6f89fc6e48cb0b7b857b2cb7f4e38ea3", "language": "us", "published": "Fri, 16 Aug 2013 09:47:37 GMT"}}, {"category": 0, "comment": "

\"\"
Ubuntu Lockscreen Adds Notifications and Ubuntu's Looks to Android
Lifehacker
Android: If you like the look of Ubuntu Touch, or just Ubuntu's overall color scheme, Ubuntu Lockscreen can turn your boring old slide-to-unlock into something a bit more useful and fun to use. The app supports app notifications, SMS alerts, password ...

", "title": "Ubuntu Lockscreen Adds Notifications and Ubuntu's Looks to Android - Lifehacker", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF_C1VFQ6047TZRTEywUGQqWmT63Q&url=http://lifehacker.com/ubuntu-lockscreen-adds-notifications-and-ubuntus-looks-1084146843", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF_C1VFQ6047TZRTEywUGQqWmT63Q&url=http://lifehacker.com/ubuntu-lockscreen-adds-notifications-and-ubuntus-looks-1084146843", "result_type": 0, "metadata": {"id": "googlenews:00d4ad9b7909e2b713c1b917ea749865a2fecf84", "language": "us", "published": "Mon, 12 Aug 2013 19:39:49 GMT"}}, {"category": 0, "comment": "

\"\"
Ubuntu Phone Seeks To Be Crowd-Funded -- for $32 Million
Sci-Tech Today
ne of the most interesting aspects of the Ubuntu Edge is that it will run both Linux and Android, instead of solely running Linux like you would expect it to. To start, people running the phone in Android mode will access Ubuntu through the Ubuntu for ...

", "title": "Ubuntu Phone Seeks To Be Crowd-Funded -- for $32 Million - Sci-Tech Today", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFasMFRRtQPkjQjbTeSjdXmQhG3SQ&url=http://www.sci-tech-today.com/story.xhtml?story_id%3D03200001OXQ8", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFasMFRRtQPkjQjbTeSjdXmQhG3SQ&url=http://www.sci-tech-today.com/story.xhtml?story_id%3D03200001OXQ8", "result_type": 0, "metadata": {"id": "googlenews:efed162137c9f8d74d419a3dd2a18f870bd4a4c3", "language": "us", "published": "Thu, 15 Aug 2013 07:02:12 GMT"}}, {"category": 0, "comment": "

\"\"
Ubuntu Edge Price Drops Again, Canonical Now Needs Even More Backers To ...
TechCrunch
The Ubuntu Edge, the smartphone made by UK-based Canonical, has received a second price drop in its $32 million crowdfunding campaign. For the remaining 14 days of its audacious Indiegogo campaign, the Edge will now cost $695 instead of its full price ...

", "title": "Ubuntu Edge Price Drops Again, Canonical Now Needs Even More Backers To ... - TechCrunch", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHPfow4-3cU0dBkq8bVO11zrOQEzg&url=http://techcrunch.com/2013/08/08/ubuntu-edge-price-drops-to-695-canonical-now-needs-even-more-backers/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHPfow4-3cU0dBkq8bVO11zrOQEzg&url=http://techcrunch.com/2013/08/08/ubuntu-edge-price-drops-to-695-canonical-now-needs-even-more-backers/", "result_type": 0, "metadata": {"id": "googlenews:3cfa43c419d885f8c2d142f9d9effa853328432c", "language": "us", "published": "Thu, 08 Aug 2013 15:07:53 GMT"}}, {"category": 0, "comment": "
\"\"
Telegraph.co.uk

\"\"
Crowdfunded Ubuntu Phone Will Be A Big Success, Even If It Never Happens
Forbes
Another day, another healthy dose of press attention for the Ubuntu Edge. This non-existent smartphone is still just a glint in the eye of its originator, billionaire and Forbes disruptor Mark Shuttleworth, but it's back in the headlines after ...
On the Edge of failure: Ubuntu smartphone looks unlikely to reach crowdfunding ...The Verge
Ubuntu Edge looks doomed, but Canonical cuts cost of making the phoneArs Technica
Ubuntu Edge crowdfunding campaign 'doomed to fail'Telegraph.co.uk
CNET -ZDNet (blog) -V3.co.uk
all 99 news articles »
", "title": "Crowdfunded Ubuntu Phone Will Be A Big Success, Even If It Never Happens - Forbes", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHsYKmQYS-EfAZ8l-4H2-gzIKlmHQ&url=http://www.forbes.com/sites/sharifsakr/2013/08/08/crowdfunded-ubuntu-phone-will-be-a-big-success-even-if-it-never-happens/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcQiOMnDRVkgj6013s35njtVHLefjF2xEb450UDf6AN1294HEz2O6phB9AmTAGhgpZMMuH6uPxw", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHsYKmQYS-EfAZ8l-4H2-gzIKlmHQ&url=http://www.forbes.com/sites/sharifsakr/2013/08/08/crowdfunded-ubuntu-phone-will-be-a-big-success-even-if-it-never-happens/", "result_type": 0, "metadata": {"id": "googlenews:dc43c8092939e2859271459553e33127f8a2aa89", "language": "us", "published": "Thu, 08 Aug 2013 18:31:56 GMT"}}, {"category": 0, "comment": "

\"\"
This Week On The TC Gadgets Podcast: Ubuntu Edge, Nexus Rumors, Beddit ...
TechCrunch
The Ubuntu Edge may be the future of the smartphone, but does it have a future? Nexus rumors abound for the next generation of Google's vanilla tablets and smartphones. Meanwhile, startups are hitting up Indiegogo with new quantified self devices, ...

", "title": "This Week On The TC Gadgets Podcast: Ubuntu Edge, Nexus Rumors, Beddit ... - TechCrunch", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHRSqyvQ-wLgeAWbevREt6CjdG_dw&url=http://techcrunch.com/2013/08/09/this-week-on-the-tc-gadgets-podcast-ubuntu-edge-nexus-rumors-beddit-and-trace-tracker/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHRSqyvQ-wLgeAWbevREt6CjdG_dw&url=http://techcrunch.com/2013/08/09/this-week-on-the-tc-gadgets-podcast-ubuntu-edge-nexus-rumors-beddit-and-trace-tracker/", "result_type": 0, "metadata": {"id": "googlenews:a0b3c7ab70d14a872d03fd005e1b73936fc2a8e1", "language": "us", "published": "Fri, 09 Aug 2013 19:03:27 GMT"}}, {"category": 0, "comment": "

\"\"
Install Classic Menu Indicator in Ubuntu 13.04, 12.10 or 12.04
ITworld.com
The Classic Menu Indicator brings back a fast way to access applications and tools in Ubuntu. After using the Classic Menu Indicator, it just reminds me of how much I still dislike Unity. It seems so much easier and faster to get to what I want in Ubuntu.

", "title": "Install Classic Menu Indicator in Ubuntu 13.04, 12.10 or 12.04 - ITworld.com", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGunsUsWRm0zi7CHRF1aew0Uz0gzQ&url=http://www.itworld.com/software/369000/install-classic-menu-indicator-ubuntu-1304-1210-or-1204", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGunsUsWRm0zi7CHRF1aew0Uz0gzQ&url=http://www.itworld.com/software/369000/install-classic-menu-indicator-ubuntu-1304-1210-or-1204", "result_type": 0, "metadata": {"id": "googlenews:f2e7c63a284849d1f84213968cdc6522f6c7820d", "language": "us", "published": "Tue, 13 Aug 2013 22:20:36 GMT"}}, {"category": 0, "comment": "
\"\"
The Guardian

\"\"
Ubuntu Edge: Crowdfunding a Super-Smartphone
Businessweek
Alexey Miller, the chief executive officer of Russian natural gas exporter Gazprom, seemed to be having a megalomaniacal moment. On July 16 he took to the company's website and demanded a tablet computer that could mimic all of the functions of a PC.
Ubuntu Edge will miss $32m crowdfunding target, say researchersThe Guardian
T-Mobile backs Ubuntu smartphoneZDNet (blog)
T-Mobile joins effort to bring Ubuntu phones to US mobile marketRegister
InfoWorld (blog) -TmoNews
all 15 news articles »
", "title": "Ubuntu Edge: Crowdfunding a Super-Smartphone - Businessweek", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHTN1jf42crYcZYbQJJ0n-CFy0ESg&url=http://www.businessweek.com/articles/2013-08-01/ubuntu-edge-crowdfunding-a-super-smartphone", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcRQQgBZwmzsOS5Q0rRmZdf18akvkEZIlSl7CiLMd3kz_LXwfjfFUk0HNs9MSWf5DGGftAk0_u5a", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHTN1jf42crYcZYbQJJ0n-CFy0ESg&url=http://www.businessweek.com/articles/2013-08-01/ubuntu-edge-crowdfunding-a-super-smartphone", "result_type": 0, "metadata": {"id": "googlenews:0d41a66c2b7d2dcb9fe9346e55413f6340ea8334", "language": "us", "published": "Fri, 02 Aug 2013 20:55:37 GMT"}}, {"category": 0, "comment": "

\"\"
Ubuntu Edge: Opening a Window to Computing's Future
Kioskea
Canonical, Ubuntu's parent company, wagered that there were enough visionaries in the world to crowd-source a whopping $32 million to develop a Linux-powered combination smartphone/PC, the Ubuntu Edge; it looks like Canonical is going to lose that bet ...

", "title": "Ubuntu Edge: Opening a Window to Computing's Future - Kioskea", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGWpK6rGouam9zZUQvpdnAZP13TOA&url=http://en.kioskea.net/news/23943-ubuntu-edge-opening-a-window-to-computing-s-future", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGWpK6rGouam9zZUQvpdnAZP13TOA&url=http://en.kioskea.net/news/23943-ubuntu-edge-opening-a-window-to-computing-s-future", "result_type": 0, "metadata": {"id": "googlenews:777635626831a109cb26d53255e64eb922577f77", "language": "us", "published": "Wed, 14 Aug 2013 19:10:14 GMT"}}, {"category": 0, "comment": "
\"\"
ZDNet (blog)

\"\"
Ubuntu: One OS, one interface, all devices
ZDNet (blog)
It's only now that this plan is coming into focus for those who don't follow Ubuntu like a hawk. As Mark Shuttleworth, Canonical and Ubuntu's founder said at OSCon, the major open-source convention held in Portland, OR, "Convergence is the core story.
Ubuntu Edge: the best smartphone you'll never ownExpert Reviews
Ubuntu Edge: A case of mistaken identityTechRepublic (blog)
Ubuntu Edge is a smartphone and desktop computer in oneGizmag
ITworld.com -Network World -InfoWorld
all 50 news articles »
", "title": "Ubuntu: One OS, one interface, all devices - ZDNet (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGvAbq_FPlK16hFTcTKHiodB1Ou8g&url=http://www.zdnet.com/ubuntu-one-os-one-interface-all-devices-7000018613/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcQt0r7R2ilMW0S0XSgnhpJv1iZBxkk8ca_IrTx9JCxEvQu0mF1tWBU0fCFwHUpUtb2PCoDKgx57", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGvAbq_FPlK16hFTcTKHiodB1Ou8g&url=http://www.zdnet.com/ubuntu-one-os-one-interface-all-devices-7000018613/", "result_type": 0, "metadata": {"id": "googlenews:b084cbeaacf6a200807be6128687cd27187084a6", "language": "us", "published": "Fri, 26 Jul 2013 18:55:23 GMT"}}, {"category": 0, "comment": "
\"\"
ExtremeTech

\"\"
SOL: The $350 Ubuntu laptop that runs on solar power
Gizmag
... a rugged laptop that doesn't rely on a power socket to stay charged. The Ubuntu Linux-powered computer is instead equipped with a detachable solar panel, which the developers claim will provide up to 10 hours of battery life after just two hours in ...
Solar-powered Ubuntu laptop boasts 10-hour battery, 2-hour charge timeEngadget
Sol, the $350 solar-powered rugged Ubuntu laptop that won't be usable in the sunExtremeTech
Penguins, I give you: The SOLAR-POWERED Ubuntu laptopRegister
Network World -Geek -LAPTOP Magazine (blog)
all 26 news articles »
", "title": "SOL: The $350 Ubuntu laptop that runs on solar power - Gizmag", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEdhOatSjUe3e7EjvwHUvjJql5mRg&url=http://www.gizmag.com/sol-solar-powered-ubuntu-laptop/28611/", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcTeYFiBF44Pt-Jk5fPDVKLHGO8lfduNIL1gVzWKDzsp3hvMlgZg0J2MfuUj3aKn1hPscSfHg4U", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEdhOatSjUe3e7EjvwHUvjJql5mRg&url=http://www.gizmag.com/sol-solar-powered-ubuntu-laptop/28611/", "result_type": 0, "metadata": {"id": "googlenews:9692c57aae0a7e193b70ebfe5b24aaa5015141ce", "language": "us", "published": "Wed, 07 Aug 2013 10:12:01 GMT"}}, {"category": 0, "comment": "
\"\"
Wired

\"\"
Ubuntu Superphone Fundraiser Rakes in $1M on Debut
Wired
Canonical \u2014 makers of the Ubuntu distribution of Linux \u2014 is asking geeks to collectively cough up $32 million dollars in the next month to crowdfund a smartphone that could serve as a laptop replacement. Supporters have already pledged over $1 ...
Can the internet raise $32 million to build the Ubuntu Edge smartphone?The Verge
Ubuntu Edge smartphone announced with $32 million Indiegogo campaign ...Engadget
10 criteria Ubuntu Edge must meet if it's going to succeedTechRepublic (blog)
ReadWrite -CNET (blog) -The Next Web
all 215 news articles »
", "title": "Ubuntu Superphone Fundraiser Rakes in $1M on Debut - Wired", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEeKBtS3WLKVftIRfV_-zt8kq5wxg&url=http://www.wired.com/wiredenterprise/2013/07/ubuntu-laptop-replacement/", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcSg0qTlxXvGlvVNzkxYBNeVG-9LcCMLy58V2qWqGVegoIZ3nWfj34Wbv1YEGRMOdN1EQpkUcASR", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEeKBtS3WLKVftIRfV_-zt8kq5wxg&url=http://www.wired.com/wiredenterprise/2013/07/ubuntu-laptop-replacement/", "result_type": 0, "metadata": {"id": "googlenews:1c1f190b1ed226fe3832279f21aa5883630f5100", "language": "us", "published": "Mon, 22 Jul 2013 19:58:38 GMT"}}, {"category": 0, "comment": "

\"\"
LinuxMint 15 delivers smooth alternative to Ubuntu
Network World
Network World - The crafters of the LinuxMint distro are in a ticklish position. Mint is based on Ubuntu, which in turn, is based on Debian, which in turn, has the moveable feast of the Linux kernel as its underpinning. All three have changed ...

", "title": "LinuxMint 15 delivers smooth alternative to Ubuntu - Network World", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEl5QpZ7jD-R-mQbp5dNRm54ceWZA&url=http://www.networkworld.com/reviews/2013/080513-linux-mint-test-272497.html", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEl5QpZ7jD-R-mQbp5dNRm54ceWZA&url=http://www.networkworld.com/reviews/2013/080513-linux-mint-test-272497.html", "result_type": 0, "metadata": {"id": "googlenews:c9a8382d28f31b0f3a02a48f1c643dbc91e213f9", "language": "us", "published": "Mon, 05 Aug 2013 10:09:40 GMT"}}, {"category": 0, "comment": "
\"\"
ZDNet (blog)

\"\"
Ubuntu Edge might just change the computing world
ZDNet (blog)
Mark Shuttleworth, founder of Ubuntu and its parent company, Canonical, is making a bet with the technology market. He's betting that enough of you will be willing to invest in a smartphone that can double as a PC, the Ubuntu Edge, to raise the $32 ...
If The Ubuntu Edge Crowdfunding Experiment Works, Backers May Get To Vote ...TechCrunch
Ultimate goal for Ubuntu Edge phone may go beyond crowdfunding campaignTechHive
Ubuntu Edge crowdfunding campaign adds lower price points as pledges slowThe Verge
ExtremeTech -Telegraph.co.uk -Ars Technica
all 66 news articles »
", "title": "Ubuntu Edge might just change the computing world - ZDNet (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEthn1_384NjulGKKdKdxZ9fDNwHg&url=http://www.zdnet.com/ubuntu-edge-might-just-change-the-computing-world-7000018464/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcSJNH5EumTYhixukNj5r4K5Xo6B-4VslhUQT5SoJj3JIAODEHrO_VKVAMKs6r5uElg8UjoGlyU", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEthn1_384NjulGKKdKdxZ9fDNwHg&url=http://www.zdnet.com/ubuntu-edge-might-just-change-the-computing-world-7000018464/", "result_type": 0, "metadata": {"id": "googlenews:2b1098c469e7c8f90732ee1abae7c3f6046b7b40", "language": "us", "published": "Wed, 24 Jul 2013 12:10:20 GMT"}}, {"category": 0, "comment": "

\"\"
Ubuntu derivatives: 5 of the best Ubuntu-based distros
TechRadar UK
Are you a Linux desktop user who loves Ubuntu but is wary of Unity? You're in luck. There are lots of Ubuntu spins, both from Canonical and independent developers, which preserve the basic infrastructure and essence of Ubuntu but replace the default ...

", "title": "Ubuntu derivatives: 5 of the best Ubuntu-based distros - TechRadar UK", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEpkk7YOWJ4iG2gfc_yLGz2rX4eSg&url=http://www.techradar.com/news/software/operating-systems/ubuntu-derivatives-5-of-the-best-ubuntu-based-distros-1170314", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEpkk7YOWJ4iG2gfc_yLGz2rX4eSg&url=http://www.techradar.com/news/software/operating-systems/ubuntu-derivatives-5-of-the-best-ubuntu-based-distros-1170314", "result_type": 0, "metadata": {"id": "googlenews:63d19c590533797de7a086a07aa76e39cbd132ae", "language": "us", "published": "Sun, 04 Aug 2013 09:08:19 GMT"}}, {"category": 0, "comment": "
\"\"
PhoneDog

\"\"
T-Mobile follows Verizon in supporting Ubuntu through Carrier Advisory Group
FierceWireless
T-Mobile US (NYSE:TMUS) joined fellow U.S. carrier Verizon Wireless (NYSE:VZ) in the Ubuntu Carrier Advisory Group, a consortium of mobile operators participating in technology discussions around the Linux-based operating system. The 13-member ...
The five best distros based on UbuntuITworld.com
T-Mobile becomes second US carrier to join Ubuntu Carrier Advisory GroupPhoneDog
Ubuntu Edge smartphone crowdfunding effort 'destined to fail'ITProPortal
The VAR Guy -Phones Review
all 13 news articles »
", "title": "T-Mobile follows Verizon in supporting Ubuntu through Carrier Advisory Group - FierceWireless", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHR0i2v3rd5WIihnpS8nSLFgZRP8A&url=http://www.fiercewireless.com/story/t-mobile-follows-verizon-supporting-ubuntu-through-carrier-advisory-group/2013-08-05", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcShWoIjktvq3gWOBX4FX6Cs2AlTBaInUqahSFdWOtOsO0YvC1MGSuqwUrQVL7DooarwpGgFA8I6", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHR0i2v3rd5WIihnpS8nSLFgZRP8A&url=http://www.fiercewireless.com/story/t-mobile-follows-verizon-supporting-ubuntu-through-carrier-advisory-group/2013-08-05", "result_type": 0, "metadata": {"id": "googlenews:94154d571a1559a9d9240fe66381a3ea02c67d8b", "language": "us", "published": "Mon, 05 Aug 2013 16:20:21 GMT"}}, {"category": 0, "comment": "
\"\"
Mobile World Live

\"\"
Canonical revamps Ubuntu Edge prices; funding miss nears
Mobile World Live
Canoncial revamped (again) the funding for an Ubuntu Edge smartphone as part of its Indiegogo crowd funding programme, as it looked increasingly likely the company will miss its $32 million target. The company fixed the price of a device at $695 ...

and more »
", "title": "Canonical revamps Ubuntu Edge prices; funding miss nears - Mobile World Live", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEnNpfyekdePybyEjbt1TY8-prR6g&url=http://www.mobileworldlive.com/canonical-revamps-ubuntu-edge-prices-funding-miss-nears", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcS8kKCTHkrCixmmo2pBXNMcfcBf7PfrTshCb6pAFf68QwbhLUG9_FvZSEyTnKQkQrymlAxicN2k", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEnNpfyekdePybyEjbt1TY8-prR6g&url=http://www.mobileworldlive.com/canonical-revamps-ubuntu-edge-prices-funding-miss-nears", "result_type": 0, "metadata": {"id": "googlenews:555e8d1cde9da3b01a6205c104902cce51d767e2", "language": "us", "published": "Mon, 12 Aug 2013 16:24:47 GMT"}}, {"category": 0, "comment": "

\"\"
Ubuntu puts forums back online, reveals autopsy of a brag hacker
Register
Only the forums and not the popular Ubuntu Linux distribution nor any Canonical or Ubuntu services, namely Ubuntu One and Launchpad, were affected. "We have repaired and hardened the Ubuntu Forums, and as the problematic settings are the default ...

", "title": "Ubuntu puts forums back online, reveals autopsy of a brag hacker - Register", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHfDylX2l5ytPZoisQmZV7Jmi69EA&url=http://www.theregister.co.uk/2013/08/02/ubuntu_forum_hack_postmortem/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHfDylX2l5ytPZoisQmZV7Jmi69EA&url=http://www.theregister.co.uk/2013/08/02/ubuntu_forum_hack_postmortem/", "result_type": 0, "metadata": {"id": "googlenews:a829bab96f46e7fd73c41803321e93510629efc6", "language": "us", "published": "Fri, 02 Aug 2013 10:20:24 GMT"}}, {"category": 0, "comment": "

\"\"
Canonical Seeks $32M To Build A Ubuntu Smartphone Designed For ...
TechCrunch
Ubuntu Mobile isn't new; Canonical has already done much to promote its efforts to break into the smartphone mobile software space. But today the company is launching an Indiegogo campaign to fund the development of its first own-branded Ubuntu mobile ...

", "title": "Canonical Seeks $32M To Build A Ubuntu Smartphone Designed For ... - TechCrunch", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF1IZYYGzcWfj_HGOH7wZZLmE6Osg&url=http://techcrunch.com/2013/07/22/ubuntu-smartphone-canonical/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF1IZYYGzcWfj_HGOH7wZZLmE6Osg&url=http://techcrunch.com/2013/07/22/ubuntu-smartphone-canonical/", "result_type": 0, "metadata": {"id": "googlenews:e8beafb50f2802e50ff5d06b99fcfa4edb927c4b", "language": "us", "published": "Mon, 22 Jul 2013 15:27:03 GMT"}}, {"category": 0, "comment": "
\"\"
ReadWrite

\"\"
Ubuntu Edge: A Device In Serious Need Of A Community
ReadWrite
Canonical, the company behind Ubuntu, hopes, however, that this funding campaign will help to birth a device that appeals to an audience far bigger than the Ubuntu desktop community, as ReadWrite's Dan Rowinski reports. Mark Shuttleworth, Canonical's ...

", "title": "Ubuntu Edge: A Device In Serious Need Of A Community - ReadWrite", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGT2m-wUiNJ0IPW4NpsvLEkrnkNBg&url=http://readwrite.com/2013/07/24/ubuntu-edge-kickstarter-community", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcRToB2758WYlQ15uBzoMI6A7SgXImjqL-6FOf1SRjrEfF1AWE4OOPqWhzDQ6aQIrqlgp_ZgMK8", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGT2m-wUiNJ0IPW4NpsvLEkrnkNBg&url=http://readwrite.com/2013/07/24/ubuntu-edge-kickstarter-community", "result_type": 0, "metadata": {"id": "googlenews:47c930461fd555fcce71358e59db46955ac14040", "language": "us", "published": "Wed, 24 Jul 2013 13:32:25 GMT"}}, {"category": 0, "comment": "
\"\"
GSMArena.com (blog)

\"\"
Canonical lowers Ubuntu Edge price to $695 on Indiegogo
GSMArena.com (blog)
Canonical's crowd-funding project, aiming to raise $32 million in order to make its Ubuntu Edge device a reality was off to a fast start, making $2 million in the first 8 hours, but it slowed down shortly after and it seems it stands little chance it ...

and more »
", "title": "Canonical lowers Ubuntu Edge price to $695 on Indiegogo - GSMArena.com (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGBbp9WGu-Px2JC_vdZTD3RnVlaVw&url=http://blog.gsmarena.com/canonical-lowering-ubuntu-edge-price-to-695-in-effort-to-close-out-next-two-weeks-on-a-roll/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcTUZELjTeLPc2jxUVDhSooRtb4MkhTnNMocey5UmwYHgkvg6tqRsD_kCpfcE29EoKosYFuZcSDq", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGBbp9WGu-Px2JC_vdZTD3RnVlaVw&url=http://blog.gsmarena.com/canonical-lowering-ubuntu-edge-price-to-695-in-effort-to-close-out-next-two-weeks-on-a-roll/", "result_type": 0, "metadata": {"id": "googlenews:cbada1a0b404bbae38e1252d243f5e98eeec6902", "language": "us", "published": "Thu, 08 Aug 2013 14:09:14 GMT"}}, {"category": 1, "comment": "
\"\"
Telegraph.co.uk

\"\"
Ubuntu Edge raises record-breaking $10 million, enters Guinness Book of Almost
Engadget
16 August 2013 \u2013 Canonical's campaign to develop the Ubuntu Edge Smartphone has smashed the record for the largest ever amount to be sourced from crowdfunding. The long-standing record, which stood at $10,266,844 and was previously held by the ...
Ubuntu Edge smartphone sets crowdfunding record, but still $20m short of targetZDNet (blog)
FWIW, the Ubuntu Edge has become a crowdfunding record-breakerGigaOM
Ubuntu Edge Beats Pebble As Biggest Crowdfunding Campaign EverThe Next Web
Telegraph.co.uk -Fast Company -Wall Street Journal (blog)
all 15 news articles »
", "title": "Ubuntu Edge raises record-breaking $10 million, enters Guinness Book of Almost - Engadget", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNESqjZPnQK9UBZDKg08TYKYz6M25w&url=http://www.engadget.com/2013/08/16/ubuntu-edge-raises-10-million-dollars/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcQiOMnDRVkgj6013s35njtVHLefjF2xEb450UDf6AN1294HEz2O6phB9AmTAGhgpZMMuH6uPxw", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNESqjZPnQK9UBZDKg08TYKYz6M25w&url=http://www.engadget.com/2013/08/16/ubuntu-edge-raises-10-million-dollars/", "result_type": 0, "metadata": {"id": "googlenews:d00e7f6999673504da5edfa44d841b365e50f6b1", "language": "us", "published": "Fri, 16 Aug 2013 09:58:04 GMT"}}, {"category": 1, "comment": "
\"\"
SlashGear

\"\"
Glorious failure: Ubuntu Edge passes $10M on Indiegogo
VentureBeat
The goal is to create a novel device that dual-boots mobile Android and desktop Ubuntu, creating a smartphone that, with the help of a monitor and a keyboard, is also your heavy-duty, hardcore desktop computer. To make it happen, the Ubuntu Edge team ...
Ubuntu Edge appears \u201cabsolutely anti-ergonomic\u201d in the wild [UPDATE]SlashGear
Ubuntu 13.10 Release Date, Features: Overshadowed by Ubuntu Edge?The VAR Guy

all 6 news articles »
", "title": "Glorious failure: Ubuntu Edge passes $10M on Indiegogo - VentureBeat", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEUk6UIVyklb_FSxXYE_m0tXMcy4w&url=http://venturebeat.com/2013/08/14/glorious-failure-ubuntu-edge-passes-10m-on-indiegogo/", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcQ4D4bjkOuyLUoKADIxo-tTP00jgShwDC-5tSvc_uJQj1MPtTk9H6VZTWIcV0QfJTDiVoNPQBM", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEUk6UIVyklb_FSxXYE_m0tXMcy4w&url=http://venturebeat.com/2013/08/14/glorious-failure-ubuntu-edge-passes-10m-on-indiegogo/", "result_type": 0, "metadata": {"id": "googlenews:3237d68565b601a6267fd3a26ba141458ebd0447", "language": "us", "published": "Wed, 14 Aug 2013 20:32:31 GMT"}}, {"category": 1, "comment": "
\"\"
Ars Technica

\"\"
Why Ubuntu's creator still invests his fortune in an unprofitable company
Ars Technica
And although Shuttleworth wanted Canonical to be self-sustaining, he didn't threaten to abandon Ubuntu if it lost money. "When we started, I told the team two years," he recently told Ars. "I didn't say, 'till it's profitable.' I said, 'You can count ...
Canonical will win even if Ubuntu Edge doesn't make its $32 millionZDNet (blog)
Canonical On Why They Chose Indiegogo Over Kickstarter For The Ubuntu EdgeCrowdfund Insider

all 7 news articles »
", "title": "Why Ubuntu's creator still invests his fortune in an unprofitable company - Ars Technica", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGhhtaMlSt_jknKiEQT6sQ0QV4obQ&url=http://arstechnica.com/information-technology/2013/08/why-ubuntus-creator-still-invests-his-fortune-in-an-unprofitable-company/", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcTahT44weOAvDDW2cUEzPdLg70ERDj8lY4PFnn4sUP1_MhpaAaTDJ1K4L6Q8j_aiAM8t5He7nk", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGhhtaMlSt_jknKiEQT6sQ0QV4obQ&url=http://arstechnica.com/information-technology/2013/08/why-ubuntus-creator-still-invests-his-fortune-in-an-unprofitable-company/", "result_type": 0, "metadata": {"id": "googlenews:fb1aa4e74bcd8426f9415be30dc73eabf4261d41", "language": "us", "published": "Wed, 14 Aug 2013 13:30:52 GMT"}}, {"category": 1, "comment": "
\"\"
San Francisco Chronicle (blog)

\"\"
Sean O'Toole claims Napa's former Ubuntu, will turn it into Torc
San Francisco Chronicle (blog)
As Sean O'Toole puts it, the running joke with his chef friends is how an Irishman like himself learned to cook. So, as O'Toole \u2014 who has cooked at Bardessono, Quince, the Mina Group and most recently Hopper Creek Kitchen \u2014 ramps up his first solo ...
Ubuntu Space In Napa Will Reopen As Torc This FallSFist

all 3 news articles »
", "title": "Sean O'Toole claims Napa's former Ubuntu, will turn it into Torc - San Francisco Chronicle (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGCOJqCV_4lg4mjJnbKNsTLVu75Og&url=http://insidescoopsf.sfgate.com/blog/2013/08/14/sean-otoole-claims-napas-former-ubuntu-will-turn-it-into-torc/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcRhkFIqDtblqPNEQUpHPoEuT5DZXNjhkbrEcLn6JF8oOT6XHjZG82ZiYWh8xux6QDCezUqd8A9d", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGCOJqCV_4lg4mjJnbKNsTLVu75Og&url=http://insidescoopsf.sfgate.com/blog/2013/08/14/sean-otoole-claims-napas-former-ubuntu-will-turn-it-into-torc/", "result_type": 0, "metadata": {"id": "googlenews:69bd0cf9a5dfe84dc0bb5d7277fe763e7e14c9d7", "language": "us", "published": "Wed, 14 Aug 2013 19:48:53 GMT"}}, {"category": 1, "comment": "
\"\"
Technology Zimbabwe

\"\"
You should give Ubuntu and Steam a chance: Evaluation and Howto.
Technology Zimbabwe
ubuntu-steam Linux has over the years consistently proven itself to be a worthwhile alternative on the desktop and has even become the champion in mobile space in the form of Android. For a long time though its inability to bring about the much ...

", "title": "You should give Ubuntu and Steam a chance: Evaluation and Howto. - Technology Zimbabwe", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFUD-j42bkux9fhgwxUsG7C-c1rog&url=http://www.techzim.co.zw/2013/08/you-should-give-ubuntu-and-steam-a-chance-evaluation-and-howto/", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcS3jgPpfKAx-ZauufFp6Owlh82AooW_cd8jLPAfhB3a2H1cQg6ia84OfbcuxTu_ArRL790hWdlF", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFUD-j42bkux9fhgwxUsG7C-c1rog&url=http://www.techzim.co.zw/2013/08/you-should-give-ubuntu-and-steam-a-chance-evaluation-and-howto/", "result_type": 0, "metadata": {"id": "googlenews:47ae12ee6f89fc6e48cb0b7b857b2cb7f4e38ea3", "language": "us", "published": "Fri, 16 Aug 2013 09:47:37 GMT"}}, {"category": 1, "comment": "

\"\"
Ubuntu Lockscreen Adds Notifications and Ubuntu's Looks to Android
Lifehacker
Android: If you like the look of Ubuntu Touch, or just Ubuntu's overall color scheme, Ubuntu Lockscreen can turn your boring old slide-to-unlock into something a bit more useful and fun to use. The app supports app notifications, SMS alerts, password ...

", "title": "Ubuntu Lockscreen Adds Notifications and Ubuntu's Looks to Android - Lifehacker", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF_C1VFQ6047TZRTEywUGQqWmT63Q&url=http://lifehacker.com/ubuntu-lockscreen-adds-notifications-and-ubuntus-looks-1084146843", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF_C1VFQ6047TZRTEywUGQqWmT63Q&url=http://lifehacker.com/ubuntu-lockscreen-adds-notifications-and-ubuntus-looks-1084146843", "result_type": 0, "metadata": {"id": "googlenews:00d4ad9b7909e2b713c1b917ea749865a2fecf84", "language": "us", "published": "Mon, 12 Aug 2013 19:39:49 GMT"}}, {"category": 1, "comment": "

\"\"
Ubuntu Phone Seeks To Be Crowd-Funded -- for $32 Million
Sci-Tech Today
ne of the most interesting aspects of the Ubuntu Edge is that it will run both Linux and Android, instead of solely running Linux like you would expect it to. To start, people running the phone in Android mode will access Ubuntu through the Ubuntu for ...

", "title": "Ubuntu Phone Seeks To Be Crowd-Funded -- for $32 Million - Sci-Tech Today", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFasMFRRtQPkjQjbTeSjdXmQhG3SQ&url=http://www.sci-tech-today.com/story.xhtml?story_id%3D03200001OXQ8", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNFasMFRRtQPkjQjbTeSjdXmQhG3SQ&url=http://www.sci-tech-today.com/story.xhtml?story_id%3D03200001OXQ8", "result_type": 0, "metadata": {"id": "googlenews:efed162137c9f8d74d419a3dd2a18f870bd4a4c3", "language": "us", "published": "Thu, 15 Aug 2013 07:02:12 GMT"}}, {"category": 1, "comment": "

\"\"
Ubuntu Edge Price Drops Again, Canonical Now Needs Even More Backers To ...
TechCrunch
The Ubuntu Edge, the smartphone made by UK-based Canonical, has received a second price drop in its $32 million crowdfunding campaign. For the remaining 14 days of its audacious Indiegogo campaign, the Edge will now cost $695 instead of its full price ...

", "title": "Ubuntu Edge Price Drops Again, Canonical Now Needs Even More Backers To ... - TechCrunch", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHPfow4-3cU0dBkq8bVO11zrOQEzg&url=http://techcrunch.com/2013/08/08/ubuntu-edge-price-drops-to-695-canonical-now-needs-even-more-backers/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHPfow4-3cU0dBkq8bVO11zrOQEzg&url=http://techcrunch.com/2013/08/08/ubuntu-edge-price-drops-to-695-canonical-now-needs-even-more-backers/", "result_type": 0, "metadata": {"id": "googlenews:3cfa43c419d885f8c2d142f9d9effa853328432c", "language": "us", "published": "Thu, 08 Aug 2013 15:07:53 GMT"}}, {"category": 1, "comment": "
\"\"
Telegraph.co.uk

\"\"
Crowdfunded Ubuntu Phone Will Be A Big Success, Even If It Never Happens
Forbes
Another day, another healthy dose of press attention for the Ubuntu Edge. This non-existent smartphone is still just a glint in the eye of its originator, billionaire and Forbes disruptor Mark Shuttleworth, but it's back in the headlines after ...
On the Edge of failure: Ubuntu smartphone looks unlikely to reach crowdfunding ...The Verge
Ubuntu Edge looks doomed, but Canonical cuts cost of making the phoneArs Technica
Ubuntu Edge crowdfunding campaign 'doomed to fail'Telegraph.co.uk
CNET -ZDNet (blog) -V3.co.uk
all 99 news articles »
", "title": "Crowdfunded Ubuntu Phone Will Be A Big Success, Even If It Never Happens - Forbes", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHsYKmQYS-EfAZ8l-4H2-gzIKlmHQ&url=http://www.forbes.com/sites/sharifsakr/2013/08/08/crowdfunded-ubuntu-phone-will-be-a-big-success-even-if-it-never-happens/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcQiOMnDRVkgj6013s35njtVHLefjF2xEb450UDf6AN1294HEz2O6phB9AmTAGhgpZMMuH6uPxw", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHsYKmQYS-EfAZ8l-4H2-gzIKlmHQ&url=http://www.forbes.com/sites/sharifsakr/2013/08/08/crowdfunded-ubuntu-phone-will-be-a-big-success-even-if-it-never-happens/", "result_type": 0, "metadata": {"id": "googlenews:dc43c8092939e2859271459553e33127f8a2aa89", "language": "us", "published": "Thu, 08 Aug 2013 18:31:56 GMT"}}, {"category": 1, "comment": "

\"\"
This Week On The TC Gadgets Podcast: Ubuntu Edge, Nexus Rumors, Beddit ...
TechCrunch
The Ubuntu Edge may be the future of the smartphone, but does it have a future? Nexus rumors abound for the next generation of Google's vanilla tablets and smartphones. Meanwhile, startups are hitting up Indiegogo with new quantified self devices, ...

", "title": "This Week On The TC Gadgets Podcast: Ubuntu Edge, Nexus Rumors, Beddit ... - TechCrunch", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHRSqyvQ-wLgeAWbevREt6CjdG_dw&url=http://techcrunch.com/2013/08/09/this-week-on-the-tc-gadgets-podcast-ubuntu-edge-nexus-rumors-beddit-and-trace-tracker/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHRSqyvQ-wLgeAWbevREt6CjdG_dw&url=http://techcrunch.com/2013/08/09/this-week-on-the-tc-gadgets-podcast-ubuntu-edge-nexus-rumors-beddit-and-trace-tracker/", "result_type": 0, "metadata": {"id": "googlenews:a0b3c7ab70d14a872d03fd005e1b73936fc2a8e1", "language": "us", "published": "Fri, 09 Aug 2013 19:03:27 GMT"}}, {"category": 1, "comment": "

\"\"
Install Classic Menu Indicator in Ubuntu 13.04, 12.10 or 12.04
ITworld.com
The Classic Menu Indicator brings back a fast way to access applications and tools in Ubuntu. After using the Classic Menu Indicator, it just reminds me of how much I still dislike Unity. It seems so much easier and faster to get to what I want in Ubuntu.

", "title": "Install Classic Menu Indicator in Ubuntu 13.04, 12.10 or 12.04 - ITworld.com", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGunsUsWRm0zi7CHRF1aew0Uz0gzQ&url=http://www.itworld.com/software/369000/install-classic-menu-indicator-ubuntu-1304-1210-or-1204", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGunsUsWRm0zi7CHRF1aew0Uz0gzQ&url=http://www.itworld.com/software/369000/install-classic-menu-indicator-ubuntu-1304-1210-or-1204", "result_type": 0, "metadata": {"id": "googlenews:f2e7c63a284849d1f84213968cdc6522f6c7820d", "language": "us", "published": "Tue, 13 Aug 2013 22:20:36 GMT"}}, {"category": 1, "comment": "
\"\"
The Guardian

\"\"
Ubuntu Edge: Crowdfunding a Super-Smartphone
Businessweek
Alexey Miller, the chief executive officer of Russian natural gas exporter Gazprom, seemed to be having a megalomaniacal moment. On July 16 he took to the company's website and demanded a tablet computer that could mimic all of the functions of a PC.
Ubuntu Edge will miss $32m crowdfunding target, say researchersThe Guardian
T-Mobile backs Ubuntu smartphoneZDNet (blog)
T-Mobile joins effort to bring Ubuntu phones to US mobile marketRegister
InfoWorld (blog) -TmoNews
all 15 news articles »
", "title": "Ubuntu Edge: Crowdfunding a Super-Smartphone - Businessweek", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHTN1jf42crYcZYbQJJ0n-CFy0ESg&url=http://www.businessweek.com/articles/2013-08-01/ubuntu-edge-crowdfunding-a-super-smartphone", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcRQQgBZwmzsOS5Q0rRmZdf18akvkEZIlSl7CiLMd3kz_LXwfjfFUk0HNs9MSWf5DGGftAk0_u5a", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHTN1jf42crYcZYbQJJ0n-CFy0ESg&url=http://www.businessweek.com/articles/2013-08-01/ubuntu-edge-crowdfunding-a-super-smartphone", "result_type": 0, "metadata": {"id": "googlenews:0d41a66c2b7d2dcb9fe9346e55413f6340ea8334", "language": "us", "published": "Fri, 02 Aug 2013 20:55:37 GMT"}}, {"category": 1, "comment": "

\"\"
Ubuntu Edge: Opening a Window to Computing's Future
Kioskea
Canonical, Ubuntu's parent company, wagered that there were enough visionaries in the world to crowd-source a whopping $32 million to develop a Linux-powered combination smartphone/PC, the Ubuntu Edge; it looks like Canonical is going to lose that bet ...

", "title": "Ubuntu Edge: Opening a Window to Computing's Future - Kioskea", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGWpK6rGouam9zZUQvpdnAZP13TOA&url=http://en.kioskea.net/news/23943-ubuntu-edge-opening-a-window-to-computing-s-future", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGWpK6rGouam9zZUQvpdnAZP13TOA&url=http://en.kioskea.net/news/23943-ubuntu-edge-opening-a-window-to-computing-s-future", "result_type": 0, "metadata": {"id": "googlenews:777635626831a109cb26d53255e64eb922577f77", "language": "us", "published": "Wed, 14 Aug 2013 19:10:14 GMT"}}, {"category": 1, "comment": "
\"\"
ZDNet (blog)

\"\"
Ubuntu: One OS, one interface, all devices
ZDNet (blog)
It's only now that this plan is coming into focus for those who don't follow Ubuntu like a hawk. As Mark Shuttleworth, Canonical and Ubuntu's founder said at OSCon, the major open-source convention held in Portland, OR, "Convergence is the core story.
Ubuntu Edge: the best smartphone you'll never ownExpert Reviews
Ubuntu Edge: A case of mistaken identityTechRepublic (blog)
Ubuntu Edge is a smartphone and desktop computer in oneGizmag
ITworld.com -Network World -InfoWorld
all 50 news articles »
", "title": "Ubuntu: One OS, one interface, all devices - ZDNet (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGvAbq_FPlK16hFTcTKHiodB1Ou8g&url=http://www.zdnet.com/ubuntu-one-os-one-interface-all-devices-7000018613/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcQt0r7R2ilMW0S0XSgnhpJv1iZBxkk8ca_IrTx9JCxEvQu0mF1tWBU0fCFwHUpUtb2PCoDKgx57", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGvAbq_FPlK16hFTcTKHiodB1Ou8g&url=http://www.zdnet.com/ubuntu-one-os-one-interface-all-devices-7000018613/", "result_type": 0, "metadata": {"id": "googlenews:b084cbeaacf6a200807be6128687cd27187084a6", "language": "us", "published": "Fri, 26 Jul 2013 18:55:23 GMT"}}, {"category": 1, "comment": "
\"\"
ExtremeTech

\"\"
SOL: The $350 Ubuntu laptop that runs on solar power
Gizmag
... a rugged laptop that doesn't rely on a power socket to stay charged. The Ubuntu Linux-powered computer is instead equipped with a detachable solar panel, which the developers claim will provide up to 10 hours of battery life after just two hours in ...
Solar-powered Ubuntu laptop boasts 10-hour battery, 2-hour charge timeEngadget
Sol, the $350 solar-powered rugged Ubuntu laptop that won't be usable in the sunExtremeTech
Penguins, I give you: The SOLAR-POWERED Ubuntu laptopRegister
Network World -Geek -LAPTOP Magazine (blog)
all 26 news articles »
", "title": "SOL: The $350 Ubuntu laptop that runs on solar power - Gizmag", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEdhOatSjUe3e7EjvwHUvjJql5mRg&url=http://www.gizmag.com/sol-solar-powered-ubuntu-laptop/28611/", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcTeYFiBF44Pt-Jk5fPDVKLHGO8lfduNIL1gVzWKDzsp3hvMlgZg0J2MfuUj3aKn1hPscSfHg4U", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEdhOatSjUe3e7EjvwHUvjJql5mRg&url=http://www.gizmag.com/sol-solar-powered-ubuntu-laptop/28611/", "result_type": 0, "metadata": {"id": "googlenews:9692c57aae0a7e193b70ebfe5b24aaa5015141ce", "language": "us", "published": "Wed, 07 Aug 2013 10:12:01 GMT"}}, {"category": 1, "comment": "
\"\"
Wired

\"\"
Ubuntu Superphone Fundraiser Rakes in $1M on Debut
Wired
Canonical \u2014 makers of the Ubuntu distribution of Linux \u2014 is asking geeks to collectively cough up $32 million dollars in the next month to crowdfund a smartphone that could serve as a laptop replacement. Supporters have already pledged over $1 ...
Can the internet raise $32 million to build the Ubuntu Edge smartphone?The Verge
Ubuntu Edge smartphone announced with $32 million Indiegogo campaign ...Engadget
10 criteria Ubuntu Edge must meet if it's going to succeedTechRepublic (blog)
ReadWrite -CNET (blog) -The Next Web
all 215 news articles »
", "title": "Ubuntu Superphone Fundraiser Rakes in $1M on Debut - Wired", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEeKBtS3WLKVftIRfV_-zt8kq5wxg&url=http://www.wired.com/wiredenterprise/2013/07/ubuntu-laptop-replacement/", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcSg0qTlxXvGlvVNzkxYBNeVG-9LcCMLy58V2qWqGVegoIZ3nWfj34Wbv1YEGRMOdN1EQpkUcASR", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEeKBtS3WLKVftIRfV_-zt8kq5wxg&url=http://www.wired.com/wiredenterprise/2013/07/ubuntu-laptop-replacement/", "result_type": 0, "metadata": {"id": "googlenews:1c1f190b1ed226fe3832279f21aa5883630f5100", "language": "us", "published": "Mon, 22 Jul 2013 19:58:38 GMT"}}, {"category": 1, "comment": "

\"\"
LinuxMint 15 delivers smooth alternative to Ubuntu
Network World
Network World - The crafters of the LinuxMint distro are in a ticklish position. Mint is based on Ubuntu, which in turn, is based on Debian, which in turn, has the moveable feast of the Linux kernel as its underpinning. All three have changed ...

", "title": "LinuxMint 15 delivers smooth alternative to Ubuntu - Network World", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEl5QpZ7jD-R-mQbp5dNRm54ceWZA&url=http://www.networkworld.com/reviews/2013/080513-linux-mint-test-272497.html", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEl5QpZ7jD-R-mQbp5dNRm54ceWZA&url=http://www.networkworld.com/reviews/2013/080513-linux-mint-test-272497.html", "result_type": 0, "metadata": {"id": "googlenews:c9a8382d28f31b0f3a02a48f1c643dbc91e213f9", "language": "us", "published": "Mon, 05 Aug 2013 10:09:40 GMT"}}, {"category": 1, "comment": "
\"\"
ZDNet (blog)

\"\"
Ubuntu Edge might just change the computing world
ZDNet (blog)
Mark Shuttleworth, founder of Ubuntu and its parent company, Canonical, is making a bet with the technology market. He's betting that enough of you will be willing to invest in a smartphone that can double as a PC, the Ubuntu Edge, to raise the $32 ...
If The Ubuntu Edge Crowdfunding Experiment Works, Backers May Get To Vote ...TechCrunch
Ultimate goal for Ubuntu Edge phone may go beyond crowdfunding campaignTechHive
Ubuntu Edge crowdfunding campaign adds lower price points as pledges slowThe Verge
ExtremeTech -Telegraph.co.uk -Ars Technica
all 66 news articles »
", "title": "Ubuntu Edge might just change the computing world - ZDNet (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEthn1_384NjulGKKdKdxZ9fDNwHg&url=http://www.zdnet.com/ubuntu-edge-might-just-change-the-computing-world-7000018464/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcSJNH5EumTYhixukNj5r4K5Xo6B-4VslhUQT5SoJj3JIAODEHrO_VKVAMKs6r5uElg8UjoGlyU", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEthn1_384NjulGKKdKdxZ9fDNwHg&url=http://www.zdnet.com/ubuntu-edge-might-just-change-the-computing-world-7000018464/", "result_type": 0, "metadata": {"id": "googlenews:2b1098c469e7c8f90732ee1abae7c3f6046b7b40", "language": "us", "published": "Wed, 24 Jul 2013 12:10:20 GMT"}}, {"category": 1, "comment": "

\"\"
Ubuntu derivatives: 5 of the best Ubuntu-based distros
TechRadar UK
Are you a Linux desktop user who loves Ubuntu but is wary of Unity? You're in luck. There are lots of Ubuntu spins, both from Canonical and independent developers, which preserve the basic infrastructure and essence of Ubuntu but replace the default ...

", "title": "Ubuntu derivatives: 5 of the best Ubuntu-based distros - TechRadar UK", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEpkk7YOWJ4iG2gfc_yLGz2rX4eSg&url=http://www.techradar.com/news/software/operating-systems/ubuntu-derivatives-5-of-the-best-ubuntu-based-distros-1170314", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEpkk7YOWJ4iG2gfc_yLGz2rX4eSg&url=http://www.techradar.com/news/software/operating-systems/ubuntu-derivatives-5-of-the-best-ubuntu-based-distros-1170314", "result_type": 0, "metadata": {"id": "googlenews:63d19c590533797de7a086a07aa76e39cbd132ae", "language": "us", "published": "Sun, 04 Aug 2013 09:08:19 GMT"}}, {"category": 1, "comment": "
\"\"
PhoneDog

\"\"
T-Mobile follows Verizon in supporting Ubuntu through Carrier Advisory Group
FierceWireless
T-Mobile US (NYSE:TMUS) joined fellow U.S. carrier Verizon Wireless (NYSE:VZ) in the Ubuntu Carrier Advisory Group, a consortium of mobile operators participating in technology discussions around the Linux-based operating system. The 13-member ...
The five best distros based on UbuntuITworld.com
T-Mobile becomes second US carrier to join Ubuntu Carrier Advisory GroupPhoneDog
Ubuntu Edge smartphone crowdfunding effort 'destined to fail'ITProPortal
The VAR Guy -Phones Review
all 13 news articles »
", "title": "T-Mobile follows Verizon in supporting Ubuntu through Carrier Advisory Group - FierceWireless", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHR0i2v3rd5WIihnpS8nSLFgZRP8A&url=http://www.fiercewireless.com/story/t-mobile-follows-verizon-supporting-ubuntu-through-carrier-advisory-group/2013-08-05", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcShWoIjktvq3gWOBX4FX6Cs2AlTBaInUqahSFdWOtOsO0YvC1MGSuqwUrQVL7DooarwpGgFA8I6", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHR0i2v3rd5WIihnpS8nSLFgZRP8A&url=http://www.fiercewireless.com/story/t-mobile-follows-verizon-supporting-ubuntu-through-carrier-advisory-group/2013-08-05", "result_type": 0, "metadata": {"id": "googlenews:94154d571a1559a9d9240fe66381a3ea02c67d8b", "language": "us", "published": "Mon, 05 Aug 2013 16:20:21 GMT"}}, {"category": 1, "comment": "
\"\"
Mobile World Live

\"\"
Canonical revamps Ubuntu Edge prices; funding miss nears
Mobile World Live
Canoncial revamped (again) the funding for an Ubuntu Edge smartphone as part of its Indiegogo crowd funding programme, as it looked increasingly likely the company will miss its $32 million target. The company fixed the price of a device at $695 ...

and more »
", "title": "Canonical revamps Ubuntu Edge prices; funding miss nears - Mobile World Live", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEnNpfyekdePybyEjbt1TY8-prR6g&url=http://www.mobileworldlive.com/canonical-revamps-ubuntu-edge-prices-funding-miss-nears", "icon_hint": "https://t0.gstatic.com/images?q=tbn:ANd9GcS8kKCTHkrCixmmo2pBXNMcfcBf7PfrTshCb6pAFf68QwbhLUG9_FvZSEyTnKQkQrymlAxicN2k", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNEnNpfyekdePybyEjbt1TY8-prR6g&url=http://www.mobileworldlive.com/canonical-revamps-ubuntu-edge-prices-funding-miss-nears", "result_type": 0, "metadata": {"id": "googlenews:555e8d1cde9da3b01a6205c104902cce51d767e2", "language": "us", "published": "Mon, 12 Aug 2013 16:24:47 GMT"}}, {"category": 1, "comment": "

\"\"
Ubuntu puts forums back online, reveals autopsy of a brag hacker
Register
Only the forums and not the popular Ubuntu Linux distribution nor any Canonical or Ubuntu services, namely Ubuntu One and Launchpad, were affected. "We have repaired and hardened the Ubuntu Forums, and as the problematic settings are the default ...

", "title": "Ubuntu puts forums back online, reveals autopsy of a brag hacker - Register", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHfDylX2l5ytPZoisQmZV7Jmi69EA&url=http://www.theregister.co.uk/2013/08/02/ubuntu_forum_hack_postmortem/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNHfDylX2l5ytPZoisQmZV7Jmi69EA&url=http://www.theregister.co.uk/2013/08/02/ubuntu_forum_hack_postmortem/", "result_type": 0, "metadata": {"id": "googlenews:a829bab96f46e7fd73c41803321e93510629efc6", "language": "us", "published": "Fri, 02 Aug 2013 10:20:24 GMT"}}, {"category": 1, "comment": "

\"\"
Canonical Seeks $32M To Build A Ubuntu Smartphone Designed For ...
TechCrunch
Ubuntu Mobile isn't new; Canonical has already done much to promote its efforts to break into the smartphone mobile software space. But today the company is launching an Indiegogo campaign to fund the development of its first own-branded Ubuntu mobile ...

", "title": "Canonical Seeks $32M To Build A Ubuntu Smartphone Designed For ... - TechCrunch", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF1IZYYGzcWfj_HGOH7wZZLmE6Osg&url=http://techcrunch.com/2013/07/22/ubuntu-smartphone-canonical/", "icon_hint": "/usr/share/icons/unity-icon-theme/places/svg/result-news.svg", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNF1IZYYGzcWfj_HGOH7wZZLmE6Osg&url=http://techcrunch.com/2013/07/22/ubuntu-smartphone-canonical/", "result_type": 0, "metadata": {"id": "googlenews:e8beafb50f2802e50ff5d06b99fcfa4edb927c4b", "language": "us", "published": "Mon, 22 Jul 2013 15:27:03 GMT"}}, {"category": 1, "comment": "
\"\"
ReadWrite

\"\"
Ubuntu Edge: A Device In Serious Need Of A Community
ReadWrite
Canonical, the company behind Ubuntu, hopes, however, that this funding campaign will help to birth a device that appeals to an audience far bigger than the Ubuntu desktop community, as ReadWrite's Dan Rowinski reports. Mark Shuttleworth, Canonical's ...

", "title": "Ubuntu Edge: A Device In Serious Need Of A Community - ReadWrite", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGT2m-wUiNJ0IPW4NpsvLEkrnkNBg&url=http://readwrite.com/2013/07/24/ubuntu-edge-kickstarter-community", "icon_hint": "https://t1.gstatic.com/images?q=tbn:ANd9GcRToB2758WYlQ15uBzoMI6A7SgXImjqL-6FOf1SRjrEfF1AWE4OOPqWhzDQ6aQIrqlgp_ZgMK8", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGT2m-wUiNJ0IPW4NpsvLEkrnkNBg&url=http://readwrite.com/2013/07/24/ubuntu-edge-kickstarter-community", "result_type": 0, "metadata": {"id": "googlenews:47c930461fd555fcce71358e59db46955ac14040", "language": "us", "published": "Wed, 24 Jul 2013 13:32:25 GMT"}}, {"category": 1, "comment": "
\"\"
GSMArena.com (blog)

\"\"
Canonical lowers Ubuntu Edge price to $695 on Indiegogo
GSMArena.com (blog)
Canonical's crowd-funding project, aiming to raise $32 million in order to make its Ubuntu Edge device a reality was off to a fast start, making $2 million in the first 8 hours, but it slowed down shortly after and it seems it stands little chance it ...

and more »
", "title": "Canonical lowers Ubuntu Edge price to $695 on Indiegogo - GSMArena.com (blog)", "uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGBbp9WGu-Px2JC_vdZTD3RnVlaVw&url=http://blog.gsmarena.com/canonical-lowering-ubuntu-edge-price-to-695-in-effort-to-close-out-next-two-weeks-on-a-roll/", "icon_hint": "https://t2.gstatic.com/images?q=tbn:ANd9GcTUZELjTeLPc2jxUVDhSooRtb4MkhTnNMocey5UmwYHgkvg6tqRsD_kCpfcE29EoKosYFuZcSDq", "mimetype": "text/html", "dnd_uri": "http://news.google.com/news/url?sa=t&fd=R&usg=AFQjCNGBbp9WGu-Px2JC_vdZTD3RnVlaVw&url=http://blog.gsmarena.com/canonical-lowering-ubuntu-edge-price-to-695-in-effort-to-close-out-next-two-weeks-on-a-roll/", "result_type": 0, "metadata": {"id": "googlenews:cbada1a0b404bbae38e1252d243f5e98eeec6902", "language": "us", "published": "Thu, 08 Aug 2013 14:09:14 GMT"}}]}, "type": "results"} {"info": {"info-ddg_related.scope": [{"comment": "Ubuntu (operating system), a Linux-based computer operating system", "metadata": {"category": "Reference", "images": {"null": ["https://i.duckduckgo.com/i/d9f41dc8.png"]}, "id": "ddg_related:143ffe8f4eb31b2c098485fe7f2fa9c8"}, "icon_hint": "https://i.duckduckgo.com/i/d9f41dc8.png", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=ZvtNmNApOMctGaSPxQ1dVzlx77VLezOZFMWpjA%3D%3D&u=http%3A%2F%2Fduckduckgo.com%2FUbuntu_%28operating_system%29", "title": "Ubuntu (operating system)"}]}, "type": "results"} {"info": {"more_suggestions-u1ms.scope": [{"metadata": {"category": "music", "price": "2.49", "currency": "EUR", "formatted_price": "2,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_175.jpg"]}, "id": "U1MS:7digital:album:1701891:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/017/018/0001701891_350.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=Uj4ZHTKPUhckGEL0r923r32qoH2c_ls_D3IYXw%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1701891", "title": "Clockwork Radio - Ubuntu [2012]"}, {"metadata": {"category": "music", "price": "3.96", "currency": "EUR", "formatted_price": "3,96 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_175.jpg"]}, "id": "U1MS:7digital:album:1799831:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/017/998/0001799831_350.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=urvCuvVILgGMukd6WjHmMDydD-U1-n5xYRq7CQ%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1799831", "title": "Olefonken - Ubuntu Tutu [2012]"}, {"metadata": {"category": "music", "price": "8.99", "currency": "EUR", "formatted_price": "8,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_175.jpg"]}, "id": "U1MS:7digital:album:2074435:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/020/744/0002074435_350.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=7lpI7UTes9Y8biUhIsKgVV6b7VDjvOIQCbnskA%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F2074435", "title": "Victoria Falls Ubuntu Bomuntu - Ubuntu Bethu (Our Humanity) [2012]"}, {"metadata": {"category": "music", "price": "9.79", "currency": "EUR", "formatted_price": "9,79 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_175.jpg"]}, "id": "U1MS:7digital:album:959972:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/009/599/0000959972_350.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=YsvIq_GdSL0XJmfTzk-LChm2xFgIRxa1Wi9XZw%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F959972", "title": "Sol-G - Ubuntu [2010]"}, {"metadata": {"category": "music", "price": "8.99", "currency": "EUR", "formatted_price": "8,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_175.jpg"]}, "id": "U1MS:7digital:album:1413417:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/014/134/0001413417_350.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=3iY1qU_dA6YAjd4tOt-SafwkfG-xY5aOWbkzpA%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1413417", "title": "Sipho Gumede - Ubuntu - Humanity [2005]"}, {"metadata": {"category": "music", "price": "8.91", "currency": "EUR", "formatted_price": "8,91 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_175.jpg"]}, "id": "U1MS:7digital:album:1767506:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/017/675/0001767506_350.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=TOqVb_Ae4S3yn0rdTz86n5yUeFQFti3MjmBcuQ%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1767506", "title": "Lady Ubuntu - Piuttosto Che Incontrarvi Farei Bungee Jumping [2012]"}]}, "type": "results"} {"info": {"reference-wikipedia.scope": [{"comment": "", "metadata": {"category": "wikipedia", "images": {"null": ["file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg"]}, "id": "WIKIPEDIA:Ubuntu_(operating_system)"}, "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg", "uri": "scopes-query://wiki:Debian", "title": "Did you mean 'Debian'?"}, {"comment": "Ubuntu One is a cloud service and Open ID-based single sign on service operated by Canonical Ltd to allow users to store data within the cloud and to log onto many Canonical-owned websites.\n", "metadata": {"category": "wikipedia", "images": {"null": ["file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg"]}, "id": "WIKIPEDIA:Ubuntu_One"}, "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=2gf9DWvyJtILHWcnSYQG-cXt6jHFXHOhXB07HA%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FUbuntu_One", "title": "Ubuntu One"}, {"comment": "Ubuntu Netbook Edition (UNE), known as Ubuntu Netbook Remix (UNR) prior to the release of Ubuntu 10.04, was a version of Ubuntu that had been optimized to enable it to work better on netbooks and other devices with small screens or with the Intel Atom CPU.\n", "metadata": {"category": "wikipedia", "images": {"96x72": ["http://upload.wikimedia.org/wikipedia/en/thumb/e/ef/Asus_Eee_701.jpg/96px-Asus_Eee_701.jpg"]}, "id": "WIKIPEDIA:Ubuntu_Netbook_Edition"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/en/thumb/e/ef/Asus_Eee_701.jpg/96px-Asus_Eee_701.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=YSBa_JRAJp6Jo5nndlwhE1NDZVOxI9CxvDanXw%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FUbuntu_Netbook_Edition", "title": "Ubuntu Netbook Edition"}, {"comment": "Ubuntu releases are made semiannually by Canonical Ltd, the developers of the Ubuntu operating system, using the year and month of the release as a version number. ", "metadata": {"category": "wikipedia", "images": {"96x59": ["http://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Ubuntu_13.04_Desktop.png/96px-Ubuntu_13.04_Desktop.png"]}, "id": "WIKIPEDIA:List_of_Ubuntu_releases#Ubuntu_11.10_.28Oneiric_Ocelot.29"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Ubuntu_13.04_Desktop.png/96px-Ubuntu_13.04_Desktop.png", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=WAuL7zZQbDZSsjlZJ40li00Vx9LAARoVjypYTA%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FList_of_Ubuntu_releases%23Ubuntu_11.10_.28Oneiric_Ocelot.29", "title": "List of Ubuntu releases"}, {"comment": "Ubuntu Font Family is an OpenType font, designed to be a modern humanist-style font by London-based type foundry Dalton Maag, with funding by Canonical Ltd. ", "metadata": {"category": "wikipedia", "images": {"96x114": ["http://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Ubuntu.svg/96px-Ubuntu.svg.png"]}, "id": "WIKIPEDIA:Ubuntu_(typeface)"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Ubuntu.svg/96px-Ubuntu.svg.png", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=xZwGNZi_2X-6zMYqEI4086pyi8Kixx1jh6e9FA%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FUbuntu_%28typeface%29", "title": "Ubuntu (typeface)"}, {"comment": "Ubuntu Studio is an officially recognized derivative of the Ubuntu Linux distribution, which is explicitly geared to general multimedia production. ", "metadata": {"category": "wikipedia", "images": {"96x72": ["http://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Ubuntu_studio_login_screen.png/96px-Ubuntu_studio_login_screen.png"]}, "id": "WIKIPEDIA:Ubuntu_Studio"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/commons/thumb/9/91/Ubuntu_studio_login_screen.png/96px-Ubuntu_studio_login_screen.png", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=bvUq-6QRGGSCq57ETjg6fo_4eQy_RYpm2NXC0w%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FUbuntu_Studio", "title": "Ubuntu Studio"}, {"comment": "Ubuntu for Android, currently under development, is a free and open source variant of Ubuntu designed to run on Android phones. ", "metadata": {"category": "wikipedia", "images": {"null": ["file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg"]}, "id": "WIKIPEDIA:Ubuntu_for_Android"}, "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=X3VFn68hKwGnGi1bMSA24v0n-sjuPdwvId_nsg%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FUbuntu_for_Android", "title": "Ubuntu for Android"}]}, "type": "results"} unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/samples/search_results1.txt0000644000015600001650000004330512657432773030346 0ustar pbuserpbgroup00000000000000{"scopes": [["more_suggestions-amazon.scope", "server"], ["more_suggestions-skimlinks.scope", "server"], ["more_suggestions-u1ms.scope", "server"], ["reference-wikipedia.scope", "server"], ["reference-dictionary.scope", "server"], ["reference-synonym.scope", "server"], ["reference-antonym.scope", "server"]], "type": "recommendations"} {"info": {"more_suggestions-amazon.scope": []}, "type": "results"} {"info": {"reference-dictionary.scope": []}, "type": "results"} {"info": {"more_suggestions-u1ms.scope": [{"metadata": {"category": "music", "price": "8.99", "currency": "EUR", "formatted_price": "8,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_175.jpg"]}, "id": "U1MS:7digital:album:1726287:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=3hoHNgE4OezJ5_H9I-eTh5YYj-UqFkMlSC8jSA%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1726287", "title": "Metallica - Metallica - Live in Moscow [2012]"}, {"metadata": {"category": "music", "price": "8.99", "currency": "EUR", "formatted_price": "8,99 \u20ac", "images": {"350x350": [""], "182x182": [""], "50x50": [""], "100x100": [""], "180x180": [""], "33x33": [""], "52x52": [""], "75x75": [""], "200x200": [""], "175x175": [""]}, "id": "U1MS:7digital:album:1864502:EU"}, "icon_hint": "", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=KBp7PtNQMrGQpNEuCoczqUtzpcS_4rQhUwF6Ww%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1864502", "title": "Metallica - Metallica - Live in Moscow - Volume 2 [2012]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_175.jpg"]}, "id": "U1MS:7digital:album:242383:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=Jk0CFKBMG3hOsSqADavD6qJ1zlyMVnDqSE2PGg%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F242383", "title": "Rockabye Baby! - Lullaby Renditions of Metallica [2007]"}, {"metadata": {"category": "music", "price": "7.99", "currency": "EUR", "formatted_price": "7,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_175.jpg"]}, "id": "U1MS:7digital:album:95490:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=jW7uZRdULlLsF6BQMHmKW3pq3mkAImXFkB8hEQ%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F95490", "title": "Various Artists - A Punk Tribute To Metallica [2006]"}, {"metadata": {"category": "music", "price": "7.99", "currency": "EUR", "formatted_price": "7,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_175.jpg"]}, "id": "U1MS:7digital:album:141050:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=sLQKdt4C-wseOKTS8JMooJvBsXWe4yef8qHMWA%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F141050", "title": "Various Artists - A Metal Tribute To Metallica [2000]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_175.jpg"]}, "id": "U1MS:7digital:album:180026:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=VZLpF7hX9NbSCSrAPRJXv5mazQrm66k8YRz25A%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F180026", "title": "Various Artists - The Blackest Box - The Ultimate Metallica Tribute [2007]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_175.jpg"]}, "id": "U1MS:7digital:album:200025:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=cxMQBVJve_3Qjr-EF3O1Y1IOLx6-SuSsiyTTAg%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F200025", "title": "Various Artists - Metallic Assault - A Tribute to Metallica [2006]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_175.jpg"]}, "id": "U1MS:7digital:album:102510:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=Gv5NdGCZ9WIRwhMhh9v4dX_hbY3ehgi8QoXWbw%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F102510", "title": "Chrome Dreams - CD Audio Series - More Maximum Metallica [2005]"}, {"metadata": {"category": "music", "price": "7.99", "currency": "EUR", "formatted_price": "7,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_175.jpg"]}, "id": "U1MS:7digital:album:126982:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=IMCJEv60Z9-rnwsEHv6JHsZoQZV5Xg3kdMkDkg%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F126982", "title": "Chrome Dreams - CD Audio Series - Maximum Metallica [1999]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_175.jpg"]}, "id": "U1MS:7digital:album:149660:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=5B_ITOFaVmRaMPiF5A5a871G2jQ9fM7PdhrDDQ%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F149660", "title": "Studio 99 - A Tribute To Metallica & Def Leppard [2006]"}]}, "type": "results"} {"info": {"reference-synonym.scope": []}, "type": "results"} {"info": {"reference-antonym.scope": []}, "type": "results"} {"info": {"reference-wikipedia.scope": [{"metadata": {"images": {"50x33": ["http://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Metallica-Mexico-City-2012.jpg/50px-Metallica-Mexico-City-2012.jpg"]}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Metallica-Mexico-City-2012.jpg/50px-Metallica-Mexico-City-2012.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=YfG6pbZ-kyD_GL_HBOMn1h-TTkvs8T_bAImnNA%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica", "title": "Metallica"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_(album)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=-j6CA82pdJ4fM7HZcUwRxssSlo1Npzri1oKWMw%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_%28album%29", "title": "Metallica (album)"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:List_of_Metallica_demos"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=vW-GzL2fKhCLHJt4G2tLLC0QYiXElE3kMrJGGg%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FList_of_Metallica_demos", "title": "List of Metallica demos"}, {"metadata": {"images": {"35x50": ["http://upload.wikimedia.org/wikipedia/en/thumb/4/45/Some_kind_of_minster_%28film%29.jpg/35px-Some_kind_of_minster_%28film%29.jpg"]}, "category": "wikipedia", "id": "WIKIPEDIA:Some_Kind_of_Monster_(film)"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/en/thumb/4/45/Some_kind_of_minster_%28film%29.jpg/35px-Some_kind_of_minster_%28film%29.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=ysIvVFjbp5IJrnTBrww2QvBcTPONJksymCgk0Q%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSome_Kind_of_Monster_%28film%29", "title": "Some Kind of Monster (film)"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_(disambiguation)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=yaKSJYbffwZMzbZdb1MCKrv0ckGlZ31JWLt5SA%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_%28disambiguation%29", "title": "Metallica (disambiguation)"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_Resources"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=ajI9sTOJp30xveqAq3_Z68kbl0mpTjRvoXmcrQ%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_Resources", "title": "Metallica Resources"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_discography"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=UXXIrkIWOXDhRWQd5j2y43iYbI31E_cSmNh08g%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_discography", "title": "Metallica discography"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_(beetle)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=5a2HG0C-32RMUQiGAXqUk32xWRDuw54TxGCfTw%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_%28beetle%29", "title": "Metallica (beetle)"}, {"metadata": {"images": {"50x50": ["http://upload.wikimedia.org/wikipedia/commons/thumb/8/86/US_DC_NorCal.svg/50px-US_DC_NorCal.svg.png"]}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_v._Napster,_Inc."}, "icon_hint": "http://upload.wikimedia.org/wikipedia/commons/thumb/8/86/US_DC_NorCal.svg/50px-US_DC_NorCal.svg.png", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=Jh6JuvHKhrqVksXGWR39exR6Ul2q9lgdWeT6Jw%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_v._Napster%2C_Inc.", "title": "Metallica v. Napster, Inc."}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Cunning_Stunts_(video)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=jpVpgdlXLcMfZATgUM0y51iwbaU7tageMS6iug%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FCunning_Stunts_%28video%29", "title": "Cunning Stunts (video)"}]}, "type": "results"} unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/samples/remote-scopes-minimal.txt0000644000015600001650000000677612657432773031463 0ustar pbuserpbgroup00000000000000[{"description": "This is an Ubuntu search plugin that enables information from Ubuntu One Music Store to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/7XpGFTkSugUON0PEsBBYHK", "name": "Ubuntu One Music Search", "keywords": ["u1ms", "u1", "ubuntuone"], "id": "more_suggestions-u1ms.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-u1.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Skimlinks to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/3VvxS1Dri9CKKloEzBs2lT", "name": "Skimlinks", "keywords": ["skimlinks"], "id": "more_suggestions-skimlinks.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Wikipedia to be searched and displayed in the Dash underneath the Reference header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/4QiCFcHJS3fmDCMFvgi9lo", "name": "Wikipedia", "keywords": ["wikipedia", "wiki"], "id": "reference-wikipedia.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Wordnik to be searched and displayed in the Dash underneath the Reference header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/15gptcYAwD6vk53cUtOrvX", "name": "Dictionary", "keywords": ["define"], "id": "reference-dictionary.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wordnik.svg"}, {"description": "Find themoviedb.org items", "screenshot": null, "name": "themoviedb.org", "keywords": ["themoviedb", "movie", "db", "actor", "cinema"], "id": "reference-themoviedb.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Amazon to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/5apoKe0t8OOj1BQkWKnfWi", "name": "Amazon", "keywords": ["amazon"], "id": "more_suggestions-amazon.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-amazon.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Etsy to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Etsy", "keywords": ["etsy"], "id": "more_suggestions-etsy.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from eBay to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "eBay", "keywords": ["ebay"], "id": "more_suggestions-ebay.scope", "icon": null}, {"description": "Find Stackexchange items", "screenshot": null, "name": "Stackexchange", "keywords": ["stackexchange"], "id": "reference-stackexchange.scope", "icon": null}] unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/samples/remote-scopes.txt0000644000015600001650000002513612657432773030026 0ustar pbuserpbgroup00000000000000[{"description": "This is an Ubuntu search plugin that enables information from Ubuntu One Music Store to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/7XpGFTkSugUON0PEsBBYHK", "name": "Ubuntu One Music Search", "keywords": ["u1ms", "u1", "ubuntuone"], "id": "more_suggestions-u1ms.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-u1.svg"}, {"description": "Find Sciencedirect items", "screenshot": null, "name": "Sciencedirect", "keywords": ["sciencedirect"], "id": "academic-sciencedirect.scope", "icon": null}, {"description": "Find Songkick items", "screenshot": null, "name": "Songkick", "keywords": ["songkick"], "id": "music-songkick.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Devhelp to be searched and displayed in the Dash underneath the Code header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Devhelp", "keywords": ["devhelp"], "id": "developer-devhelp.scope", "icon": null}, {"description": "Find Art and Historical documents", "screenshot": null, "name": "Europeana", "keywords": ["europeana", "art", "history"], "id": "reference-europeana.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from COLOURlovers to be searched and displayed in the Dash underneath the Graphics header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "COLOURlovers", "keywords": ["colourlovers", "color", "colors", "patterns", "pattern", "palette", "palettes"], "id": "graphics-colourlovers.scope", "icon": null}, {"description": "Find Pubmed items", "screenshot": null, "name": "Pubmed", "keywords": ["pubmed"], "id": "academic-pubmed.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Skimlinks to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/3VvxS1Dri9CKKloEzBs2lT", "name": "Skimlinks", "keywords": ["skimlinks"], "id": "more_suggestions-skimlinks.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg"}, {"description": "Find Songsterr items", "screenshot": null, "name": "Songsterr", "keywords": ["songsterr"], "id": "music-songsterr.scope", "icon": null}, {"description": "Find Scholar items", "screenshot": null, "name": "Scholar", "keywords": ["scholar"], "id": "academic-googlescholar.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Wikipedia to be searched and displayed in the Dash underneath the Reference header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/4QiCFcHJS3fmDCMFvgi9lo", "name": "Wikipedia", "keywords": ["wikipedia", "wiki"], "id": "reference-wikipedia.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wikipedia.svg"}, {"description": "Find Google Books books", "screenshot": null, "name": "Google Books", "keywords": ["googlebooks", "books"], "id": "books-googlebooks.scope", "icon": null}, {"description": "Find Foursquare items", "screenshot": null, "name": "Foursquare", "keywords": ["foursquare"], "id": "info-foursquare.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from deviantART to be searched and displayed in the Dash underneath the Graphics header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "deviantART", "keywords": ["art", "deviantart", "image", "da", "deviant"], "id": "graphics-deviantart.scope", "icon": null}, {"description": "Find Reddit posts", "screenshot": null, "name": "Reddit", "keywords": ["reddit"], "id": "info-reddit.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Wordnik to be searched and displayed in the Dash underneath the Reference header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/15gptcYAwD6vk53cUtOrvX", "name": "Dictionary", "keywords": ["define"], "id": "reference-dictionary.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-wordnik.svg"}, {"description": "Find themoviedb.org items", "screenshot": null, "name": "themoviedb.org", "keywords": ["themoviedb", "movie", "db", "actor", "cinema"], "id": "reference-themoviedb.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from TeXdoc to be searched and displayed in the Dash underneath the Help header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "TeXdoc", "keywords": ["texdoc"], "id": "help-texdoc.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables weather forecasts from OpenWeatherMap to be searched and displayed in the Dash underneath the Info header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "OpenWeatherMap", "keywords": ["openweathermap", "weather", "sky", "forecast"], "id": "info-openweathermap.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Amazon to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/5apoKe0t8OOj1BQkWKnfWi", "name": "Amazon", "keywords": ["amazon"], "id": "more_suggestions-amazon.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/service-amazon.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Google News to be searched and displayed in the Dash underneath the News header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Google News", "keywords": ["news", "googlenews"], "id": "news-googlenews.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Duck Duck Go to be searched and displayed in the Dash underneath the Info header. If you do not wish to search this content source, you can disable this search plugin.\n", "screenshot": "http://ubuntuone.com/1HrljlWGkjCHMy9L7EL9v1", "name": "DuckDuckGo Related", "keywords": ["ddg", "duckduckgo"], "id": "info-ddg_related.scope", "icon": "file:///usr/share/icons/unity-icon-theme/places/svg/group-info.svg"}, {"description": "This is an Ubuntu search plugin that enables information from Gallica to be searched and displayed in the Dash underneath the Books header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Gallica", "keywords": ["gallica", "book", "library", "books", "art", "history", "bnf"], "id": "books-gallica.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Etsy to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Etsy", "keywords": ["etsy"], "id": "more_suggestions-etsy.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from SoundCloud to be searched and displayed in the Dash underneath the Music header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "SoundCloud", "keywords": ["soundcloud", "music", "track", "sound", "listen"], "id": "music-soundcloud.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from OpenClipArt to be searched and displayed in the Dash underneath the Graphics header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "OpenClipArt", "keywords": ["openclipart", "clipart", "svg", "vector"], "id": "graphics-openclipart.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from GitHub to be searched and displayed in the Dash underneath the Code header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "GitHub", "keywords": ["github", "git", "code"], "id": "code-github.scope", "icon": null}, {"description": "Find Medicines items", "screenshot": null, "name": "Medicines", "keywords": ["medicines"], "id": "info-medicines.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Grooveshark to be searched and displayed in the Dash underneath the Music header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Grooveshark", "keywords": ["music", "grooveshark"], "id": "music-grooveshark.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from eBay to be searched and displayed in the Dash underneath the More Suggestions header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "eBay", "keywords": ["ebay"], "id": "more_suggestions-ebay.scope", "icon": null}, {"description": "Find Jstor items", "screenshot": null, "name": "Jstor", "keywords": ["jstor"], "id": "academic-jstor.scope", "icon": null}, {"description": "Find Recipepuppy items", "screenshot": null, "name": "Recipepuppy", "keywords": ["recipepuppy"], "id": "recipes-recipepuppy.scope", "icon": null}, {"description": "This is an Ubuntu search plugin that enables information from Yahoo Finance to be searched and displayed in the Dash underneath the News header. If you do not wish to search this content source, you can disable this search plugin.", "screenshot": null, "name": "Yahoo Finance", "keywords": ["yahoo", "stock", "market", "price"], "id": "news-yahoostock.scope", "icon": null}, {"description": "Find Stackexchange items", "screenshot": null, "name": "Stackexchange", "keywords": ["stackexchange"], "id": "reference-stackexchange.scope", "icon": null}, {"description": "Find Ubuntu accessories", "screenshot": null, "name": "Ubuntu Accessories", "keywords": ["canonicalshop", "shop", "ubuntu", "gift"], "id": "shopping-ubuntushop.scope", "icon": null}]unity-scope-home-6.8.2+16.04.20160212.1/tests/fake-server/samples/search_results2.txt0000644000015600001650000000132512657432773030343 0ustar pbuserpbgroup00000000000000{"scopes": [["more_suggestions-amazon.scope", "server"], ["more_suggestions-skimlinks.scope", "server"], ["more_suggestions-u1ms.scope", "server"], ["reference-wikipedia.scope", "server"], ["reference-dictionary.scope", "server"], ["reference-synonym.scope", "server"], ["reference-antonym.scope", "server"]], "type": "recommendations"} {"info": {"more_suggestions-amazon.scope": []}, "type": "results"} {"info": {"more_suggestions-u1ms.scope": []}, "type": "results"} {"info": {"reference-antonym.scope": []}, "type": "results"} {"info": {"reference-synonym.scope": []}, "type": "results"} {"info": {"reference-wikipedia.scope": []}, "type": "results"} {"info": {"reference-dictionary.scope": []}, "type": "results"} unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/0000755000015600001650000000000012657434047021566 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/test-home-scope.vala0000644000015600001650000022065512657432773025465 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski */ using Unity.HomeScope.SmartScopes; using Unity.Protocol; namespace Unity.HomeScope { static const string HOME_SCOPE_DBUS_NAME = "com.canonical.Unity.Scope.HomeTest"; static const string HOME_SCOPE_DBUS_PATH = "/com/canonical/unity/home"; static const string FEEDBACK_FILE = Config.TESTRUNDATADIR + "/feedback.dump"; bool verbose_debug = false; HomeScope? scope = null; Application? app = null; private KeywordSearch kw; const string[] RESULTS_SCHEMA = {"s", "s", "u", "u", "s", "s", "s", "s", "a{sv}"}; //TODO use schema def from libunity when it's public public class FakeSmartScopesServer { private Rand rand = new Rand (); private Pid server_pid; public int server_port { get; internal set; } public void start () throws SpawnError { server_port = rand.int_range (1024, 9000); Process.spawn_async (null, {Config.TOPSRCDIR + "/tests/fake-server/fake-sss-server.py", "--scopes", Config.TOPSRCDIR + "/tests/fake-server/samples/remote-scopes-minimal.txt", "--search", Config.TESTRUNDATADIR + "/search.dump", "--feedback", FEEDBACK_FILE, "--requests", "9", "--timeout", "15", "--port", server_port.to_string (), Config.TOPSRCDIR + "/tests/unit/data/search_results1.txt"}, null, 0, null, out server_pid); Socket socket = new Socket (SocketFamily.IPV4, SocketType.STREAM, SocketProtocol.TCP); assert (socket != null); InetAddress addr = new InetAddress.from_bytes ({127, 0, 0, 1}, SocketFamily.IPV4); InetSocketAddress server_addr = new InetSocketAddress (addr, (uint16)server_port); bool conn = false; int retry = 5; while (!conn && retry > 0) { try { conn = socket.connect (server_addr); } catch (Error e) {} if (!conn) Thread.usleep (1*1000000); // sleep for 1 second --retry; } } public void stop () { Posix.kill (server_pid, Posix.SIGTERM); Process.close_pid (server_pid); } } FakeSmartScopesServer fake_server; public static int main (string[] args) { Environment.set_variable ("GSETTINGS_BACKEND", "memory", true); Environment.set_variable ("HOME_SCOPE_IGNORE_OFONO", "1", true); var xdg_data_dirs = Environment.get_variable ("XDG_DATA_DIRS"); if (xdg_data_dirs == null) xdg_data_dirs = "/usr/share"; Environment.set_variable ("XDG_DATA_DIRS", "%s:%s".printf (Config.TESTDATADIR, xdg_data_dirs), true); Environment.set_variable ("LIBUNITY_SCOPE_DIRECTORIES", "%s/unity/scopes".printf (Config.TESTDATADIR), true); fake_server = new FakeSmartScopesServer (); fake_server.start (); kw = new KeywordSearch (); Test.init (ref args); Test.add_data_func ("/Unit/Search/ScopeRegistry", Fixture.create (HomeScopeSearchTester.test_registry)); Test.add_data_func ("/Unit/Search/KeywordSearch", Fixture.create (HomeScopeSearchTester.test_keyword_search)); Test.add_data_func ("/Unit/Search/SearchQueryState", Fixture.create (HomeScopeSearchTester.test_search_query_state)); Test.add_data_func ("/Unit/CategoryManager/BinarySearch", Fixture.create (HomeScopeSearchTester.test_category_manager_binary_search)); Test.add_data_func ("/Unit/CategoryManager/CmpCategoryData", Fixture.create (HomeScopeSearchTester.test_category_manager_cmp_category_data)); Test.add_data_func ("/Unit/CategoryManager/SortingPersonalContentOnly", Fixture.create (HomeScopeSearchTester.test_category_manager_sort_personal)); Test.add_data_func ("/Unit/CategoryManager/SortingFull", Fixture.create (HomeScopeSearchTester.test_category_manager_sort_full)); Test.add_data_func ("/Unit/CategoryManager/ContainsVisibleMatch", Fixture.create (HomeScopeSearchTester.test_category_manager_contains_visible_match)); Test.add_data_func ("/Unit/SearchUtil/BuildSearchScopesList", Fixture.create (SearchUtilTester.test_build_search_scopes_list)); Test.add_data_func ("/Unit/SearchUtil/SetSubscopesFilterHint", Fixture.create (SearchUtilTester.test_set_subscopes_filter_hint)); Test.add_data_func ("/Unit/SearchUtil/ScopesToQueryFromRequestedIds", Fixture.create (SearchUtilTester.test_scopes_to_query_from_requested_ids)); Test.add_data_func ("/Unit/SearchUtil/GetMasterScopeIdFromScopeId", Fixture.create (SearchUtilTester.test_get_master_id_from_scope_id)); Test.add_data_func ("/Unit/FilterState/SetFilters", Fixture.create (FilterStateTester.test_set_filters)); Test.add_data_func ("/Unit/SmartScopes/Parse", Fixture.create (SmartScopesUtilTester.test_smart_scopes_parse)); Test.add_data_func ("/Unit/SmartScopes/ParseErrors", Fixture.create (SmartScopesUtilTester.test_smart_scopes_parse_errors)); Test.add_data_func ("/Unit/SmartScopes/ParseMissingOptionalFields", Fixture.create (SmartScopesUtilTester.test_smart_scopes_parse_missing_optional_fields)); Test.add_data_func ("/Unit/SmartScopes/SearchString", Fixture.create (SmartScopesUtilTester.test_smart_scopes_search_string)); Test.add_data_func ("/Unit/SmartScopes/OnChunkData", Fixture.create (SmartScopesUtilTester.test_smart_scopes_on_chunk_data)); Test.add_data_func ("/Unit/SmartScopes/Metrics", Fixture.create (SmartScopesUtilTester.test_smart_scopes_metrics)); Test.add_data_func ("/Unit/SmartScopes/SessionId", Fixture.create (SmartScopesUtilTester.test_smart_scopes_session_id)); Test.add_data_func ("/Unit/SmartScopes/ChannelIdMap", Fixture.create (SmartScopesUtilTester.test_channel_id_map)); Test.add_data_func ("/Unit/SmartScopes/ClientInterfaceSearch", Fixture.create (SmartScopesInterfaceTester.test_smart_scopes_client_iface_search)); Test.add_data_func ("/Unit/SmartScopes/ClientInterfaceMetrics",Fixture.create (SmartScopesInterfaceTester.test_smart_scopes_client_iface_metrics)); Test.add_data_func ("/Unit/SmartScopes/ClientInterfaceError", Fixture.create (SmartScopesInterfaceTester.test_smart_scopes_client_iface_error)); Test.add_data_func ("/Unit/SmartScopes/ClientScopesInfoFromData", Fixture.create (SmartScopesUtilTester.test_client_scopes_info_from_data)); Test.add_data_func ("/Unit/SmartScopes/ClientScopesInfoFromFile", Fixture.create (SmartScopesUtilTester.test_client_scopes_info_from_file)); Test.add_data_func ("/Unit/SmartScopes/PlatformVersion", Fixture.create (SmartScopesUtilTester.test_platform_version)); Test.add_data_func ("/Unit/MarkupCleaner/NoMarkup", Fixture.create (MarkupCleanerTester.test_no_markup)); Test.add_data_func ("/Unit/MarkupCleaner/BrTagSupport", Fixture.create (MarkupCleanerTester.test_br_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/BTagSupport", Fixture.create (MarkupCleanerTester.test_b_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/ITagSupport", Fixture.create (MarkupCleanerTester.test_i_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/UTagSupport", Fixture.create (MarkupCleanerTester.test_u_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/TtTagSupport", Fixture.create (MarkupCleanerTester.test_tt_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/STagSupport", Fixture.create (MarkupCleanerTester.test_s_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/StrikeTagSupport", Fixture.create (MarkupCleanerTester.test_strike_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/BigTagSupport", Fixture.create (MarkupCleanerTester.test_big_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/SmallTagSupport", Fixture.create (MarkupCleanerTester.test_small_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/SubTagSupport", Fixture.create (MarkupCleanerTester.test_sub_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/SupTagSupport", Fixture.create (MarkupCleanerTester.test_sup_tag_support)); Test.add_data_func ("/Unit/MarkupCleaner/UnsupportedTags", Fixture.create (MarkupCleanerTester.test_unsupported_tags)); Test.add_data_func ("/Unit/MarkupCleaner/NestedTags", Fixture.create (MarkupCleanerTester.test_nested_tags)); Test.add_data_func ("/Unit/MarkupCleaner/AmpEntitySupport", Fixture.create (MarkupCleanerTester.test_amp_entity)); Test.add_data_func ("/Unit/MarkupCleaner/NbspEntitySupport", Fixture.create (MarkupCleanerTester.test_nbsp_entity)); Test.add_data_func ("/Unit/MarkupCleaner/EntitiesArePreserved", Fixture.create (MarkupCleanerTester.test_basic_entities_are_preserved)); Test.add_data_func ("/Unit/MarkupCleaner/UnsupportedEntitiesAreRaw", Fixture.create (MarkupCleanerTester.test_unsupported_entities_are_raw)); Test.add_data_func ("/Unit/MarkupCleaner/NumericEntitiesArePreserved", Fixture.create (MarkupCleanerTester.test_num_entities_are_preserved)); Test.add_data_func ("/Unit/HomeScopeInstance/ActivationMetrics", Fixture.create (HomeScopeInstanceTester.test_activation_metrics)); Test.add_data_func ("/Unit/HomeScopeInstance/PhoneFilters", Fixture.create (HomeScopeInstanceTester.test_phone_filters)); Test.add_data_func ("/Unit/HomeScopeInstance/DesktopFilters", Fixture.create (HomeScopeInstanceTester.test_desktop_filters)); Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (fake_server.server_port), true); HomeScope.discover_scopes_sync (); app = Extras.dbus_own_name (HOME_SCOPE_DBUS_NAME, () => { scope = new HomeScope (); }); kw.rebuild (); Test.run (); fake_server.stop (); return 0; } class HomeScopeInstanceTester: Object, Fixture { ScopeProxy? proxy = null; string channel_id; private void setup () { if (!scope.smart_scopes_ready) { var ml = new MainLoop (); scope.notify["smart_scopes_ready"].connect(() => { ml.quit (); }); run_with_timeout (ml, 8); } proxy = acquire_test_proxy (HOME_SCOPE_DBUS_NAME, HOME_SCOPE_DBUS_PATH); channel_id = open_channel (proxy, ChannelType.GLOBAL, null); assert (channel_id != null); } private void teardown () { proxy.close_channel (channel_id, null); proxy = null; } internal void test_activation_metrics () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); int num_click_events = 0; scope.sss_client.metrics_event_added.connect ((ev) => { if (ev.get_event_type () == "clicked") { if ((ev as SmartScopesMetrics.ClickMetricEvent).scope_id == "reference-wikipedia.scope") num_click_events++; } }); var search_hints = new HashTable (str_hash, str_equal); search_hints["form-factor"] = "phone"; perform_search (proxy, channel_id, "black sabbath", search_hints, null); var content = new HashTable (str_hash, str_equal); content["scope-id"] = new Variant.string ("reference-wikipedia.scope"); content["content"] = new HashTable (str_hash, str_equal); Unity.ScopeResult result = Unity.ScopeResult (); result.uri = "http://askubuntu.com"; result.icon_hint = ""; result.category = 0; result.result_type = Unity.ResultType.DEFAULT; result.mimetype = "inode/folder"; result.title = "Title"; result.comment = ""; result.dnd_uri = "file:///"; result.metadata = new HashTable (str_hash, str_equal); result.metadata["scope-id"] = new Variant.string ("reference.scope"); result.metadata["content"] = content; var hints = new HashTable(str_hash, str_equal); // activate result, expect result url back Unity.Protocol.ActivationReplyRaw? activation_reply = activate (proxy, channel_id, Unity.Protocol.ActionType.ACTIVATE_RESULT, result, hints); assert (activation_reply.handled == Unity.HandledType.NOT_HANDLED); assert (activation_reply.uri == "http://askubuntu.com"); // activate preview action, pass url via action id and expect it back in reply uri hints["preview-action-id"] = new Variant.string ("http://ubuntu.com"); activation_reply = activate (proxy, channel_id, Unity.Protocol.ActionType.PREVIEW_ACTION, result, hints); assert (activation_reply.handled == Unity.HandledType.NOT_HANDLED); assert (activation_reply.uri == "http://ubuntu.com"); // force metrics sending, otherwise Home Scope would flush them much too late var ml = new MainLoop (); scope.sss_client.send_feedback.begin (null, (obj, res) => { scope.sss_client.send_feedback.end (res); ml.quit (); }); assert (run_with_timeout (ml)); assert (num_click_events == 2); // activation events for wikipedia scope from this test case } internal void test_phone_filters () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); bool got_filters_update = false; proxy.filter_settings_changed.connect ((chid, filter_rows) => { got_filters_update = true; }); var hints = new HashTable (str_hash, str_equal); hints["form-factor"] = "phone"; perform_search (proxy, channel_id, "metallica", hints, null); assert (got_filters_update == false); // we expect no filter updates on the phone } internal void test_desktop_filters () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); int got_filters_update = 0; Variant? filters = null; proxy.filter_settings_changed.connect ((chid, filter_rows) => { got_filters_update += 1; // note: there are 2 filter updates during search, but we care about the final value of filters filters = filter_rows; }); assert (channel_id != null); assert (got_filters_update == 0); wait_for_synchronization (proxy.filters_model); assert (proxy.filters_model.get_n_rows () == 2); // verify all filter options are initially inactive int option_count = 0; for (var iter = proxy.filters_model.get_first_iter (); iter != proxy.filters_model.get_last_iter (); iter = proxy.filters_model.next (iter)) { var opts = proxy.filters_model.get_row (iter)[4].lookup_value ("options", null); for (int i = 0; i (str_hash, str_equal); hints["form-factor"] = "desktop"; perform_search (proxy, channel_id, "iron maiden", hints, null); assert (got_filters_update == 2); assert (filters.n_children() == 2); // two filters ('sources' and 'categories') var src_filter = filters.get_child_value(0); var cat_filter = filters.get_child_value(1); // verify 'sources' filter var opts = src_filter.get_child_value (4).lookup_value ("options", null); var option_flags = new HashTable(str_hash, str_equal); for (int i = 0; i enabled flag lookup for sources filter { var opt = opts.get_child_value(i); option_flags[opt.get_child_value (0).get_string ()] = opt.get_child_value (3).get_boolean (); } assert (option_flags["reference-stackexchange.scope"] == false); assert (option_flags["reference-dictionary.scope"] == false); assert (option_flags["reference-themoviedb.scope"] == false); assert (option_flags["masterscope_b-subscope1.scope"] == false); assert (option_flags["masterscope_b-subscope2.scope"] == false); assert (option_flags["masterscope_a-subscope1.scope"] == false); assert (option_flags["masterscope_a-subscope2.scope"] == false); assert (option_flags["more_suggestions-amazon.scope"] == false); assert (option_flags["more_suggestions-etsy.scope"] == false); assert (option_flags["more_suggestions-ebay.scope"] == false); assert (option_flags["more_suggestions-skimlinks.scope"] == false); assert (option_flags["more_suggestions-u1ms.scope"] == true); assert (option_flags["reference-wikipedia.scope"] == true); // verify 'categories' filter opts = cat_filter.get_child_value (4).lookup_value ("options", null); option_flags = new HashTable(str_hash, str_equal); for (int i = 0; i enabled flag lookup for categories filter { var opt = opts.get_child_value(i); option_flags[opt.get_child_value (0).get_string ()] = opt.get_child_value (3).get_boolean (); } assert (option_flags["masterscope_a.scope"] == false); assert (option_flags["masterscope_b.scope"] == false); assert (option_flags["more_suggestions.scope"] == true); assert (option_flags["reference.scope"] == true); } } class HomeScopeSearchTester: Object, Fixture { private Variant empty_asv = new Variant.array (new VariantType ("{sv}"), {}); private void teardown () { CategoryManager.instance ().clear (); } internal void test_registry () { // scope registry is a singleton and is initialized in main on start var registry = ScopeRegistry.instance (); var scopes = registry.flatten (); assert (scopes.size == 8); assert (scopes.contains ("masterscope_a.scope")); assert (scopes.contains ("masterscope_b.scope")); assert (scopes.contains ("masterscope_a-subscope1.scope")); assert (scopes.contains ("masterscope_a-subscope2.scope")); assert (scopes.contains ("masterscope_b-subscope1.scope")); assert (scopes.contains ("masterscope_b-subscope2.scope")); } internal void test_keyword_search () { assert (kw.num_of_mappings == 9); string new_search_string; assert (kw.process_query ("abcd: foobar", out new_search_string) == null); //unknown keyword, leave query as is assert (new_search_string == null); unowned Gee.Set? ids = kw.process_query ("subb1: foobar", out new_search_string); assert (ids != null); assert (ids.size == 1 && ids.contains ("masterscope_b-subscope1.scope")); assert (new_search_string == "foobar"); ids = kw.process_query ("mastera:foobar:123", out new_search_string); assert (ids != null); assert (ids.size == 1 && ids.contains ("masterscope_a.scope")); ids = kw.process_query ("mastera:foobar:123", out new_search_string); assert (ids != null); assert (ids.size == 1 && ids.contains ("masterscope_a.scope")); assert (new_search_string == "foobar:123"); ids = kw.process_query ("sub1a1b: foobar:123", out new_search_string); assert (ids != null); assert (ids.size == 2 && ids.contains ("masterscope_a-subscope1.scope") && ids.contains ("masterscope_b-subscope1.scope")); assert (new_search_string == "foobar:123"); } internal void test_search_query_state () { var search_state = new SearchQueryState (); assert (search_state.has_channel ("channel1") == false); assert (search_state.search_query_changed ("channel1", "") == SearchQueryChange.NEW_QUERY); assert (search_state.search_query_changed ("channel1", "") == SearchQueryChange.NOT_CHANGED); //two empty queries in a row, this is intended assert (search_state.has_channel ("channel1") == true); assert (search_state.search_query_changed ("channel1", "t") == SearchQueryChange.NEW_QUERY); assert (search_state.has_channel ("channel1") == true); assert (search_state.search_query_changed ("channel2", "t") == SearchQueryChange.NEW_QUERY); assert (search_state.search_query_changed ("channel1", "t") == SearchQueryChange.NOT_CHANGED); assert (search_state.search_query_changed ("channel1", "te") == SearchQueryChange.APPENDS_TO_PREVIOUS_QUERY); assert (search_state.search_query_changed ("channel1", "term") == SearchQueryChange.APPENDS_TO_PREVIOUS_QUERY); assert (search_state.search_query_changed ("channel1", "te") == SearchQueryChange.REMOVES_FROM_PREVIOUS_QUERY); assert (search_state.search_query_changed ("channel1", "") == SearchQueryChange.NEW_QUERY); assert (search_state.has_channel ("channel1") == true); // simulate canned queries assert (search_state.search_query_changed ("channel1", "ab") == SearchQueryChange.NEW_QUERY); search_state.set_canned_query("channel1", "foo:lo"); assert (search_state.search_query_changed ("channel1", "foo:lo") == SearchQueryChange.CANNED_QUERY); assert (search_state.search_query_changed ("channel1", "foo:lo") == SearchQueryChange.NOT_CHANGED); // simulate canned queries, user types a query immediately after activation assert (search_state.search_query_changed ("channel1", "me") == SearchQueryChange.NEW_QUERY); search_state.set_canned_query("channel1", "foo:metallica"); assert (search_state.search_query_changed ("channel1", "iron maiden") == SearchQueryChange.NEW_QUERY); assert (search_state.search_query_changed ("channel1", "foo:metallica") == SearchQueryChange.NEW_QUERY); //it's no longer considered canned query } internal void test_category_manager_binary_search () { var model = new Dee.SharedModel ("com.canonical.unity.scopes.test.scope1"); model.set_schema_full (RESULTS_SCHEMA); var iter = CategoryManager.binary_search (model, 0); // search on empty model assert (iter == null); model.append ("uri", "", 333, 0, "", "", "", "", empty_asv); iter = CategoryManager.binary_search (model, 333); // model with 1 row assert (iter != null); assert (model.get_string (iter, 0) == "uri"); // insert 100 categories, 3 rows for each category for (int i = 0; i < 100; i++) { for (int j = 1; j <= 3; j++) model.append ("uri%d-%d".printf (i, j), "", i, 0, "", "", "", "", empty_asv); } iter = CategoryManager.binary_search (model, 999); // doesn't exist assert (iter == null); iter = CategoryManager.binary_search (model, 0); assert (iter != null); assert (model.get_string (iter, 0) == "uri0-1"); iter = CategoryManager.binary_search (model, 1); assert (iter != null); assert (model.get_string (iter, 0) == "uri1-1"); iter = CategoryManager.binary_search (model, 13); assert (iter != null); assert (model.get_string (iter, 0) == "uri13-1"); iter = CategoryManager.binary_search (model, 14); assert (iter != null); assert (model.get_string (iter, 0) == "uri14-1"); iter = CategoryManager.binary_search (model, 99); assert (iter != null); assert (model.get_string (iter, 0) == "uri99-1"); } internal void test_category_manager_cmp_category_data () { CategoryManager.CategoryData cat1 = new CategoryManager.CategoryData ("scope1.scope"); cat1.recommended_order = -1; cat1.dconf_order = -1; cat1.results[Unity.ResultType.PERSONAL] = 1; cat1.results[Unity.ResultType.SEMI_PERSONAL] = 3; cat1.results[Unity.ResultType.DEFAULT] = 5; CategoryManager.CategoryData cat2 = new CategoryManager.CategoryData ("scope2.scope"); cat2.recommended_order = -1; cat2.dconf_order = -1; cat2.results[Unity.ResultType.PERSONAL] = 2; cat2.results[Unity.ResultType.SEMI_PERSONAL] = 1; cat2.results[Unity.ResultType.DEFAULT] = 5; assert (CategoryManager.cmp_category_data (cat1, cat2) > 0); // cat2 first, based on personal content cat1.results[Unity.ResultType.PERSONAL] = 2; // both have now same number of personal content assert (CategoryManager.cmp_category_data (cat1, cat2) < 0); // cat1 first, based on semi-personal content cat1.results[Unity.ResultType.SEMI_PERSONAL] = 1; // both have now same number of semi-personal content assert (CategoryManager.cmp_category_data (cat1, cat2) == 0); // equal cat1.results[Unity.ResultType.DEFAULT] = 7; // cat1 has now more default content assert (CategoryManager.cmp_category_data (cat1, cat2) < 0); // cat1 first, based on default content cat2.recommended_order = 1; //recommended order for cat1 remains unset assert (CategoryManager.cmp_category_data (cat1, cat2) > 0); // cat2 first, based on recommended order cat1.recommended_order = 2; assert (CategoryManager.cmp_category_data (cat1, cat2) > 0); // cat2 still first, based on recommended order cat1.recommended_order = 0; assert (CategoryManager.cmp_category_data (cat1, cat2) < 0); // cat1 first, higher recommended order cat2.dconf_order = 0; assert (CategoryManager.cmp_category_data (cat1, cat2) > 0); // cat2 first, based on dconf order, overrides recommended order of cat1 cat1.dconf_order = 0; cat2.dconf_order = 1; assert (CategoryManager.cmp_category_data (cat1, cat2) < 0); // cat1 first, based on dconf order } /* * Test sorting based on number of personal/semi-personal/public content *only* (no app scope visible match, no dconf order defined, no recommendations */ internal void test_category_manager_sort_personal () { var mgr = CategoryManager.instance (); mgr.register ("scope1.scope"); mgr.register ("scope2.scope"); mgr.register ("scope3.scope"); mgr.register ("scope4.scope"); mgr.register ("scope5.scope"); mgr.register ("more_suggestions.scope"); assert (mgr.get_category_index ("scope1.scope") == 0); assert (mgr.get_category_index ("scope2.scope") == 1); assert (mgr.get_category_index ("scope3.scope") == 2); assert (mgr.get_category_index ("scope4.scope") == 3); assert (mgr.get_category_index ("scope5.scope") == 4); assert (mgr.get_category_index ("more_suggestions.scope") == 5); assert (mgr.get_scope_id_by_category_index (0) == "scope1.scope"); assert (mgr.get_scope_id_by_category_index (1) == "scope2.scope"); assert (mgr.get_scope_id_by_category_index (2) == "scope3.scope"); assert (mgr.get_scope_id_by_category_index (3) == "scope4.scope"); assert (mgr.get_scope_id_by_category_index (4) == "scope5.scope"); assert (mgr.get_scope_id_by_category_index (5) == "more_suggestions.scope"); var scope_model1_ch1 = new Dee.SharedModel ("com.canonical.unity.scopes.test.scope1"); scope_model1_ch1.set_schema_full (RESULTS_SCHEMA); var scope_model2_ch1 = new Dee.SharedModel ("com.canonical.unity.scopes.test.scope2"); scope_model2_ch1.set_schema_full (RESULTS_SCHEMA); mgr.observe ("channel1", scope_model1_ch1); mgr.observe ("channel2", scope_model2_ch1); // populate home model with results from various scopes; normally this is done my synchronizer. // note that source scope is determined by category index value and matches get_scope_id_by_category_index // values above. // scope3 adds 3 public results scope_model1_ch1.append ("uri5", "icon", 2, ResultType.DEFAULT, "mimetype", "title3", "comment3", "dnd_uri3", empty_asv); scope_model1_ch1.append ("uri7", "icon", 2, ResultType.DEFAULT, "mimetype", "title1", "comment1", "dnd_uri1", empty_asv); scope_model1_ch1.append ("uri7", "icon", 2, ResultType.DEFAULT, "mimetype", "title4", "comment3", "dnd_uri4", empty_asv); // scope1 adds 2 personal results scope_model1_ch1.append ("uri2", "icon", 0, ResultType.PERSONAL, "mimetype", "title2", "comment2", "dnd_uri2", empty_asv); scope_model1_ch1.append ("uri1", "icon", 0, ResultType.PERSONAL, "mimetype", "title1", "comment1", "dnd_uri1", empty_asv); // scope2 adds 2 public results scope_model1_ch1.append ("uri4", "icon", 1, ResultType.DEFAULT, "mimetype", "title4", "comment3", "dnd_uri4", empty_asv); scope_model1_ch1.append ("uri3", "icon", 1, ResultType.DEFAULT, "mimetype", "title3", "comment3", "dnd_uri3", empty_asv); // scope5 adds 2 personal and 1 semi-personal result scope_model1_ch1.append ("uri8", "icon", 4, ResultType.PERSONAL, "mimetype", "title8", "comment8", "dnd_uri8", empty_asv); scope_model1_ch1.append ("uri9", "icon", 4, ResultType.PERSONAL, "mimetype", "title9", "comment9", "dnd_uri9", empty_asv); scope_model1_ch1.append ("uri10", "icon", 4, ResultType.SEMI_PERSONAL, "mimetype", "title10", "comment10", "dnd_uri10", empty_asv); // results for channel2, not really used but they are here to verify they don't impact sorting of channel1 scope_model2_ch1.append ("uri1", "icon", 2, ResultType.PERSONAL, "mimetype", "title1", "comment1", "dnd_uri1", empty_asv); scope_model2_ch1.append ("uri2", "icon", 2, ResultType.PERSONAL, "mimetype", "title2", "comment2", "dnd_uri2", empty_asv); scope_model2_ch1.append ("uri2", "icon", 2, ResultType.PERSONAL, "mimetype", "title2", "comment2", "dnd_uri2", empty_asv); var recommendations = new List (); // verify order of scope ids var order_ids = mgr.sort_categories ("foo", "channel1", scope_model1_ch1, recommendations); assert (order_ids.size == 6); assert (order_ids[0] == "scope5.scope"); assert (order_ids[1] == "scope1.scope"); assert (order_ids[2] == "scope3.scope"); assert (order_ids[3] == "scope2.scope"); // no data for scope4 or more_suggestions.scope // verify order of scope indices (it matches order of ids) var order_idx = mgr.get_category_order ("foo", "channel1", scope_model1_ch1, recommendations); assert (order_idx.length == 6); assert (order_idx[0] == 4); assert (order_idx[1] == 0); assert (order_idx[2] == 2); assert (order_idx[3] == 1); } /* * Full category sorting scenario: personal/public content, visible match in app scope, defined dconf order & recommendations from Smart Scope Service, * shopping results. */ internal void test_category_manager_sort_full () { var mgr = CategoryManager.instance (); mgr.register ("scope1.scope"); mgr.register ("scope2.scope"); mgr.register ("scope3.scope"); mgr.register ("scope4.scope"); mgr.register ("scope5.scope"); mgr.register ("applications.scope"); mgr.register ("more_suggestions.scope"); var scope_model1_ch1 = new Dee.SharedModel ("com.canonical.unity.scopes.test.scope1"); scope_model1_ch1.set_schema_full (RESULTS_SCHEMA); mgr.observe ("channel1", scope_model1_ch1); // populate home model with results from various scopes; normally this is done my synchronizer. // note that source scope is determined by category index value and matches get_scope_id_by_category_index // values above. // scope3 adds 3 public results scope_model1_ch1.append ("uri5", "icon", 2, ResultType.DEFAULT, "mimetype", "title3", "comment3", "dnd_uri3", empty_asv); scope_model1_ch1.append ("uri7", "icon", 2, ResultType.DEFAULT, "mimetype", "title1", "comment1", "dnd_uri1", empty_asv); scope_model1_ch1.append ("uri7", "icon", 2, ResultType.DEFAULT, "mimetype", "title4", "comment3", "dnd_uri4", empty_asv); // scope1 adds 2 personal results scope_model1_ch1.append ("uri2", "icon", 0, ResultType.PERSONAL, "mimetype", "title2", "comment2", "dnd_uri2", empty_asv); scope_model1_ch1.append ("uri1", "icon", 0, ResultType.PERSONAL, "mimetype", "title1", "comment1", "dnd_uri1", empty_asv); // scope2 adds 2 personal results as well scope_model1_ch1.append ("uri4", "icon", 1, ResultType.PERSONAL, "mimetype", "title4", "comment3", "dnd_uri4", empty_asv); scope_model1_ch1.append ("uri3", "icon", 1, ResultType.PERSONAL, "mimetype", "title3", "comment3", "dnd_uri3", empty_asv); // scope5 adds 2 personal and 1 semi-personal result scope_model1_ch1.append ("uri8", "icon", 4, ResultType.PERSONAL, "mimetype", "title8", "comment8", "dnd_uri8", empty_asv); scope_model1_ch1.append ("uri9", "icon", 4, ResultType.PERSONAL, "mimetype", "title9", "comment9", "dnd_uri9", empty_asv); scope_model1_ch1.append ("uri10", "icon", 4, ResultType.SEMI_PERSONAL, "mimetype", "title10", "comment10", "dnd_uri10", empty_asv); // unity-scope-applications.scope results scope_model1_ch1.append ("uri11", "icon", 5, ResultType.PERSONAL, "mimetype", "firefox", "Firefox web browser", "dnd_uri8", empty_asv); // more_suggestions.scope results scope_model1_ch1.append ("uri11", "icon", 6, ResultType.DEFAULT, "mimetype", "Metallica", "Master of Puppets", "dnd_uri8", empty_asv); // recommendations from Smart Scopes Service. // - first recommended scope contains only public content // - second and third recommended scopes contain equal number of personal results // - therefore recommended order will affect scope2 & scope1 only, scope3 will appear after them. var recommendations = new List (); recommendations.append (SmartScopes.RecommendedScope () { scope_id = "scope3.scope", scope_type = SmartScopes.ScopeType.ClientScope }); recommendations.append (SmartScopes.RecommendedScope () { scope_id = "scope1.scope", scope_type = SmartScopes.ScopeType.ClientScope }); recommendations.append (SmartScopes.RecommendedScope () { scope_id = "scope2.scope", scope_type = SmartScopes.ScopeType.ClientScope }); var dconf_order = new Gee.ArrayList (); // verify order of scope ids var order_ids = mgr.sort_categories ("firefox", "channel1", scope_model1_ch1, recommendations); assert (order_ids.size == 7); assert (order_ids[0] == "applications.scope"); assert (order_ids[1] == "scope5.scope"); assert (order_ids[2] == "more_suggestions.scope"); assert (order_ids[3] == "scope1.scope"); assert (order_ids[4] == "scope2.scope"); assert (order_ids[5] == "scope3.scope"); assert (order_ids[6] == "scope4.scope"); // no data for scope4 // repeat with dconf order that takes precedence over recommendations dconf_order.add ("applications.scope"); dconf_order.add ("scope2.scope"); dconf_order.add ("scope1.scope"); mgr.set_default_sort_order (dconf_order); order_ids = mgr.sort_categories ("firefox", "channel1", scope_model1_ch1, recommendations); assert (order_ids.size == 7); assert (order_ids[0] == "applications.scope"); assert (order_ids[1] == "scope5.scope"); assert (order_ids[2] == "more_suggestions.scope"); assert (order_ids[3] == "scope2.scope"); assert (order_ids[4] == "scope1.scope"); assert (order_ids[5] == "scope3.scope"); assert (order_ids[6] == "scope4.scope"); // no data for scope4 } internal void test_category_manager_contains_visible_match () { var mgr = CategoryManager.instance (); mgr.register ("scope1.scope"); mgr.register ("applications.scope"); assert (mgr.get_category_index ("applications.scope") == 1); var scope_model1_ch1 = new Dee.SharedModel ("com.canonical.unity.scopes.test.scope1"); scope_model1_ch1.set_schema_full (RESULTS_SCHEMA); // scope1 adds 2 results scope_model1_ch1.append ("uri1", "icon", 0, ResultType.DEFAULT, "mimetype", "title1", "comment3", "dnd_uri3", empty_asv); scope_model1_ch1.append ("uri2", "icon", 0, ResultType.DEFAULT, "mimetype", "title2", "comment1", "dnd_uri1", empty_asv); // app scope adds 5 scope_model1_ch1.append ("uri3", "icon", 1, ResultType.PERSONAL, "mimetype", "this is a title3", "comment one", "dnd_uri2", empty_asv); scope_model1_ch1.append ("uri4", "icon", 1, ResultType.PERSONAL, "mimetype", "another title4", "comment two", "dnd_uri1", empty_asv); scope_model1_ch1.append ("uri5", "icon", 1, ResultType.PERSONAL, "mimetype", "foo bar title5", "comment three", "dnd_uri1", empty_asv); scope_model1_ch1.append ("uri7", "icon", 1, ResultType.PERSONAL, "mimetype", "abcdef title7", "comment five", "dnd_uri1", empty_asv); scope_model1_ch1.append ("uri6", "icon", 1, ResultType.PERSONAL, "mimetype", "foo bar title6", "comment four", "dnd_uri1", empty_asv); scope_model1_ch1.append ("uri6", "icon", 1, ResultType.PERSONAL, "mimetype", "foo bar title7", "comment four", "dnd_uri1", empty_asv); assert (mgr.contains_visible_match (scope_model1_ch1, 1, "qwerty") == false); assert (mgr.contains_visible_match (scope_model1_ch1, 1, "foo") == true); //one word from a title assert (mgr.contains_visible_match (scope_model1_ch1, 1, "foo bar title6") == true); //all words from a title assert (mgr.contains_visible_match (scope_model1_ch1, 1, "foo bar title7") == false); //fails because we only take 5 results into account assert (mgr.contains_visible_match (scope_model1_ch1, 1, "def") == false); //no suffix match assert (mgr.contains_visible_match (scope_model1_ch1, 1, "abc") == true); //prefix match works assert (mgr.contains_visible_match (scope_model1_ch1, 1, "five") == true); //match on second word from comment column assert (mgr.contains_visible_match (scope_model1_ch1, 1, "comment") == true); //match on first word from comment column assert (mgr.contains_visible_match (scope_model1_ch1, 1, "this one") == true); //match on "this" in the title and "one" in comment column. } } class SearchUtilTester: Object, Fixture { internal void test_build_search_scopes_list () { var search_scopes = new HashTable?> (str_hash, str_equal); SearchUtil.build_search_scopes_list ("masterscope_a.scope", search_scopes); SearchUtil.build_search_scopes_list ("masterscope_b-subscope1.scope", search_scopes); assert (search_scopes.size () == 2); assert (search_scopes.contains ("masterscope_a.scope")); assert (search_scopes["masterscope_a.scope"] == null); assert (search_scopes.contains ("masterscope_b.scope")); var subscopes = search_scopes["masterscope_b.scope"]; assert (subscopes.size == 1); assert (subscopes.contains ("masterscope_b-subscope1.scope")); } internal void test_set_subscopes_filter_hint () { Gee.Set subscopes1 = new Gee.TreeSet (); Gee.Set subscopes2 = new Gee.TreeSet (); var search_scopes = new HashTable?> (str_hash, str_equal); subscopes1.add ("scope1-subscope1.scope"); subscopes2.add ("scope2-subscope1.scope"); subscopes2.add ("scope2-subscope2.scope"); search_scopes["scope1.scope"] = subscopes1; search_scopes["scope2.scope"] = subscopes2; var hints = new HashTable (str_hash, str_equal); SearchUtil.set_subscopes_filter_hint (hints, search_scopes, "scope1.scope"); assert (hints.size () == 1); assert (hints.contains ("subscopes-filter")); var subscopes_variant = hints["subscopes-filter"]; assert (subscopes_variant.n_children () == 1); assert (subscopes_variant.get_child_value (0).get_string () == "scope1-subscope1.scope"); SearchUtil.set_subscopes_filter_hint (hints, search_scopes, "scope2.scope"); assert (hints.size () == 1); assert (hints.contains ("subscopes-filter")); subscopes_variant = hints["subscopes-filter"]; assert (subscopes_variant.n_children () == 2); assert (subscopes_variant.get_child_value (0).get_string () == "scope2-subscope1.scope"); assert (subscopes_variant.get_child_value (1).get_string () == "scope2-subscope2.scope"); hints.remove_all (); SearchUtil.set_subscopes_filter_hint (hints, search_scopes, "foobar.scope"); assert (hints.size () == 0); //no hints if unknown scope } internal void test_scopes_to_query_from_requested_ids () { Gee.Set requested_by_kw = new Gee.TreeSet (); requested_by_kw.add ("masterscope_a.scope"); requested_by_kw.add ("masterscope_b-subscope1.scope"); requested_by_kw.add ("masterscope_b-subscope2.scope"); var search_scopes = new HashTable?> (str_hash, str_equal); bool direct_search = SearchUtil.scopes_to_query_from_requested_ids (requested_by_kw, search_scopes); assert (direct_search == true); assert (search_scopes.size () == 2); assert (search_scopes.contains ("masterscope_a.scope")); assert (search_scopes["masterscope_a.scope"] == null); assert (search_scopes.contains ("masterscope_b.scope")); var subscopes = search_scopes["masterscope_b.scope"]; assert (subscopes.size == 2); assert (subscopes.contains ("masterscope_b-subscope1.scope")); assert (subscopes.contains ("masterscope_b-subscope2.scope")); } internal void test_get_master_id_from_scope_id () { assert (SearchUtil.get_master_id_from_scope_id ("foo-bar.scope") == "foo.scope"); assert (SearchUtil.get_master_id_from_scope_id ("foo.scope") == null); } } class FilterStateTester: Object, Fixture { internal void test_set_filters () { var categories = new Unity.OptionsFilter (); categories.add_option ("a.scope", "A"); var sources = new Unity.OptionsFilter (); sources.add_option ("a-b.scope", "A"); var enabled_scopes = new string [0]; assert (FilterState.set_filters (categories, sources, enabled_scopes) == false); assert (categories.get_option ("a.scope").active == false); assert (sources.get_option ("a-b.scope").active == false); enabled_scopes += "a.scope"; assert (FilterState.set_filters (categories, sources, enabled_scopes) == true); assert (FilterState.set_filters (categories, sources, enabled_scopes) == false); assert (categories.get_option ("a.scope").active == true); assert (sources.get_option ("a-b.scope").active == false); enabled_scopes += "a-b.scope"; assert (FilterState.set_filters (categories, sources, enabled_scopes) == true); assert (FilterState.set_filters (categories, sources, enabled_scopes) == false); assert (categories.get_option ("a.scope").active == true); assert (sources.get_option ("a-b.scope").active == true); } } class SmartScopesUtilTester: Object, Fixture { internal void test_smart_scopes_parse () { CategoryManager.instance ().register ("more_suggestions.scope"); CategoryManager.instance ().register ("reference.scope"); int row_count = 0; int recommend_count = 0; var search_handler = new SmartScopes.SearchResponseHandler (); search_handler.parse_results_line ("""{"scopes": [["more_suggestions-amazon","server"],["more_suggestions-u1ms","server"]],"server_sid": "abcd", "type": "recommendations"}""", (scope_id, row) => { assert_not_reached (); }, (server_sid, recommend) => { recommend_count++; assert (recommend.length () == 2); assert (recommend.nth_data (0).scope_id == "more_suggestions-amazon"); assert (recommend.nth_data (1).scope_id == "more_suggestions-u1ms"); }); assert (recommend_count == 1); search_handler.parse_results_line ("""{"info": {"reference-wikipedia": [{"metadata":{"id":"1", "images":{}},"uri":"foo","title":"a"}]}, "type": "results"}""", (scope_id, row) => { row_count++; assert (scope_id == "reference-wikipedia"); //TODO assert on row values }, (server_sid, recommend) => { assert_not_reached (); }); assert (row_count == 1); } internal void test_smart_scopes_parse_errors () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); var search_handler = new SmartScopes.SearchResponseHandler (); bool got_excp = false; try { search_handler.parse_results_line ("""{"type": "recommendations"}""", (scope_id, row) => { assert_not_reached (); }, (recommend) => { assert_not_reached (); }); } catch (SmartScopes.ParseError e) { got_excp = true; } assert (got_excp == true); got_excp = false; try { search_handler.parse_results_line ("""{"type": "results"}""", (scope_id, row) => { assert_not_reached (); }, (recommend) => { assert_not_reached (); }); } catch (SmartScopes.ParseError e) { got_excp = true; } } internal void test_smart_scopes_parse_missing_optional_fields () { int row_count = 0; var search_handler = new SmartScopes.SearchResponseHandler (); // missing or null 'images' in the metadata search_handler.parse_results_line ("""{"info": {"searchin-scope.scope": [{"title": "search in foursquare...", "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/group-info.svg", "uri": "scopes-query://foursquare:drink", "metadata": {"images":null}}, {"title": "search in recipepuppy...", "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/group-recipes.svg", "uri": "scopes-query://recipepuppy:drink", "metadata": {}}, {"title": "search in grooveshark...", "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/service-grooveshark.svg", "uri": "scopes-query://grooveshark:drink", "metadata": {}}, {"title": "search in ebay...", "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/service-ebay.svg", "uri": "scopes-query://ebay:drink", "metadata": {}}, {"title": "search in songkick...", "icon_hint": "file:///usr/share/icons/unity-icon-theme/places/svg/group-music.svg", "uri": "scopes-query://songkick:drink", "metadata": {}}]}, "type": "results"}""", (scope_id, row) => { assert (scope_id == "searchin-scope.scope"); if (row_count == 0) { assert (row[0].get_string () == "x-unity-no-preview-scopes-query://foursquare:drink"); // uri assert (row[1].get_string () == "file:///usr/share/icons/unity-icon-theme/places/svg/group-info.svg"); // icon hint assert (row[2].get_uint32 () == 0); // category assert (row[3].get_uint32 () == Unity.ResultType.DEFAULT); // result type assert (row[4].get_string () == "text/html"); // mimetype assert (row[5].get_string () == "search in foursquare..."); // title assert (row[6].get_string () == ""); // comment assert (row[7].get_string () == "x-unity-no-preview-scopes-query://foursquare:drink"); // dnd uri assert (row[8].is_of_type (new VariantType ("a{sv}")) && row[8].n_children () == 0); // metadata } row_count++; }, (recommend) => { assert_not_reached (); }); assert (row_count == 5); } const string SERVER_URI = "https://foobar.ubuntu.com"; const string SEARCH_URI_PREFIX = SERVER_URI + "/smartscopes/v1/search"; internal void test_smart_scopes_search_string () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); Environment.set_variable ("SMART_SCOPES_SERVER", SERVER_URI, true); var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {"scope1","scope2"}, {"scope3","scope4"}); var client = new SmartScopes.SmartScopesClient (pinfo); assert (client.build_search_uri ("foo", "desktop", "xyz") == SEARCH_URI_PREFIX + "?q=foo&platform=desktop-1304&session_id=xyz&locale=EN&added_scopes=scope1,scope2&removed_scopes=scope3,scope4"); // locale and added/removed scopes are optional pinfo = new SmartScopes.PlatformInfo.with_data ("1304", null, {}, {}); client = new SmartScopes.SmartScopesClient (pinfo); assert (client.build_search_uri ("foo", "desktop", "xyz", {"scope3"}) == SEARCH_URI_PREFIX + "?q=foo&platform=desktop-1304&session_id=xyz&scopes=scope3"); // build_id is optional as well pinfo = new SmartScopes.PlatformInfo.with_data ("1304", null, {}, {}); pinfo.build_id = "edge-2014"; client = new SmartScopes.SmartScopesClient (pinfo); assert (client.build_search_uri ("foo", "phone", "xyz", {}) == SEARCH_URI_PREFIX + "?q=foo&platform=phone-1304&session_id=xyz&build_id=edge-2014"); // try the full build_id, mcc & mnc triple pinfo = new SmartScopes.PlatformInfo.with_data ("1304", null, {}, {}); pinfo.build_id = "edge-2014"; pinfo.country_code = "234"; pinfo.network_code = "000"; client = new SmartScopes.SmartScopesClient (pinfo); assert (client.build_search_uri ("foo", "phone", "xyz", {}) == SEARCH_URI_PREFIX + "?q=foo&platform=phone-1304&session_id=xyz&build_id=edge-2014&country_code=234&network_code=000"); } internal void test_smart_scopes_on_chunk_data () { CategoryManager.instance ().register ("more_suggestions.scope"); CategoryManager.instance ().register ("reference.scope"); int recommend_count = 0; int result_count = 0; var search_msg = new SmartScopesClient.SearchMessage ((scope_id, row) => { result_count++; assert (scope_id == "more_suggestions-amazon.scope"); }, (server_sid, scopes) => { recommend_count++; assert (scopes.length () == 2); assert (scopes.nth_data (0).scope_id == "more_suggestions-amazon.scope"); assert (scopes.nth_data (1).scope_id == "more_suggestions-skimlinks.scope"); }); string data1 = """{"scopes": [["more_suggestions-amazon.scope", "server"], ["more_suggestions-skimlinks.scope", "server"]], "server_sid": "abcd", "type":"recommendations"} {"info": {"more_suggestions-amazon.scope": [{"metadata": {"category": "music", "price": "8.5", "currency": "GBP", "images": {"30x26": ["http://ecx.images-amazon.com/images/I/41e6vjJc4vL._SL30_.jpg"], "160x138": ["http://ecx.es-amazon.com/Jc4vL._SL_.jpg"]}, "id": "zon:K:music:YATC3C", "formatted_price": "\u00a38"}, "icon_hint": "http://mages-aon.com/ages/I/110_.jpg", "uri": "https://product.com/v1/%2FE", "title": "Even The Odd Ones"}]}, "type": "results"}"""; var end = data1.length; // simulate small chunks of data var buffer1 = new Soup.Buffer (Soup.MemoryUse.TEMPORARY, data1.data[0:10]); search_msg.on_data_chunk (buffer1); buffer1 = new Soup.Buffer (Soup.MemoryUse.TEMPORARY, data1.data[10:30]); search_msg.on_data_chunk (buffer1); buffer1 = new Soup.Buffer (Soup.MemoryUse.TEMPORARY, data1.data[30:end]); search_msg.on_data_chunk (buffer1); search_msg.finalize_parsing (); // normally called by do_search assert (recommend_count == 1); assert (result_count == 1); } internal void test_smart_scopes_metrics () { var server_sid1 = "abcdef"; var server_sid2 = "ghijkl"; var now = new DateTime.now_utc (); var metrics = new SmartScopes.SmartScopesMetrics (); assert (metrics.num_events == 0); metrics.add_preview_event ("123", server_sid1, "foo1.scope", now); var time = now.add_minutes (1); metrics.add_click_event ("123", server_sid1, "foo2.scope", time); time = now.add_minutes (2); metrics.add_preview_event ("981", server_sid1, "foo4.scope", time); time = now.add_minutes (3); metrics.add_click_event ("888", server_sid2, "foo5.scope", time); var scope_res = new Gee.HashMap (); scope_res["foo1.scope"] = 1; scope_res["foo2.scope"] = 2; time = now.add_minutes (4); metrics.add_found_event ("456", server_sid1, scope_res, time); time = now.add_minutes (5); metrics.add_found_event ("711", server_sid2, scope_res, time); // verify parsed metrics var json = metrics.get_json (); var parser = new Json.Parser (); bool status = false; try { status = parser.load_from_data (json); } catch (Error e) { assert_not_reached (); } assert (status == true); var node = parser.get_root (); var event_array = node.get_array (); int num_click_events = 0; int num_preview_events = 0; int num_found_events = 0; int num_found_events_results = 0; // number of [scope, resultcount] results from all 'found' events event_array.foreach_element ((_array, _index, _node) => { var ev_element = _node.get_object (); var ev_type = ev_element.get_string_member ("type"); var session_id = ev_element.get_string_member ("session_id"); var timestamp = ev_element.get_int_member ("timestamp"); if (ev_type == "previewed") { var scope_id = ev_element.get_string_member ("previewed_scope"); var server_sid = ev_element.get_string_member ("server_sid"); if (scope_id == "foo1.scope") { ++num_preview_events; assert (session_id == "123"); assert (server_sid == "abcdef"); assert (timestamp == now.to_unix ()); } else if (scope_id == "foo4.scope") { ++num_preview_events; assert (session_id == "981"); assert (server_sid == "abcdef"); assert (timestamp == now.to_unix () + 2*60); } } else if (ev_type == "clicked") { var scope_id = ev_element.get_string_member ("clicked_scope"); if (scope_id == "foo2.scope") { ++num_click_events; assert (session_id == "123"); assert (timestamp == now.to_unix () + 60); assert (ev_element.has_member ("server_sid") == false); //server_sid same as for foo1.scope with session 123, so optimzed out } if (scope_id == "foo5.scope") { ++num_click_events; assert (session_id == "888"); assert (timestamp == now.to_unix () + 3*60); var server_sid = ev_element.get_string_member ("server_sid"); assert (server_sid == "ghijkl"); } } else if (ev_type == "found") { var results = ev_element.get_array_member ("results"); var server_sid = ev_element.get_string_member ("server_sid"); if (session_id == "456") { ++num_found_events; assert (server_sid == "abcdef"); assert (timestamp == now.to_unix () + 4*60); } else if (session_id == "711") { ++num_found_events; assert (server_sid == "ghijkl"); assert (timestamp == now.to_unix () + 5*60); } else { assert_not_reached (); } assert (results.get_length () == 2); // in this test each 'found' event has results for 2 scopes results.foreach_element ((_resarray, _resindex, _resnode) => { var scope_res_arr = _resnode.get_array (); if (scope_res_arr.get_string_element (0) == "foo1.scope") { ++num_found_events_results; assert (scope_res_arr.get_int_element (1) == 1); } else if (scope_res_arr.get_string_element (0) == "foo2.scope") { ++num_found_events_results; assert (scope_res_arr.get_int_element (1) == 2); } else { assert_not_reached (); } }); } else { assert_not_reached (); } }); assert (num_preview_events == 2); assert (num_click_events == 2); assert (num_found_events == 2); assert (num_found_events_results == 4); assert (metrics.num_events == 6); metrics.clear_events (); assert (metrics.num_events == 0); } internal void test_smart_scopes_session_id () { // a very basic check of uuid var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {}, {}); var client = new SmartScopes.SmartScopesClient (pinfo); var session_id = client.create_session_id (); assert (session_id != null); assert (session_id.replace ("-", "").length == 32); } internal void test_client_scopes_info_from_data () { var installed = new Gee.TreeSet (); installed.add ("scope2.scope"); installed.add ("scope3.scope"); installed.add ("scope9.scope"); string client_scopes = """{"unity-scope-a": ["scope1.scope","scope2.scope"], "unity-scope-b": ["scope3.scope"], "unity-scope-c": ["scope4.scope"]}"""; var clinfo = ClientScopesInfo.from_data (client_scopes, installed); var added = clinfo.get_added_scopes (); var removed = clinfo.get_removed_scopes (); assert (added.size == 1); assert (added.contains ("scope9.scope")); assert (removed.size == 2); assert (removed.contains ("scope1.scope")); assert (removed.contains ("scope4.scope")); // check error on invalid json try { var clinfo2 = ClientScopesInfo.from_data (";;", installed); assert_not_reached (); } catch (Error e) {} } internal void test_client_scopes_info_from_file () { var installed = new Gee.TreeSet (); installed.add ("scope2.scope"); installed.add ("scope4.scope"); var clinfo = ClientScopesInfo.from_file (Config.TOPSRCDIR + "/tests/unit/data/unity/client-scopes.json", installed); var added = clinfo.get_added_scopes (); var removed = clinfo.get_removed_scopes (); assert (added.size == 1); assert (added.contains ("scope4.scope")); assert (removed.size == 2); assert (removed.contains ("scope1.scope")); assert (removed.contains ("scope3.scope")); // check error on missing file try { var clinfo4 = ClientScopesInfo.from_file ("/non/existing/file", installed); assert_not_reached (); } catch (Error e) {} } internal void test_channel_id_map () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); var ids = new SmartScopes.ChannelIdMap (); assert (ids.has_session_id_for_channel ("foo") == false); assert (ids.has_server_sid_for_channel ("foo") == false); assert (ids.map_server_sid ("channel3", "ssid2") == false); // it's an error to map server_sid before mapping session first ids.map_session_id ("channel1", "session1"); ids.map_session_id ("channel2", "session2"); assert (ids.has_session_id_for_channel ("channel1")); assert (ids.session_id_for_channel ("channel1") == "session1"); assert (ids.has_server_sid_for_channel ("channel1") == false); assert (ids.server_sid_for_channel ("channel1") == null); assert (ids.map_server_sid ("channel1", "ssid1")); assert (ids.has_session_id_for_channel ("channel1")); assert (ids.has_server_sid_for_channel ("channel1")); assert (ids.server_sid_for_channel ("channel1") == "ssid1"); ids.remove_channel ("channel1"); assert (ids.has_session_id_for_channel ("channel1") == false); assert (ids.has_server_sid_for_channel ("channel1") == false); assert (ids.has_session_id_for_channel ("channel2")); } internal void test_platform_version () { var rel = PlatformInfo.get_release_string (); assert (rel != null); // version info is dynamic, so make it convinient and possible to run these tests on various ubuntu releases assert (GLib.Regex.match_simple ("^\\d\\d\\.\\d\\d$", rel)); } } class SmartScopesInterfaceTester: Object, Fixture { internal void test_smart_scopes_client_iface_search () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (fake_server.server_port), true); var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {"scope1"}, {"scope3"}); var client = new SmartScopes.SmartScopesClient (pinfo); var session_id = "5d06cc10-751b-11e2-87e0-fb468b0a185a"; var ml = new MainLoop (); int num_results = 0; int num_recommend = 0; client.search.begin ("foo", "desktop", session_id, {"scope3"}, {}, (scope_id, row) => { num_results++; }, (server_sid, recommend) => { num_recommend++; assert (recommend.length () == 7); }, null, (obj, res) => { try { client.search.end (res); } catch (Error e) { warning ("Smart scopes search failed: %s", e.message); assert_not_reached (); } ml.quit (); }); assert (run_with_timeout (ml)); assert (num_results > 0); assert (num_recommend == 1); uint8[] contents; var search_dump = File.new_for_path (Config.TESTRUNDATADIR + "/search.dump"); assert (search_dump.load_contents (null, out contents, null)); assert (((string)contents).strip () == "q=foo&platform=desktop-1304&session_id=5d06cc10-751b-11e2-87e0-fb468b0a185a&locale=EN&added_scopes=scope1&removed_scopes=scope3&scopes=scope3"); } internal void test_smart_scopes_client_iface_metrics () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:%d".printf (fake_server.server_port), true); var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {"scope1"}, {"scope3"}); var client = new SmartScopes.SmartScopesClient (pinfo); var session_id = "5d06cc10-751b-11e2-87e0-fb468b0a185a"; var session_id1 = "5d06cc10-751b-11e2-87e0-fb468b0a185a"; var session_id2 = "9d06cd01-931c-2212-3333-ab668b0a3850"; var server_sid1 = "abcdef"; var server_sid2 = "ghijkl"; var server_sid3 = "mnopqr"; var scope_res1 = new Gee.HashMap (); scope_res1["foo1.scope"] = 1; var scope_res2 = new Gee.HashMap (); scope_res1["foo2.scope"] = 2; var scope_res3 = new Gee.HashMap (); scope_res1["foo3.scope"] = 3; var ml = new MainLoop (); var now = new DateTime.now_utc (); client.add_found_event (session_id1, server_sid1, scope_res1, now); client.add_preview_event (session_id1, server_sid1, "scope1.scope", now); client.add_click_event (session_id1, server_sid1, "scope1.scope", now); client.add_found_event (session_id1, server_sid2, scope_res2, now); client.add_click_event (session_id1, server_sid2, "scope3.scope", now); client.add_found_event (session_id2, server_sid3, scope_res3, now); client.add_click_event (session_id2, server_sid3, "scope4.scope", now); client.send_feedback.begin (null, (obj, res) => { client.send_feedback.end (res); ml.quit (); }); assert (run_with_timeout (ml)); // load feedback data dumped by server, verify events var parser = new Json.Parser (); bool status = parser.load_from_file (Config.TESTRUNDATADIR + "/feedback.dump"); assert (status == true); var ev_array = parser.get_root ().get_array (); assert (ev_array.get_length () == 7); Json.Object el; el = ev_array.get_element (0).get_object (); assert (el.get_string_member ("type") == "found"); assert (el.get_string_member ("session_id") == session_id1); assert (el.get_string_member ("server_sid") == server_sid1); el = ev_array.get_element (1).get_object (); assert (el.get_string_member ("type") == "previewed"); assert (el.get_string_member ("session_id") == session_id1); assert (el.has_member ("server_sid") == false); // same server_sid, optimized out el = ev_array.get_element (2).get_object (); assert (el.get_string_member ("type") == "clicked"); assert (el.get_string_member ("session_id") == session_id1); assert (el.has_member ("server_sid") == false); // same server_sid, optimized out el = ev_array.get_element (3).get_object (); assert (el.get_string_member ("type") == "found"); assert (el.get_string_member ("session_id") == session_id1); assert (el.get_string_member ("server_sid") == server_sid2); el = ev_array.get_element (4).get_object (); assert (el.get_string_member ("type") == "clicked"); assert (el.get_string_member ("session_id") == session_id1); assert (el.has_member ("server_sid") == false); // same server_sid, optimized out el = ev_array.get_element (5).get_object (); assert (el.get_string_member ("type") == "found"); assert (el.get_string_member ("session_id") == session_id2); assert (el.get_string_member ("server_sid") == server_sid3); el = ev_array.get_element (6).get_object (); assert (el.get_string_member ("type") == "clicked"); assert (el.get_string_member ("session_id") == session_id2); assert (el.has_member ("server_sid") == false); // same server_sid, optimized out } internal void test_smart_scopes_client_iface_error () { // ignore warnings Test.log_set_fatal_handler (() => { return false; }); Environment.set_variable ("SMART_SCOPES_SERVER", "http://127.0.0.1:9999", true); // non-existing server (connection failure) var pinfo = new SmartScopes.PlatformInfo.with_data ("1304", "EN", {"scope1"}, {"scope3"}); var client = new SmartScopes.SmartScopesClient (pinfo); var session_id = "5d06cc10-751b-11e2-87e0-fb468b0a185a"; var server_sid = "abcdef"; var ml = new MainLoop (); client.search.begin ("foo", "desktop", session_id, {"scope3"}, {}, (scope_id, row) => { assert_not_reached (); }, (recommend) => { assert_not_reached (); }, null, (obj, res) => { try { client.search.end (res); assert_not_reached (); } catch (Error e) { ml.quit (); } }); assert (run_with_timeout (ml)); var now = new DateTime.now_utc (); client.add_click_event (session_id, server_sid, "scope1.scope", now); client.send_feedback.begin (null, (obj, res) => { try { client.send_feedback.end (res); assert_not_reached (); } catch (Error e) { ml.quit (); } }); assert (run_with_timeout (ml)); } } class MarkupCleanerTester: Object, Fixture { internal void test_no_markup () { string input = "This is a\ntest"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a\ntest"); } internal void test_br_tag_support () { string input = "This is
a
test
"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is\n a\ntest\n"); } internal void test_b_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_i_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_u_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_tt_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_s_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_strike_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_small_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_big_tag_support () { string input = "T>his is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "T>his is a test"); } internal void test_sub_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_sup_tag_support () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_unsupported_tags () { string input = "This is a test"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "This is a test"); } internal void test_nested_tags () { string input = "Click me!"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "Click me!"); } internal void test_amp_entity () { string input = "Foo & Bar"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "Foo & Bar"); } internal void test_nbsp_entity () { string input = "Foo Bar"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "Foo Bar"); } internal void test_basic_entities_are_preserved () { string input = "Foo & bar < > &Quot; '"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "Foo & bar < > " '"); } internal void test_unsupported_entities_are_raw () { string input = "Foo ¼ bar ¬"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "Foo &frac14 bar &not"); } internal void test_num_entities_are_preserved () { string input = "Foo bar €"; string result = MarkupCleaner.html_to_pango_markup (input); assert (result == "Foo bar €"); } } } unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/Makefile.am0000644000015600001650000000577312657432777023646 0ustar pbuserpbgroup00000000000000include $(top_srcdir)/Makefile.decl check_PROGRAMS = \ test-home-scope \ $(NULL) TEST_PROGS += $(check_PROGRAMS) run_server: pkill fake-sss-server 2>&1 > /dev/null || true $(top_srcdir)/tests/fake-server/fake-sss-server.py --search $(top_builddir)/tests/unit/search.dump --feedback $(top_builddir)/tests/unit/feedback.dump --requests 2 --port 8888 $(top_srcdir)/tests/unit/data/search_results1.txt & AM_VALAFLAGS = \ --pkg dee-1.0 \ --pkg unity \ --pkg unity-protocol \ --pkg gio-2.0 \ --pkg gio-unix-2.0 \ --pkg glib-2.0 \ --pkg json-glib-1.0 \ --pkg gee-0.8 \ --pkg unity-extras \ --vapidir $(srcdir) \ --vapidir $(top_srcdir)/vapi \ --target-glib=2.26 \ --pkg libsoup-gnome-2.4 \ --pkg libsoup-2.4 \ --pkg libuuid \ --pkg posix \ $(MAINTAINER_VALAFLAGS) \ $(NULL) AM_LDFLAGS = -rpath $(PROTOCOLPRIVATELIBDIR) $(COVERAGE_LDFLAGS) LDADD = $(HOME_SCOPE_LIBS) \ $(test_libs) AM_CPPFLAGS = \ -DDATADIR=\"$(DATADIR)\" \ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \ -DG_LOG_DOMAIN=\"unity-scope-home\" \ $(HOME_SCOPE_CFLAGS) \ $(MAINTAINER_CFLAGS) \ -I$(srcdir) \ $(COVERAGE_CFLAGS) \ $(NULL) if !ENABLE_C_WARNINGS AM_CPPFLAGS += -w endif MAINSOURCES = \ ../../src/scope.vala \ ../../src/scope-manager.vala \ ../../src/keyword-search.vala \ ../../src/scope-registry.vala \ ../../src/remote-scope-registry.vala \ ../../src/meta-scope-registry.vala \ ../../src/search-util.vala \ ../../src/filter-state.vala \ ../../src/scope-definitions-parser.vala \ ../../src/search-query-state.vala \ ../../src/smart-scopes-interface.vala \ ../../src/smart-scopes-id-map.vala \ ../../src/smart-scopes-parse.vala \ ../../src/smart-scopes-default-parser.vala \ ../../src/smart-scopes-more-suggestions-parser.vala \ ../../src/smart-scopes-remote-scopes-parser.vala \ ../../src/smart-scopes-search.vala \ ../../src/smart-scopes-metrics.vala \ ../../src/client-scopes-info.vala \ ../../src/platform-info.vala \ ../../src/category-manager.vala \ ../../src/markup-cleaner.vala \ ../../src/master-scopes.vala \ ../../src/smart-scopes-preview-parser.vala \ $(NULL) TEST_SOURCES = \ config-tests.vala \ test-utils.vala \ test-home-scope.vala \ $(NULL) test_home_scope_VALASOURCES = $(TEST_SOURCES) $(MAINSOURCES) # the patsubst rule below and objects override are needed to force dependency on *.c files # generated in current test dir rather than on those from ../../src nodist_test_home_scope_SOURCES = $(TEST_SOURCES:.vala=.c) $(patsubst ../../src/%,%,$(MAINSOURCES:.vala=.c)) nodist_test_home_scope_OBJECTS = $(nodist_test_home_scope_SOURCES:.c=.o) CLEANFILES = *.stamp \ *.c \ $(NULL) EXTRA_DIST = \ $(test_home_scope_VALASOURCES) \ $(NULL) BUILT_SOURCES = \ test-home-scope.vala.stamp \ $(NULL) test-home-scope.vala.stamp: $(test_home_scope_VALASOURCES) $(AM_V_GEN)$(VALAC) -C $(AM_VALAFLAGS) $(VALAFLAGS) $^ @touch $@ # START HEADLESS TESTS if ENABLE_HEADLESS_TESTS test-headless: $(XVFB) make test-nonrecursive; \ sleep 1; endif # END HEADLESS TESTS unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/0000755000015600001650000000000012657434047022477 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/search_results1.txt0000644000015600001650000004273212657432773026363 0ustar pbuserpbgroup00000000000000{"scopes": [["more_suggestions-amazon.scope", "server"], ["more_suggestions-skimlinks.scope", "server"], ["more_suggestions-u1ms.scope", "server"], ["reference-wikipedia.scope", "server"], ["reference-dictionary.scope", "server"], ["reference-synonym.scope", "server"], ["reference-antonym.scope", "server"]], "server_sid": "abcd", "type": "recommendations"} {"info": {"more_suggestions-u1ms.scope": [{"metadata": {"category": "music", "price": "8.99", "currency": "EUR", "formatted_price": "8,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_175.jpg"]}, "id": "U1MS:7digital:album:1726287:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/017/262/0001726287_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=3hoHNgE4OezJ5_H9I-eTh5YYj-UqFkMlSC8jSA%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1726287", "title": "Metallica - Metallica - Live in Moscow [2012]"}, {"metadata": {"category": "music", "price": "8.99", "currency": "EUR", "formatted_price": "8,99 \u20ac", "images": {"350x350": [""], "182x182": [""], "50x50": [""], "100x100": [""], "180x180": [""], "33x33": [""], "52x52": [""], "75x75": [""], "200x200": [""], "175x175": [""]}, "id": "U1MS:7digital:album:1864502:EU"}, "icon_hint": "", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=KBp7PtNQMrGQpNEuCoczqUtzpcS_4rQhUwF6Ww%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F1864502", "title": "Metallica - Metallica - Live in Moscow - Volume 2 [2012]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_175.jpg"]}, "id": "U1MS:7digital:album:242383:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/002/423/0000242383_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=Jk0CFKBMG3hOsSqADavD6qJ1zlyMVnDqSE2PGg%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F242383", "title": "Rockabye Baby! - Lullaby Renditions of Metallica [2007]"}, {"metadata": {"category": "music", "price": "7.99", "currency": "EUR", "formatted_price": "7,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_175.jpg"]}, "id": "U1MS:7digital:album:95490:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/000/954/0000095490_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=jW7uZRdULlLsF6BQMHmKW3pq3mkAImXFkB8hEQ%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F95490", "title": "Various Artists - A Punk Tribute To Metallica [2006]"}, {"metadata": {"category": "music", "price": "7.99", "currency": "EUR", "formatted_price": "7,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_175.jpg"]}, "id": "U1MS:7digital:album:141050:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/410/0000141050_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=sLQKdt4C-wseOKTS8JMooJvBsXWe4yef8qHMWA%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F141050", "title": "Various Artists - A Metal Tribute To Metallica [2000]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_175.jpg"]}, "id": "U1MS:7digital:album:180026:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/800/0000180026_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=VZLpF7hX9NbSCSrAPRJXv5mazQrm66k8YRz25A%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F180026", "title": "Various Artists - The Blackest Box - The Ultimate Metallica Tribute [2007]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_175.jpg"]}, "id": "U1MS:7digital:album:200025:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/002/000/0000200025_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=cxMQBVJve_3Qjr-EF3O1Y1IOLx6-SuSsiyTTAg%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F200025", "title": "Various Artists - Metallic Assault - A Tribute to Metallica [2006]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_175.jpg"]}, "id": "U1MS:7digital:album:102510:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/025/0000102510_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=Gv5NdGCZ9WIRwhMhh9v4dX_hbY3ehgi8QoXWbw%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F102510", "title": "Chrome Dreams - CD Audio Series - More Maximum Metallica [2005]"}, {"metadata": {"category": "music", "price": "7.99", "currency": "EUR", "formatted_price": "7,99 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_175.jpg"]}, "id": "U1MS:7digital:album:126982:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/269/0000126982_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=IMCJEv60Z9-rnwsEHv6JHsZoQZV5Xg3kdMkDkg%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F126982", "title": "Chrome Dreams - CD Audio Series - Maximum Metallica [1999]"}, {"metadata": {"category": "music", "price": "9.49", "currency": "EUR", "formatted_price": "9,49 \u20ac", "images": {"350x350": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_350.jpg"], "182x182": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_182.jpg"], "50x50": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_50.jpg"], "100x100": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_100.jpg"], "180x180": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_180.jpg"], "33x33": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_33.jpg"], "52x52": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_52.jpg"], "75x75": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_75.jpg"], "200x200": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_200.jpg"], "175x175": ["http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_175.jpg"]}, "id": "U1MS:7digital:album:149660:EU"}, "icon_hint": "http://cdn.7static.com/static/img/sleeveart/00/001/496/0000149660_175.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=5B_ITOFaVmRaMPiF5A5a871G2jQ9fM7PdhrDDQ%3D%3D&u=http%3A%2F%2Fmusicsearch.ubuntu.com%2Fv1%2Ftracker%3Furl%3Dhttps%253A%252F%252Fone.ubuntu.com%252Fmusic-store%252Frelease%252F149660", "title": "Studio 99 - A Tribute To Metallica & Def Leppard [2006]"}]}, "type": "results"} {"info": {"reference-wikipedia.scope": [{"metadata": {"images": {"50x33": ["http://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Metallica-Mexico-City-2012.jpg/50px-Metallica-Mexico-City-2012.jpg"]}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Metallica-Mexico-City-2012.jpg/50px-Metallica-Mexico-City-2012.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=YfG6pbZ-kyD_GL_HBOMn1h-TTkvs8T_bAImnNA%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica", "title": "Metallica"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_(album)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=-j6CA82pdJ4fM7HZcUwRxssSlo1Npzri1oKWMw%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_%28album%29", "title": "Metallica (album)"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:List_of_Metallica_demos"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=vW-GzL2fKhCLHJt4G2tLLC0QYiXElE3kMrJGGg%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FList_of_Metallica_demos", "title": "List of Metallica demos"}, {"metadata": {"images": {"35x50": ["http://upload.wikimedia.org/wikipedia/en/thumb/4/45/Some_kind_of_minster_%28film%29.jpg/35px-Some_kind_of_minster_%28film%29.jpg"]}, "category": "wikipedia", "id": "WIKIPEDIA:Some_Kind_of_Monster_(film)"}, "icon_hint": "http://upload.wikimedia.org/wikipedia/en/thumb/4/45/Some_kind_of_minster_%28film%29.jpg/35px-Some_kind_of_minster_%28film%29.jpg", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=ysIvVFjbp5IJrnTBrww2QvBcTPONJksymCgk0Q%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSome_Kind_of_Monster_%28film%29", "title": "Some Kind of Monster (film)"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_(disambiguation)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=yaKSJYbffwZMzbZdb1MCKrv0ckGlZ31JWLt5SA%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_%28disambiguation%29", "title": "Metallica (disambiguation)"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_Resources"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=ajI9sTOJp30xveqAq3_Z68kbl0mpTjRvoXmcrQ%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_Resources", "title": "Metallica Resources"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_discography"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=UXXIrkIWOXDhRWQd5j2y43iYbI31E_cSmNh08g%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_discography", "title": "Metallica discography"}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_(beetle)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=5a2HG0C-32RMUQiGAXqUk32xWRDuw54TxGCfTw%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_%28beetle%29", "title": "Metallica (beetle)"}, {"metadata": {"images": {"50x50": ["http://upload.wikimedia.org/wikipedia/commons/thumb/8/86/US_DC_NorCal.svg/50px-US_DC_NorCal.svg.png"]}, "category": "wikipedia", "id": "WIKIPEDIA:Metallica_v._Napster,_Inc."}, "icon_hint": "http://upload.wikimedia.org/wikipedia/commons/thumb/8/86/US_DC_NorCal.svg/50px-US_DC_NorCal.svg.png", "uri": "https://productsearch.ubuntu.com/v1/redirect?s=Jh6JuvHKhrqVksXGWR39exR6Ul2q9lgdWeT6Jw%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMetallica_v._Napster%2C_Inc.", "title": "Metallica v. Napster, Inc."}, {"metadata": {"images": {}, "category": "wikipedia", "id": "WIKIPEDIA:Cunning_Stunts_(video)"}, "uri": "https://productsearch.ubuntu.com/v1/redirect?s=jpVpgdlXLcMfZATgUM0y51iwbaU7tageMS6iug%3D%3D&u=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FCunning_Stunts_%28video%29", "title": "Cunning Stunts (video)"}]}, "type": "results"} unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/0000755000015600001650000000000012657434047023647 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/client-scopes.json0000644000015600001650000000013512657432773027315 0ustar pbuserpbgroup00000000000000{ "unity-scope-a": ["scope1.scope","scope2.scope"], "unity-scope-b": ["scope3.scope"] } unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/0000755000015600001650000000000012657434047025143 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/more_suggestions.scope0000644000015600001650000000057212657432773031602 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.HomeTest DBusPath=/com/canonical/unity/masterscope/moresuggestions Icon= CategoryIcon=/usr/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg Name=More suggestions Type=moresug IsMaster=true [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category more_suggestions] Name=More suggestions Icon= SortField=uri unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_b/0000755000015600001650000000000012657434047027771 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_b/subscope1.scope0000644000015600001650000000053112657432773032733 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.SubscopeB1 DBusPath=/com/canonical/unity/scope/subscopeB1 Icon=/usr/share/unity/6/icon-sub1.svg RequiredMetadata=bid[s]; OptionalMetadata= Keywords=subb1;sub1a1b Type=varia QueryPattern=^@ Name=Sample subscope 1 Description=Find various stuff subscope b1 SearchHint=Search stuff subscope b1 Shortcut=x unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_b/subscope2.scope0000644000015600001650000000045012657432773032734 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.SubscopeB2 DBusPath=/com/canonical/unity/scope/subscopeB2 Icon=/usr/share/unity/6/icon-sub2.svg Keywords=subb2; Type=various QueryPattern=^~ Name=Sample subscope 2 Description=Find various stuff subscope b2 SearchHint=Search stuff subscope b2 Shortcut=uunity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_a.scope0000644000015600001650000000057312657432773031034 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.HomeTest DBusPath=/com/canonical/unity/masterscopes/mastera Icon=/usr/share/unity/6/icon1.svg IsMaster=true RequiredMetadata=some_id[s];foo[s];bar[y]; OptionalMetadata=baz[u]; # Keywords is localized Keywords=mastera; Type=varia QueryPattern= Name=Sample Master Scope 1 Description=Find various stuff SearchHint=Search stuff Shortcut=q unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/reference.scope0000644000015600001650000000103712657432773030141 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.HomeTest DBusPath=/com/canonical/unity/masterscope/reference Icon= CategoryIcon=/usr/share/icons/unity-icon-theme/places/svg/group-reference.svg Name=Reference Type=reference IsMaster=true Keywords=reference; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category encyclopedia] Name=Encyclopedia Icon= DedupField=uri [Category vocabulary] Name=Vocabulary Icon= DedupField=uri [Category scholar] Name=Scholar Icon= DedupField=uri [Category sources] Name=Sources Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_b.scope0000644000015600001650000000056312657432773031034 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.HomeTest DBusPath=/com/canonical/unity/masterscopes/masterb Icon=/usr/share/unity/6/icon2.svg IsMaster=true RequiredMetadata=bid[s];a[s];b[y]; OptionalMetadata= # Keywords is localized Keywords=misc;booze; Type=booze QueryPattern=^( Name=Sample Master Scope 2 Description=Find even more stuff SearchHint=Search things Shortcut=wunity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_a/0000755000015600001650000000000012657434047027770 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_a/subscope1.scope0000644000015600001650000000053112657432773032732 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.SubscopeA1 DBusPath=/com/canonical/unity/scope/subscopeA1 Icon=/usr/share/unity/6/icon-sub1.svg RequiredMetadata=bid[s]; OptionalMetadata= Keywords=suba1;sub1a1b Type=varia QueryPattern=^@ Name=Sample subscope 1 Description=Find various stuff subscope a1 SearchHint=Search stuff subscope a1 Shortcut=x unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/data/unity/scopes/masterscope_a/subscope2.scope0000644000015600001650000000045012657432773032733 0ustar pbuserpbgroup00000000000000[Scope] DBusName=com.canonical.Unity.Scope.SubscopeA2 DBusPath=/com/canonical/unity/scope/subscopeA2 Icon=/usr/share/unity/6/icon-sub2.svg Keywords=suba2; Type=various QueryPattern=^~ Name=Sample subscope 2 Description=Find various stuff subscope a2 SearchHint=Search stuff subscope a2 Shortcut=uunity-scope-home-6.8.2+16.04.20160212.1/tests/unit/config-tests.vala.in0000644000015600001650000000065512657432773025457 0ustar pbuserpbgroup00000000000000namespace Config { const string VERSION = "@VERSION@"; const string TOPSRCDIR = "@abs_top_srcdir@"; const string TESTDATADIR = "@abs_top_srcdir@/tests/unit/data"; const string TESTRUNDATADIR = "@abs_top_builddir@/tests/unit"; const string DATADIR = "@DATADIR@"; const string PKGDATADIR = "@abs_top_srcdir@/tests/unit/data/unity"; const string LOCALEDIR = "@DATADIR@/locale"; const string PACKAGE = "@PACKAGE@"; } unity-scope-home-6.8.2+16.04.20160212.1/tests/unit/test-utils.vala0000644000015600001650000001545712657432773024570 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2011-2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Michal Hruby * */ using Unity.Protocol; public static bool run_with_timeout (MainLoop ml, uint timeout_ms = 5000) { bool timeout_reached = false; var t_id = Timeout.add (timeout_ms, () => { timeout_reached = true; debug ("Timeout reached"); ml.quit (); return false; }); ml.run (); if (!timeout_reached) Source.remove (t_id); return !timeout_reached; } /* A bit of magic to get proper-ish fixture support */ public interface Fixture : Object { class DelegateWrapper { TestDataFunc func; public DelegateWrapper (owned TestDataFunc f) { func = (owned) f; } } public virtual void setup () {} public virtual void teardown () {} [CCode (has_target = false)] public delegate void Callback (T ptr); private static List _tests; public static unowned TestDataFunc create (Callback cb) requires (typeof (F).is_a (typeof (Fixture))) { TestDataFunc functor = () => { var type = typeof (F); var instance = Object.new (type) as Fixture; instance.setup (); cb (instance); instance.teardown (); }; unowned TestDataFunc copy = functor; _tests.append (new DelegateWrapper ((owned) functor)); return copy; } public static unowned TestDataFunc create_static (Callback cb) { return create ((Callback) cb); } } // this will auto-disconnect signals when it goes out of scope public class SignalWrapper { unowned Object obj; ulong sig_id; public SignalWrapper (Object o, ulong signal_id) { obj = o; sig_id = signal_id; } ~SignalWrapper () { SignalHandler.disconnect (obj, sig_id); } } public static ScopeProxy? acquire_test_proxy (string name, string path) { var ml = new MainLoop (); ScopeProxy? proxy = null; ScopeProxy.new_from_dbus.begin (name, path, null, (obj, res) => { try { proxy = ScopeProxy.new_from_dbus.end (res); } catch (Error e) {} ml.quit (); }); assert (run_with_timeout (ml)); return proxy; } public static void wait_for_synchronization (Dee.Model model) { var shared_model = model as Dee.SharedModel; if (shared_model == null) return; if (shared_model.is_synchronized ()) return; SignalWrapper[] signals = {}; var ml = new MainLoop (); signals += new SignalWrapper (shared_model, shared_model.notify["synchronized"].connect (() => { ml.quit (); })); run_with_timeout (ml); } public static string open_channel (ScopeProxy proxy, ChannelType channel_type, out Dee.SerializableModel model, bool wait_for_sync = false, ChannelFlags flags = 0) { string? channel_id = null; Dee.Model? real_model = null; var ml = new MainLoop (); /* Need to use PRIVATE channel, cause standard SharedModel won't * synchronize properly when trying to connect to the model * from the same process (/bus address) */ proxy.open_channel.begin (channel_type, flags | ChannelFlags.PRIVATE, null, (obj, res) => { try { channel_id = proxy.open_channel.end (res, out real_model); if (wait_for_sync) { wait_for_synchronization (real_model); } ml.quit (); } catch (Error err) { ml.quit (); } }); assert (run_with_timeout (ml)); assert (channel_id != null); model = real_model as Dee.SerializableModel; return channel_id; } public static HashTable perform_search ( ScopeProxy proxy, string channel_id, string query, HashTable? hints = null, Dee.SerializableModel? model = null) { var ml = new MainLoop (); HashTable? reply_dict = null; proxy.search.begin (channel_id, query, hints ?? new HashTable (null, null), null, (obj, res) => { try { reply_dict = proxy.search.end (res); } catch (Error err) {} ml.quit (); }); bool got_search_signal = false; assert (run_with_timeout (ml, 10000)); assert (reply_dict != null); return reply_dict; } public static Variant[] scope_result_to_variant (Unity.ScopeResult result) { var v = new Variant[9]; v[0] = result.uri; v[1] = result.icon_hint; v[2] = result.category; v[3] = (uint) result.result_type; v[4] = result.mimetype; v[5] = result.title; v[6] = result.comment; v[7] = result.dnd_uri; v[8] = result.metadata; return v; } public static ActivationReplyRaw? activate ( ScopeProxy proxy, string channel_id, Unity.Protocol.ActionType action_type, Unity.ScopeResult result, HashTable hints) { var ml = new MainLoop (); var result_arr = scope_result_to_variant (result); Unity.Protocol.ActivationReplyRaw? activation_reply = null; proxy.activate.begin (channel_id, result_arr, action_type, hints, null, (obj, res) => { try { activation_reply = proxy.activate.end (res); } catch (Error err) { warning ("%s", err.message); } ml.quit (); }); assert (run_with_timeout (ml)); return activation_reply; } unity-scope-home-6.8.2+16.04.20160212.1/src/0000755000015600001650000000000012657434047020234 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/src/scope-manager.vala0000644000015600001650000000736512657432773023641 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { public class ScopeManager: Object { public signal void disabled_scopes_changed (); private PreferencesManager preferences = PreferencesManager.get_default (); private MasterScopesManager master_scopes_mgr = new MasterScopesManager (); private Gee.Set client_scopes = new Gee.HashSet (); private Gee.Set disabled_scopes_lut = new Gee.HashSet (); private Settings gp_settings; private const string HOMELENS_DEFAULT_VIEW = "home-lens-default-view"; private const string DISABLED_SCOPES_KEY = "disabled-scopes"; private const string REMOTE_CONTENT_KEY = "remote-content-search"; public ScopeManager () { gp_settings = new Settings ("com.canonical.Unity.Lenses"); gp_settings.bind (HOMELENS_DEFAULT_VIEW, this, "home_lens_default_view", SettingsBindFlags.DEFAULT); // bind get/set update_disabled_scopes (); preferences.notify[DISABLED_SCOPES_KEY].connect ((obj, pspec) => { update_disabled_scopes (); disabled_scopes_changed (); }); update_remote_content_search (); preferences.notify[REMOTE_CONTENT_KEY].connect ((obj, pspec) => { update_remote_content_search (); }); } private void update_remote_content_search () { remote_content_search = (preferences.remote_content_search == Unity.PreferencesManager.RemoteContent.ALL); } private void update_disabled_scopes () { disabled_scopes_lut.clear (); foreach (var scope_id in preferences.disabled_scopes) { disabled_scopes_lut.add (scope_id); } } public string[] home_lens_default_view { get; set; default = new string[0]; } public string[] disabled_scopes { get { return preferences.disabled_scopes; } } public bool remote_content_search { get; internal set; } public void start_master_scopes () { master_scopes_mgr.start (); } public bool is_disabled (string scope_id) { return disabled_scopes_lut.contains (scope_id); } /** * Returns true if scope is a client scope; shouldn't be called for master scopes. */ public bool is_client_scope (string scope_id) { var reg = ScopeRegistry.instance (); if (client_scopes.size == 0) { foreach (var id in reg.flatten ()) { if (!reg.is_master (id)) // ignore master scopes client_scopes.add (id); } } return client_scopes.contains (scope_id); } /** * Get ids of scopes with always-search flag on */ public Gee.Set get_always_search_scopes () { var scopes = new Gee.TreeSet (); foreach (string scope in preferences.always_search) { // only return scope if its in registry foreach (var topscope in ScopeRegistry.instance ().scopes) { if (topscope.scope_info.id == scope) { scopes.add (scope); break; } foreach (var subscope in topscope.sub_scopes) { if (subscope.id == scope) { scopes.add (scope); break; } } } } return scopes; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/markup-cleaner.vala0000644000015600001650000000723012657432773024015 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012-2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { namespace MarkupCleaner { /** * Regex for capturing HTML tags. It's closely related to the logic in replace_cb, * so be careful with any changes to it; don't forget to run unit tests! * First part of the regex captures <..> tags. * The last part captures '&' unless it's an entity. */ internal static const string HTML_MARKUP_RE = "(?'OPENING_BRACKET']*?)\\s*(?'CLOSING_BRACKET'/?>)|(?'ENTITY'\\&(?'ENTITY_NAME'[a-zA-Z0-9]+);)|(?'AMPERSAND'\\&(?!(#\\d+)))"; internal static bool replace_cb (MatchInfo match, StringBuilder result) { string? entire_substring = match.fetch (0).down (); /* html <..> tags */ if (match.get_match_count () == 4 && entire_substring != null) { string? opening_bracket = match.fetch_named ("OPENING_BRACKET"); string? html_tag_name = match.fetch_named ("TAG_NAME").down (); string? closing_bracket = match.fetch_named ("CLOSING_BRACKET"); if (html_tag_name == "br") { result.append ("\n"); } else if (html_tag_name == "b" || html_tag_name == "i" || html_tag_name == "u" || html_tag_name == "s" || html_tag_name == "tt" || html_tag_name == "big" || html_tag_name == "small" || html_tag_name == "sup" || html_tag_name == "sub") { result.append (entire_substring); } else if (html_tag_name == "strike") { result.append (opening_bracket + "s" + closing_bracket); } } else if (match.get_match_count () == 6) { string entity_name = match.fetch_named ("ENTITY_NAME").down (); if (entity_name == "amp" || entity_name == "quot" || entity_name == "apos" || entity_name == "lt" || entity_name == "gt") { result.append (match.fetch_named ("ENTITY").down ()); } else if (entity_name == "nbsp") { result.append (" "); } else //append raw entity name with & prefix { result.append ("&"); result.append (entity_name); } } else if (match.get_match_count () == 7 && match.fetch_named ("AMPERSAND") == "&") { result.append ("&"); } /* else - ignore all other html tags */ return false; } public static string html_to_pango_markup (string html_text) { try { var html_markup_re = new GLib.Regex (HTML_MARKUP_RE, GLib.RegexCompileFlags.MULTILINE); return html_markup_re.replace_eval (html_text, html_text.length, 0, 0, replace_cb); } catch (GLib.RegexError e) // this shouldn't really happen.. if it happens, it's a programming error { warning ("Regex compilation failed: %s", e.message); } return html_text; } } }unity-scope-home-6.8.2+16.04.20160212.1/src/remote-scope-registry.vala0000644000015600001650000000573312657432773025365 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Michal Hruby * */ namespace Unity.HomeScope { /** * Wrapper for information about remote scopes which can share the data * in a Dee.SharedModel. */ public class RemoteScopeRegistry : Object { private const string SWARM_NAME = "com.canonical.Unity.SmartScopes.RemoteScopesModel"; private Dee.Model scopes_model; private SmartScopes.RemoteScopeInfo[] remote_scopes; private Gee.TreeSet remote_scopes_lut = new Gee.TreeSet (); public RemoteScopeRegistry.for_scopes (SmartScopes.RemoteScopeInfo[] info) { Object (); remote_scopes = info; foreach (var scope in remote_scopes) { remote_scopes_lut.add (scope.scope_id); // also add a fake master scope id to the lookup var master_id = SearchUtil.get_master_id_from_scope_id (scope.scope_id); if (master_id != null) remote_scopes_lut.add (master_id); } } public Dee.Model create_model () { if (scopes_model != null) return scopes_model; var peer = Object.new (typeof (Dee.Peer), "swarm-name", SWARM_NAME, "swarm-owner", true) as Dee.Peer; var access_mode = Dee.SharedModelAccessMode.LEADER_WRITABLE; var model = Object.new (typeof (Dee.SharedModel), "peer", peer, "back-end", new Dee.SequenceModel (), "access-mode", access_mode) as Dee.Model; model.set_schema ("s", "s", "s", "s", "s", "as"); model.set_column_names ("scope_id", "name", "description", "icon", "screenshot_url", "keywords"); foreach (unowned SmartScopes.RemoteScopeInfo info in remote_scopes) { var keywords_var = new Variant.strv (info.keywords); model.append (info.scope_id, info.name, info.description, info.icon_hint, info.screenshot_url, keywords_var); } scopes_model = model; return scopes_model; } public bool has_scope (string scope_id) { return remote_scopes_lut.contains (scope_id); } /** * Get list of all remote scopes; note: this also includes master scopes. */ public unowned SmartScopes.RemoteScopeInfo[] get_scopes () { return remote_scopes; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-remote-scopes-parser.vala0000644000015600001650000000452512657432773027610 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { /** * Parser for results of /remote-scopes server request */ public class RemoteScopesParser { public RemoteScopeInfo[] parse (Json.Parser parser) throws Error { var scopes = parser.get_root ().get_array (); RemoteScopeInfo[] res = new RemoteScopeInfo [0]; scopes.foreach_element ((_array, _index, _node) => { var obj = _node.get_object (); unowned string id = obj.get_string_member ("id"); if (id == null) return; unowned string name = null; unowned string description = null; unowned string icon_hint = null; unowned string screenshot = null; if (obj.has_member ("name")) name = obj.get_string_member ("name"); if (obj.has_member ("description")) description = obj.get_string_member ("description"); if (obj.has_member ("icon")) icon_hint = obj.get_string_member ("icon"); if (obj.has_member ("screenshot")) screenshot = obj.get_string_member ("screenshot"); string[] keywords = {}; if (obj.has_member ("keywords")) { var keywords_arr = obj.get_array_member ("keywords"); foreach (unowned Json.Node element in keywords_arr.get_elements ()) { unowned string keyword = element.get_string (); if (keyword != null) keywords += keyword; } } RemoteScopeInfo info = RemoteScopeInfo () { scope_id = id, name = name, description = description, icon_hint = icon_hint, screenshot_url = screenshot, keywords = (owned) keywords }; res += (owned) info; }); return res; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/client-scopes-info.vala0000644000015600001650000000662112657432773024613 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { /** * Parses /usr/share/unity/client-scopes.json that provides list of scopes installed by default. * Computes diff between that list and scopes from the registry to provide lists of added / removed scopes. */ public class ClientScopesInfo { public static const string CLIENT_SCOPES_FILE = Config.PKGDATADIR + "/client-scopes.json"; private Gee.ArrayList added_scopes = new Gee.ArrayList (); private Gee.ArrayList removed_scopes = new Gee.ArrayList (); public ClientScopesInfo (Gee.Set default_scopes, Gee.Set installed_scopes) { compute_diff (default_scopes, installed_scopes); } public static ClientScopesInfo from_file (string path, Gee.Set installed_scopes) throws Error { var parser = new Json.Parser (); parser.load_from_file (path); var default_scopes = ClientScopesInfo.load (parser.get_root ()); var cl = new ClientScopesInfo (default_scopes, installed_scopes); return cl; } public static ClientScopesInfo from_data (string json_data, Gee.Set installed_scopes) throws Error { var parser = new Json.Parser (); parser.load_from_data (json_data); var default_scopes = ClientScopesInfo.load (parser.get_root ()); var cl = new ClientScopesInfo (default_scopes, installed_scopes); return cl; } public Gee.ArrayList get_added_scopes () { return added_scopes; } public Gee.ArrayList get_removed_scopes () { return removed_scopes; } private void compute_diff (Gee.Set default_scopes, Gee.Set installed_scopes) { foreach (var scope_id in default_scopes) { // home.scope is explicitly ignored in the registry, so add it here so that it's not // considered removed and sent in removed_scopes list. if (!installed_scopes.contains (scope_id) && scope_id != "home.scope") removed_scopes.add (scope_id); } foreach (var scope_id in installed_scopes) { if (!default_scopes.contains (scope_id)) added_scopes.add (scope_id); } } private static Gee.Set load (Json.Node node) throws Error { var default_scopes = new Gee.TreeSet (); var dict = node.get_object (); dict.foreach_member ((_obj, _name, _node) => { // _name is package name, ignore it as we only care about scope ids Json.Array pkg_scopes = _node.get_array (); pkg_scopes.foreach_element ((_array, _index, _pnode) => { default_scopes.add (_pnode.get_string ()); }); }); return default_scopes; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/Makefile.am0000644000015600001650000000422712657432777022305 0ustar pbuserpbgroup00000000000000NULL = BUILT_SOURCES = CLEANFILES = EXTRA_DIST = DATADIR = $(datadir) pkglibexec_PROGRAMS = \ unity-scope-home \ $(NULL) unity_scope_home_CPPFLAGS = \ -DDATADIR=\"$(DATADIR)\" \ -DPKGDATADIR=\"$(PKGDATADIR)\" \ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \ -DG_LOG_DOMAIN=\"unity-scope-home\" \ $(HOME_SCOPE_CFLAGS) \ $(MAINTAINER_CFLAGS) \ -I$(srcdir) \ $(COVERAGE_CFLAGS) \ $(NULL) if !ENABLE_C_WARNINGS unity_scope_home_CPPFLAGS += -w endif unity_scope_home_VALAFLAGS = \ -C \ --pkg dee-1.0 \ --pkg gee-0.8 \ --pkg unity \ --pkg unity-extras \ --pkg unity-protocol \ --pkg gio-2.0 \ --pkg gio-unix-2.0 \ --pkg glib-2.0 \ --pkg json-glib-1.0 \ --pkg libsoup-gnome-2.4 \ --pkg libsoup-2.4 \ --pkg libuuid \ --vapidir $(srcdir) \ --vapidir $(top_srcdir)/vapi \ --target-glib=2.26 \ $(MAINTAINER_VALAFLAGS) \ $(NULL) unity_scope_home_LDFLAGS = -rpath $(PROTOCOLPRIVATELIBDIR) $(COVERAGE_LDFLAGS) unity_scope_home_LDADD = \ $(HOME_SCOPE_LIBS) \ $(NULL) unity_scope_home_VALASOURCES = \ category-manager.vala \ config.vala \ keyword-search.vala \ main.vala \ scope.vala \ scope-manager.vala \ scope-registry.vala \ client-scopes-info.vala \ markup-cleaner.vala \ master-scopes.vala \ platform-info.vala \ remote-scope-registry.vala \ meta-scope-registry.vala \ search-util.vala \ filter-state.vala \ scope-definitions-parser.vala \ search-query-state.vala \ smart-scopes-interface.vala \ smart-scopes-id-map.vala \ smart-scopes-parse.vala \ smart-scopes-search.vala \ smart-scopes-metrics.vala \ smart-scopes-default-parser.vala \ smart-scopes-more-suggestions-parser.vala \ smart-scopes-remote-scopes-parser.vala \ smart-scopes-preview-parser.vala \ $(NULL) unity_scope_home_SOURCES = \ $(unity_scope_home_VALASOURCES:.vala=.c) \ $(NULL) BUILT_SOURCES += \ unity_scope_home.vala.stamp \ $(NULL) EXTRA_DIST += \ $(unity_scope_home_VALASOURCES) \ $(NULL) unity_scope_home.vala.stamp: $(unity_scope_home_VALASOURCES) $(AM_V_GEN) $(VALAC) $(unity_scope_home_VALAFLAGS) $^ @touch $@ CLEANFILES += \ *.stamp \ $(unity_scope_home_VALASOURCES:.vala=.c) \ $(NULL) unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-interface.vala0000644000015600001650000000566312657432773025475 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { public enum ScopeType { ClientScope, ServerScope } public struct RecommendedScope { string scope_id; ScopeType scope_type; } public struct RemoteScopeInfo { string scope_id; string name; string description; string icon_hint; string screenshot_url; string[] keywords; } public delegate void SmartScopeResult (string scope_id, Variant[] row); public delegate void SmartScopeRecommendations (string server_sid, List scopes); public interface SmartScopeClientInterface : Object { public signal void metrics_event_added (SmartScopesMetrics.AbstractMetricEvent event); public abstract string create_session_id (); public abstract async void search (string query, string form_factor, string session_id, string[] scopes, string[] disabled_scopes, owned SmartScopeResult result_cb, owned SmartScopeRecommendations recommend_cb, GLib.Cancellable? cancellable) throws Error; public abstract async Preview? preview (string server_sid, string session_id, string result_id, ScopeResult result, GLib.Cancellable? cancellable) throws Error; public abstract async RemoteScopeInfo[]? remote_scopes (GLib.Cancellable? cancellable) throws Error; public abstract int num_feedback_events (); public abstract async void send_feedback (GLib.Cancellable? cancellable) throws Error; public abstract void add_click_event (string session_id, string server_sid, string scope_id, DateTime timestamp); public abstract void add_preview_event (string session_id, string server_sid, string scope_id, DateTime timestamp); public abstract void add_found_event (string session_id, string server_sid, Gee.Map scope_results, DateTime timestamp); } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-parse.vala0000644000015600001650000001403012657432773024633 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { public errordomain ParseError { INVALID_JSON } public interface SingleResultParser: Object { public abstract Variant[] parse (string scope_id, Json.Object result_dict) throws ParseError; } public class SearchResponseHandler { private Json.Parser parser = new Json.Parser (); private HashTable handlers = new HashTable (str_hash, str_equal); public SearchResponseHandler () { var more_suggestions = new SmartScopes.MoreSuggestionsParser (); handlers["more_suggestions-amazon.scope"] = more_suggestions; handlers["more_suggestions-skimlinks.scope"] = more_suggestions; handlers["more_suggestions-u1ms.scope"] = more_suggestions; handlers["default"] = new SmartScopes.DefaultParser (); } public void parse_results_line (string line, SmartScopeResult result_cb, SmartScopeRecommendations recommend_cb) throws Error { if (parser.load_from_data (line)) { try { parse_results_line_json (parser.get_root ().get_object (), result_cb, recommend_cb); } catch (Error e) { throw new ParseError.INVALID_JSON ("Error parsing JSON"); } } else { warning ("Failed to parse JSON: '%s'", line); } } public void parse_results_line_json (Json.Object obj, SmartScopeResult result_cb, SmartScopeRecommendations recommend_cb) throws Error { if (obj.has_member ("type")) { var type = obj.get_string_member ("type"); if (type == "results") { handle_results (obj, result_cb); } else if (type == "recommendations") { handle_recommendations (obj, recommend_cb); } else { warning ("Unknown type: %s", type); throw new ParseError.INVALID_JSON ("Unknown type: %s", type); } } else { warning ("Missing 'type' element"); throw new ParseError.INVALID_JSON ("Missing 'type' element"); } } private void handle_results (Json.Object obj, SmartScopeResult result_cb) throws Error { if (obj.has_member ("info")) { Json.Object info = obj.get_object_member ("info"); // iterate over {"a_scope_id" : [{...},{...} ,...}]} results info.foreach_member ((_obj, _member, _node) => { var handler = handlers.lookup (_member); if (handler == null) handler = handlers["default"]; Json.Array rows = _node.get_array (); // array of result rows (each row is a dict) rows.foreach_element ((_array, _index, _arrnode) => { Json.Object _res_dict = _arrnode.get_object (); try { var row = handler.parse (_member, _res_dict); if (row.length > 0) result_cb (_member, row); } catch (ParseError e) // error in single result shouldn't break processing of other results from that scope { warning ("Parsing error: %s", e.message); } }); }); } else { warning ("Missing 'info' element"); throw new ParseError.INVALID_JSON ("Missing 'info' element"); } } private void handle_recommendations (Json.Object obj, SmartScopeRecommendations recommend_cb) throws Error { if (obj.has_member ("scopes")) { if (!obj.has_member ("server_sid")) // server_sid is used by server to match feedback events with search queries; we send it back with feedback { warning ("Missing 'server_sid' element"); throw new ParseError.INVALID_JSON ("Missing 'server_sid' element"); } var sid = obj.get_string_member ("server_sid"); var recommendations = new GLib.List (); Json.Array recommend = obj.get_array_member ("scopes"); recommend.foreach_element ((_array, _index, _node) => { Json.Array scope_elm_array = _node.get_array (); // each recommendation is an array: ["scope id", "type"], type is "server" or "client" if (scope_elm_array.get_length () == 2) { var id = scope_elm_array.get_element (0).get_string (); ScopeType tp = ScopeType.ClientScope; bool invalid_type = false; var type_str = scope_elm_array.get_element (1).get_string (); if (type_str == "client") tp = ScopeType.ClientScope; else if (type_str == "server") tp = ScopeType.ServerScope; else invalid_type = true; if (!invalid_type) { var scope_rec = RecommendedScope () { scope_id = id, scope_type = tp }; recommendations.append (scope_rec); } else { warning ("Invalid recommended scope type: %s", type_str); } } else { warning ("Invalid recommendations array"); } }); recommend_cb (sid, recommendations); } else { warning ("Missing 'scopes' element"); throw new ParseError.INVALID_JSON ("Missing 'scopes' element"); } } } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-more-suggestions-parser.vala0000644000015600001650000001412312657432773030330 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { public class MoreSuggestionsParser: SingleResultParser, Object { public Variant[] parse (string scope_id, Json.Object result_dict) throws ParseError { var category_index = CategoryManager.instance ().get_category_index (CategoryManager.MORE_SUGGESTIONS_SCOPE_ID); if (category_index < 0) { warning ("No category index for More Suggestions"); return new Variant[0]; } if (!result_dict.has_member ("metadata")) throw new ParseError.INVALID_JSON ("metadata element is missing"); var metadata_node = result_dict.get_member ("metadata"); var metadata = metadata_node.get_object (); string? result_uri = result_dict.get_string_member ("uri"); if (result_uri == null || result_uri == "") throw new ParseError.INVALID_JSON ("uri element is missing"); if (result_uri.has_prefix("scopes-query")) result_uri = "x-unity-no-preview-" + result_uri; string? price = null; if (metadata.has_member ("formatted_price")) price = metadata.get_string_member ("formatted_price"); else if (metadata.has_member ("price")) price = metadata.get_string_member ("price"); var image_obj = metadata.get_object_member ("images"); string? image_uri = null; if (image_obj != null) image_uri = extract_image_uri (image_obj, 128*128); if ((image_uri == null || image_uri == "") && result_dict.has_member ("icon_hint")) { image_uri = result_dict.get_string_member ("icon_hint"); } if (image_uri != null && image_uri != "") { var file = File.new_for_uri (image_uri); var icon = new AnnotatedIcon (new FileIcon (file)); // FIXME: dash doesn't like empty string as ribbon icon.ribbon = price == null || price == "" ? " " : price; unowned string category = ""; if (metadata.has_member ("category")) category = metadata.get_string_member ("category"); switch (category) { case "application": icon.category = CategoryType.APPLICATION; break; case "movie": icon.category = CategoryType.MOVIE; break; case "music": icon.category = CategoryType.MUSIC; break; case "book": icon.category = CategoryType.BOOK; break; default: icon.category = CategoryType.HOME; // will use generic icon break; } image_uri = icon.to_string (); } Variant? metadata_var = null; try { if (image_obj == null) metadata_node.get_object ().remove_member ("images"); // the binding is wrong, deserialize returns floating variant metadata_var = json_gvariant_deserialize (metadata_node, "a{sv}"); } catch (Error e) { warning ("Error deserializing metadata: %s", e.message); metadata_var = new Variant.array (VariantType.VARDICT.element (), {} ); } Variant row[9] = { result_uri, image_uri != null ? image_uri : "", new Variant.uint32 (0), new Variant.uint32 (Unity.ResultType.DEFAULT), new Variant.string ("text/html"), new Variant.string (result_dict.get_string_member ("title")), new Variant.string (result_dict.has_member ("comment") ? result_dict.get_string_member ("comment") : ""), new Variant.string (result_uri), metadata_var }; return row; } private HashTable get_image_uri_dict (Json.Object image_obj) { var dict = new HashTable (str_hash, str_equal); foreach (unowned string dimensions in image_obj.get_members ()) { int width, height; int res = dimensions.scanf ("%dx%d", out width, out height); if (res != 2) continue; dict[dimensions] = image_obj.get_array_member (dimensions).get_string_element (0); } return dict; } /** * extract_image_uri: * * Returns image uri with pixel size (width * height) that is more than * or equal to the given pixel size. * In case only images with smaller pixel size are available, returns * the largest of those. */ private string extract_image_uri (Json.Object? image_obj, int pixel_size) { if (image_obj == null) return ""; var dict = get_image_uri_dict (image_obj); var keys_list = get_sorted_keys_for_dim_dict (dict); if (keys_list == null) return ""; // short-circuit evaluation if (pixel_size == int.MAX) return dict[keys_list.last ().data]; foreach (unowned string dim_string in keys_list) { int width, height; dim_string.scanf ("%dx%d", out width, out height); if (width * height >= pixel_size) { return dict[dim_string]; } } return dict[keys_list.last ().data]; } private List get_sorted_keys_for_dim_dict (HashTable dict) { var list = dict.get_keys (); list.sort ((a_str, b_str) => { int width1, height1, width2, height2; a_str.scanf ("%dx%d", out width1, out height1); b_str.scanf ("%dx%d", out width2, out height2); return width1 * height1 - width2 * height2; }); return list; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/config.vala.in0000644000015600001650000000047012657432773022760 0ustar pbuserpbgroup00000000000000namespace Config { const string PREFIX = "@prefix@"; const string DATADIR = "@DATADIR@"; const string PKGDATADIR = "@DATADIR@/unity"; const string BINDIR = "@prefix@/bin"; const string LOCALEDIR = "@DATADIR@/locale"; const string PACKAGE = "@PACKAGE@"; const string VERSION = "@VERSION@"; } unity-scope-home-6.8.2+16.04.20160212.1/src/scope-definitions-parser.vala0000644000015600001650000001163512657432773026027 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Michal Hruby * */ namespace Unity.HomeScope { namespace DefinitionsParser { public static Filter? parse_filter_definition (Unity.Protocol.FilterDefinition definition) { Filter? filter = null; Icon? icon = null; FilterRenderer renderer = FilterRenderer.from_string (definition.filter_type); switch (renderer) { case FilterRenderer.CHECK_OPTIONS: filter = new CheckOptionFilter (definition.id, definition.name, icon); break; case FilterRenderer.CHECK_OPTIONS_COMPACT: filter = new CheckOptionFilterCompact (definition.id, definition.name, icon); break; case FilterRenderer.RADIO_OPTIONS: filter = new RadioOptionFilter (definition.id, definition.name, icon); break; case FilterRenderer.RATINGS: filter = new RatingsFilter (definition.id, definition.name, icon); break; case FilterRenderer.MULTIRANGE: filter = new MultiRangeFilter (definition.id, definition.name, icon); break; default: break; } var options_filter = filter as OptionsFilter; if (options_filter != null && definition.sort_type != null) { switch (definition.sort_type) { case "display-name": options_filter.sort_type = Unity.OptionsFilter.SortType.DISPLAY_NAME; break; default: break; } } unowned string[] option_ids = definition.get_option_ids (); unowned string[] option_names = definition.get_option_names (); for (int i = 0; i < option_ids.length; i++) { options_filter.add_option (option_ids[i], option_names[i]); } return filter; } public static Category? parse_category_definition (Unity.Protocol.CategoryDefinition definition, Icon? default_icon = null) { Icon? icon = null; if (definition.icon != null && definition.icon[0] != '\0') { try { icon = Icon.new_for_string (definition.icon); } catch (Error err) { icon = null; } } if (icon == null) icon = default_icon; Category? category = null; CategoryRenderer renderer = CategoryRenderer.DEFAULT; if (definition.renderer != null) { renderer = CategoryRenderer.from_string (definition.renderer); } category = new Category (definition.id, definition.name, icon, renderer); if (definition.content_type != null) { category.content_type = CategoryContentType.from_string (definition.content_type); } if (definition.renderer_hint != null) { category.renderer_hint = definition.renderer_hint; } return category; } public static void apply_category_constraints (Unity.AggregatorScope scope, Unity.Protocol.CategoryDefinition[] definitions) { var cat_ids = new HashTable (str_hash, str_equal); int i = 0; foreach (var category in scope.categories.get_categories ()) { cat_ids[category.id] = i++; } foreach (var definition in definitions) { if (!cat_ids.contains (definition.id)) continue; int cat_idx = cat_ids[definition.id]; if (definition.sort_field != null && definition.sort_field[0] != '\0') { // this supports SortField=sort_field1;sort_field2::asc; foreach (var component in definition.sort_field.split (";")) { if (component == "") continue; var order = Unity.AggregatorScope.SortFlags.ASCENDING; var field_name = component; if ("::" in component) { var sort_props = component.split ("::", 2); field_name = sort_props[0]; if (sort_props[1].down ().has_prefix ("des")) order = Unity.AggregatorScope.SortFlags.DESCENDING; } scope.add_sorter (cat_idx, field_name, order); } } if (definition.dedup_field != null && definition.dedup_field[0] != '\0' && cat_ids.contains (definition.dedup_field)) { scope.add_constraint (cat_idx, definition.dedup_field); } } } } } unity-scope-home-6.8.2+16.04.20160212.1/src/scope-registry.vala0000644000015600001650000001170512657432773024070 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { /** * Wrapper for Protocol.ScopeRegistry with some high-level helpers. */ public class ScopeRegistry { private static ScopeRegistry reg; private SList registry = new SList (); private HashTable subscope_to_master = new HashTable (str_hash, str_equal); //maps subscope id to master scope id private HashTable master_to_node = new HashTable (str_hash, str_equal); //maps master scope id to node data public bool scopes_ready { get; internal set; default = false; } //whether scopes tree has been processed on startup public GLib.SList? scopes { get { return registry; } } public static ScopeRegistry instance () { if (reg == null) reg = new ScopeRegistry (); return reg; } private ScopeRegistry () { } public unowned Protocol.ScopeRegistry.ScopeMetadata? get_master_scope_info (string scope_id) { var node = master_to_node.lookup (scope_id); if (node != null) return node.scope_info; return null; } public unowned Protocol.ScopeRegistry.ScopeRegistryNode? get_master_scope_node (string scope_id) { return master_to_node.lookup (scope_id); } public bool is_master (string scope_id) { return master_to_node.contains (scope_id); } public string? get_master_scope_id (string subscope_id) { return subscope_to_master.lookup (subscope_id); } public async void find_scopes () { debug ("Searching for scopes"); Protocol.ScopeRegistry tmp_registry = null; try { tmp_registry = yield Protocol.ScopeRegistry.find_scopes (null); debug ("Scope discovery done"); scopes_ready = true; } catch (Error e) { error ("Scope discovery failed: %s", e.message); } create_lookups (tmp_registry); } private void create_lookups (Protocol.ScopeRegistry registry) { foreach (var node in registry.scopes) { if (node.scope_info.id != "home.scope") { var sub_scopes = new GLib.SList (); foreach (var sub_scope in node.sub_scopes) { // ignore subscope if query_binary is missing; this effecitvely removes it from // the registry as seen by home scope. if (!has_binary (sub_scope.query_binary)) { debug ("Binary %s missing for %s, disabling", sub_scope.query_binary, sub_scope.id); continue; } sub_scopes.append (sub_scope); subscope_to_master.insert (sub_scope.id, node.scope_info.id); } node.sub_scopes = (owned)sub_scopes; //replace original list of subscopes for this master node // FIXME: we could disable master scope if all subscopes are disabled, however this poses issues // with remote scopes; we would need to wait for remote-scopes query to finish to know if // if this master is needed for remote results. // sort master scopes by scope_id this.registry.insert_sorted (node, (node_a, node_b) => { return strcmp (node_a.scope_info.id, node_b.scope_info.id); }); master_to_node.insert (node.scope_info.id, node); } else { debug ("Skipping scope %s", node.scope_info.id); } } } public Gee.Set flatten () { if (!scopes_ready) error ("Scopes registry not ready"); var scopes_set = new Gee.TreeSet (); foreach (var node in registry) { scopes_set.add (node.scope_info.id); foreach (var sub_scope in node.sub_scopes) { scopes_set.add (sub_scope.id); } } return scopes_set; } private bool has_binary (string? binary) { if (binary != null && binary != "" && GLib.Environment.find_program_in_path (binary) == null) return false; return true; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/keyword-search.vala0000644000015600001650000000553712657432773024046 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { public class KeywordSearch { private Regex regex; // map keywords to associated scope_id, based on Keywords definition in .scope file private HashTable?> keyword_to_scope_id = new HashTable?> (str_hash, str_equal); internal uint num_of_mappings { get { return keyword_to_scope_id.get_keys ().length (); } } public KeywordSearch () { try { regex = new Regex ("^\\s*(\\w+):\\s*(.*)$"); } catch (Error e) // this shouldn't really happen { critical ("Error in regular expression: %s", e.message); } } public void index_keywords (string scope_id, GLib.SList keywords) { foreach (var kw in keywords) { kw = kw.down (); if (!keyword_to_scope_id.contains (kw)) { keyword_to_scope_id.insert (kw, new Gee.TreeSet ()); } keyword_to_scope_id[kw].add (scope_id); debug ("Indexing %s -> %s", kw, scope_id); } } public void rebuild () { debug ("Rebuilding keyword - scope id lookup"); foreach (var node in ScopeRegistry.instance ().scopes) { index_keywords (node.scope_info.id, node.scope_info.keywords); //add master scope keywords foreach (var scope in node.sub_scopes) // add subscopes keywords { index_keywords (scope.id, scope.keywords); } } } public unowned Gee.Set? process_query (string search_query, out string new_search_query) { MatchInfo info; new_search_query = null; if (regex.match (search_query, 0, out info)) { if (info != null) { var keyword = info.fetch (1); if (keyword != null) { var query_part = info.fetch (2); unowned Gee.Set? scopes = keyword_to_scope_id.lookup (keyword.down ()); if (scopes != null) { new_search_query = query_part != null ? query_part : ""; return scopes; } } } } return null; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-default-parser.vala0000644000015600001650000000564112657432773026447 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ // workaround a problem in json-glib bindings public extern unowned Variant json_gvariant_deserialize (Json.Node node, string signature) throws Error; namespace Unity.HomeScope.SmartScopes { public class DefaultParser: SingleResultParser, Object { public Variant[] parse (string scope_id, Json.Object result_dict) throws ParseError { if (!result_dict.has_member ("metadata")) throw new ParseError.INVALID_JSON ("metadata element is missing"); var metadata_node = result_dict.get_member ("metadata"); var metadata = metadata_node.get_object (); string? result_uri = result_dict.get_string_member ("uri"); if (result_uri == null || result_uri == "") throw new ParseError.INVALID_JSON ("uri element is missing"); if (result_uri.has_prefix("scopes-query")) result_uri = "x-unity-no-preview-" + result_uri; string? icon_hint = null; if (result_dict.has_member ("icon_hint")) icon_hint = result_dict.get_string_member ("icon_hint"); Variant? metadata_var = null; try { if (metadata.has_member("images")) { var image_obj = metadata.get_object_member ("images"); // protect against "images":null if (image_obj == null) metadata_node.get_object ().remove_member ("images"); } // the binding is wrong, deserialize returns floating variant metadata_var = json_gvariant_deserialize (metadata_node, "a{sv}"); } catch (Error e) { warning ("Error deserializing metadata: %s", e.message); metadata_var = new Variant.array (VariantType.VARDICT.element (), {} ); } var uri_variant = new Variant.string (result_uri); Variant row[9] = { uri_variant, new Variant.string (icon_hint != null ? icon_hint : ""), new Variant.uint32 (0), //global category new Variant.uint32 (Unity.ResultType.DEFAULT), new Variant.string ("text/html"), new Variant.string (result_dict.get_string_member ("title")), new Variant.string (result_dict.has_member ("comment") ? result_dict.get_string_member ("comment") : ""), uri_variant, metadata_var }; return row; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/search-util.vala0000644000015600001650000002161412657432773023331 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SearchUtil { /* * Simple wrapper for GLib's source id that calls Source.remove once * it gets out of scope. */ [Compact] public class GLibSourceWrapper { public uint source_id; public SourceFunc src_cb; public GLibSourceWrapper (uint timeout, owned SourceFunc cb) { src_cb = (owned)cb; if (timeout == 0) { source_id = 0; Idle.add (cb_wrapper); } else { source_id = Timeout.add (timeout, cb_wrapper); } } private bool cb_wrapper () { bool status = src_cb (); if (!status) source_id = 0; return status; } public void remove () { if (source_id > 0) { GLib.Source.remove (source_id); source_id = 0; } } ~GLibSourceWrapper () { remove (); } } /* * Simple wrapper for signal id that disconnects it * when it gets out of scope. */ [Compact] public class SignalWrapper { public unowned Object obj; public ulong sig_id; public SignalWrapper (Object o, ulong signal_id) { obj = o; sig_id = signal_id; } ~SignalWrapper () { SignalHandler.disconnect (obj, sig_id); } } public bool update_filters (HashTable?> search_scopes, List? recommendations, Unity.AggregatedScopeSearch scope_search, bool check_result_counts) { bool changed = false; // create a lookup for recommended scopes ids and search_scopes. // recommendations include only specific scopes, so for every recommended scope insert its master as well. var rec_lookup = new Gee.TreeSet (); if (recommendations != null) { foreach (var scope in recommendations) { string master_scope_id = get_master_id_from_scope_id (scope.scope_id); rec_lookup.add (scope.scope_id); rec_lookup.add (master_scope_id); } } search_scopes.foreach ((master, subscopes) => { rec_lookup.add (master); if (subscopes != null) { foreach (var scope in subscopes) rec_lookup.add (scope); } else //no subscopes specified - assume all subscopes FIXME: use meta registry { var registry = MetaScopeRegistry.instance (); var sub_scopes = registry.get_subscopes (master); if (sub_scopes != null) { foreach (var scope_id in sub_scopes) { rec_lookup.add (scope_id); } } } }); var home_channel = scope_search.channel_id; unowned Unity.OptionsFilter categories = scope_search.get_filter ("categories") as Unity.OptionsFilter; unowned Unity.OptionsFilter sources = scope_search.get_filter ("sources") as Unity.OptionsFilter; var mgr = CategoryManager.instance (); if (categories != null) { foreach (var opt in categories.options) //master scopes { int result_count = mgr.get_result_count (home_channel, opt.id); debug ("Results for %s: %d", opt.id, result_count); bool value = false; if (rec_lookup.contains (opt.id)) value = check_result_counts == false || result_count > 0; else value = false; if (opt.active != value) { opt.active = value; changed = true; } debug ("Setting category filter %s: %s", opt.id, opt.active.to_string ()); } } else { warning ("Couldn't get categories filter"); } if (sources != null) { foreach (var opt in sources.options) { // light up subscope in sources filter if it has results int result_count = mgr.get_result_count (home_channel, opt.id); bool value = false; if (rec_lookup.contains (opt.id)) value = check_result_counts == false || result_count > 0; else value = false; if (opt.active != value) { opt.active = value; changed = true; } debug ("Setting sources filter %s: %s", opt.id, opt.active.to_string ()); } } else { warning ("Couldn't get sources filter"); if (categories == null) return false; // no filters } return changed; } public void build_search_scopes_list (string scope_id, HashTable?> search_scopes) { var registry = MetaScopeRegistry.instance (); if (!registry.is_master (scope_id)) { var master_id = get_master_id_from_scope_id (scope_id); if (master_id != null) // if null, then treat it as a non-master scope that connects directly to home { var subscopes = search_scopes.lookup (master_id); if (subscopes == null) { subscopes = new Gee.TreeSet (); search_scopes[master_id] = subscopes; } subscopes.add (scope_id); return; } } search_scopes[scope_id] = null; } /** * Populate search hints with "subscopes-filter" value, suitable for passing to search_scope. */ public void set_subscopes_filter_hint (HashTable hints, HashTable?> search_scopes, string scope_id) { var subscopes = search_scopes.lookup (scope_id); if (subscopes != null) { var src_filter_var = new GLib.Variant.strv (subscopes.to_array ()); hints ["subscopes-filter"] = src_filter_var; } } /** Populate list of master scopes and their subscopes to query based on sources and categories filters. search_scopes maps master scope_id to its subscopes. */ public void scopes_to_query_from_filters (OptionsFilter sources_filter, OptionsFilter cat_filter, HashTable?> search_scopes) { if (sources_filter == null || cat_filter == null) return; Gee.Set requested_ids = new Gee.TreeSet (); foreach (var opt in sources_filter.options) //each option is a subscope id { if (opt.active) requested_ids.add (opt.id); } foreach (var cat in cat_filter.options) { if (cat.active) requested_ids.add (cat.id); } scopes_to_query_from_requested_ids (requested_ids, search_scopes); } public string[] scopes_to_query_to_array (HashTable?> search_scopes) { string[] scopes = new string [0]; search_scopes.foreach ((master_id, subscopes) => { scopes += master_id; if (subscopes != null) { foreach (var id in subscopes) { scopes += id; } } }); return scopes; } public string? get_master_id_from_scope_id (string scope_id) { var parts = scope_id.split ("-", 2); if (parts.length == 2) return "%s.scope".printf (parts[0]); return null; } public bool scopes_to_query_from_requested_ids (Gee.Set requested_scope_ids, HashTable?> search_scopes) { bool found = false; var registry = MetaScopeRegistry.instance (); foreach (var req_scope_id in requested_scope_ids) { if (registry.is_master (req_scope_id)) { if (!found) search_scopes.remove_all (); if (!search_scopes.contains (req_scope_id)) search_scopes[req_scope_id] = null; found = true; } else // user may have requested a subscope (not a master scope), we need to find a master that needs to receive the query { var master_id = get_master_id_from_scope_id (req_scope_id); if (master_id != null) { if (!found) search_scopes.remove_all (); Gee.Set? subscopes = search_scopes.lookup (master_id); if (subscopes == null) { subscopes = new Gee.TreeSet (); } subscopes.add (req_scope_id); search_scopes[master_id] = subscopes; found = true; } else { warning ("Requested scope '%s' doesn't match any known scope id", req_scope_id); } } } return found; } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-metrics.vala0000644000015600001650000001332612657432773025176 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { public class SmartScopesMetrics: Object { public abstract class AbstractMetricEvent : Object { public int64 timestamp { get; set; } public string session_id { get ; set; } public string server_sid { get; set; } public abstract void json_for_event (Json.Builder builder); public abstract string get_event_type (); public void get_json (Json.Builder builder, string prev_session_id, string prev_server_sid) { var dict = builder.begin_object (); dict.set_member_name ("type"); dict.add_string_value (get_event_type ()); dict.set_member_name ("timestamp"); dict.add_int_value (timestamp); dict.set_member_name ("session_id"); dict.add_string_value (session_id); if (prev_session_id.length == 0 || session_id != prev_session_id || server_sid != prev_server_sid) { dict.set_member_name ("server_sid"); dict.add_string_value (server_sid); } json_for_event (dict); dict.end_object (); } } public signal void event_added (AbstractMetricEvent event); public class ClickMetricEvent : AbstractMetricEvent { public string scope_id { get; set; } public ClickMetricEvent (string session_id, string server_sid, int64 timestamp, string scope_id) { Object (session_id: session_id, server_sid: server_sid, timestamp: timestamp, scope_id: scope_id); } public override string get_event_type () { return "clicked"; } public override void json_for_event (Json.Builder builder) { builder.set_member_name ("clicked_scope"); builder.add_string_value (scope_id); } } public class PreviewMetricEvent : AbstractMetricEvent { public string scope_id { get; set; } public PreviewMetricEvent (string session_id, string server_sid, int64 timestamp, string scope_id) { Object (session_id: session_id, server_sid: server_sid, timestamp: timestamp, scope_id: scope_id); } public override string get_event_type () { return "previewed"; } public override void json_for_event (Json.Builder builder) { builder.set_member_name ("previewed_scope"); builder.add_string_value (scope_id); } } public class FoundMetricEvent : AbstractMetricEvent { public Gee.Map scope_results { get; set; } public FoundMetricEvent (string session_id, string server_sid, int64 timestamp, Gee.Map scope_results) { Object (session_id: session_id, server_sid: server_sid, timestamp: timestamp, scope_results: scope_results); } public override string get_event_type () { return "found"; } public override void json_for_event (Json.Builder builder) { builder.set_member_name ("results"); var resarray = builder.begin_array (); var iter = scope_results.map_iterator (); while (iter.next ()) { var scoperes = resarray.begin_array (); scoperes.add_string_value (iter.get_key ()); scoperes.add_int_value (iter.get_value ()); scoperes.end_array (); } resarray.end_array (); } } private Gee.ArrayList events = new Gee.ArrayList (); private Json.Generator json_generator = new Json.Generator (); private void add_event (AbstractMetricEvent ev) { events.add (ev); event_added (ev); } public void add_click_event (string session_id, string server_sid, string scope_id, DateTime timestamp) { var ev = new ClickMetricEvent (session_id, server_sid, timestamp.to_unix (), scope_id); add_event (ev); } public void add_preview_event (string session_id, string server_sid, string scope_id, DateTime timestamp) { var ev = new PreviewMetricEvent (session_id, server_sid, timestamp.to_unix (), scope_id); add_event (ev); } public void add_found_event (string session_id, string server_sid, Gee.Map scope_results, DateTime timestamp) { var ev = new FoundMetricEvent (session_id, server_sid, timestamp.to_unix (), scope_results); add_event (ev); } public int num_events { get { return events.size; } } public string get_json () { var builder = new Json.Builder (); var ev_array = builder.begin_array (); var iter = events.iterator (); string prev_session_id = ""; string prev_server_sid = ""; while (iter.next ()) { var ev = iter.get (); ev.get_json (ev_array, prev_session_id, prev_server_sid); prev_session_id = ev.session_id; prev_server_sid = ev.server_sid; } builder.end_array (); size_t len; json_generator.set_root (builder.get_root ()); return json_generator.to_data (out len); } public void clear_events () { events.clear (); } } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-search.vala0000644000015600001650000004132012657432773024770 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ // workaround a problem in json-glib bindings public extern unowned Json.Builder json_builder_add_value (Json.Builder builder, owned Json.Node node); namespace Unity.HomeScope.SmartScopes { public class SmartScopesClient : SmartScopeClientInterface, Object { public static const string SERVER = "https://productsearch.ubuntu.com"; public static const string SEARCH_URI = "/smartscopes/v1/search"; public static const string PREVIEW_URI = "/smartscopes/v1/preview"; public static const string FEEDBACK_URI = "/smartscopes/v1/feedback"; public static const string REMOTE_SCOPES_URI = "/smartscopes/v1/remote-scopes"; private SmartScopesPreviewParser preview_parser = new SmartScopesPreviewParser (); internal class SearchMessage : Object { internal Soup.Message msg; SmartScopeResult result_cb; SmartScopeRecommendations recommend_cb; private StringBuilder current_line = new StringBuilder (); private SearchResponseHandler response_handler; construct { response_handler = new SearchResponseHandler (); } internal SearchMessage (owned SmartScopeResult result_cb, owned SmartScopeRecommendations recommend_cb) { this.result_cb = (owned) result_cb; this.recommend_cb = (owned) recommend_cb; } internal void on_data_chunk (Soup.Buffer buffer) { var bld = new StringBuilder.sized (buffer.length + 1); bld.append_len ((string)buffer.data, (ssize_t)buffer.length); string buffer_str = bld.str; int eol_idx = 0; int start = 0; while (eol_idx >= 0) { eol_idx = buffer_str.index_of ("\n", start); if (eol_idx < 0) { current_line.append (buffer_str.substring (start)); } else { current_line.append (buffer_str.substring (start, eol_idx - start)); if (current_line.str != "") { try { response_handler.parse_results_line (current_line.str, result_cb, recommend_cb); } catch (Error e) { warning ("Error parsing server data: %s", e.message); } current_line.truncate (); } start = eol_idx + 1; } }; } /* Parse remaining bytes in the buffer; may happen if there was no eol in the last chunk */ internal void finalize_parsing () throws Error { if (current_line.str != "") { response_handler.parse_results_line (current_line.str, result_cb, recommend_cb); current_line.truncate (); } } internal async void do_search (Soup.Session session, string uri, GLib.Cancellable? cancellable) throws Error { debug ("Sending request: %s", uri); current_line.truncate (); msg = new Soup.Message ("GET", uri); msg.response_body.set_accumulate (false); //incoming data handled by on_data_chunk, no need to get body var sig_id = msg.got_chunk.connect (on_data_chunk); try { msg = yield queue_soup_message (session, msg, cancellable); cancellable.set_error_if_cancelled (); if (msg.status_code == 200) finalize_parsing (); } finally { SignalHandler.disconnect (msg, sig_id); } } } internal class PreviewRequest: Object { internal Soup.Message msg; public PreviewRequest () { } public static string scope_result_to_json (ScopeResult result) { Json.Generator json_generator = new Json.Generator (); var builder = new Json.Builder (); Json.Builder b = builder.begin_object (); b.set_member_name ("title"); b.add_string_value (result.title); b.set_member_name ("uri"); b.add_string_value (result.uri); b.set_member_name ("icon_hint"); b.add_string_value (result.icon_hint); b.set_member_name ("comment"); b.add_string_value (result.comment); b.set_member_name ("metadata"); var content = result.metadata.lookup ("content"); //master scopes put 'metadata' in 'content' var metadata_var = content; Json.Node meta_node = Json.gvariant_serialize (metadata_var); json_builder_add_value (b, meta_node); //workaround for API bug b.end_object (); json_generator.set_root (builder.get_root ()); size_t len; string res = json_generator.to_data (out len); return res; } public async string? request_preview (Soup.Session session, string uri, string server_sid, ScopeResult result, GLib.Cancellable? cancellable) throws Error { debug ("Sending request: %s, server_sid=%s", uri, server_sid); var serialized_result = scope_result_to_json (result); msg = new Soup.Message ("GET", uri); msg.request_headers.append ("X-SERVER-SID", server_sid ); msg.request_headers.append ("X-PREVIOUS-RESULT", serialized_result); msg = yield queue_soup_message (session, msg, cancellable); if (msg.status_code == 200) return (string)msg.response_body.data; return null; } } private Soup.Session session; private string base_uri; private string feedback_uri; private string geo_store; private PlatformInfo info; private HashTable search_requests = new HashTable (str_hash, str_equal); private HashTable preview_requests = new HashTable (str_hash, str_equal); private SmartScopesMetrics metrics; construct { metrics = new SmartScopesMetrics (); metrics.event_added.connect ((ev) => { metrics_event_added (ev); }); } public SmartScopesClient (PlatformInfo platform) { this.info = platform; init_soap (); } private void init_soap () { base_uri = Environment.get_variable ("SMART_SCOPES_SERVER"); if (base_uri == null) base_uri = SERVER; feedback_uri = "%s%s".printf (base_uri, FEEDBACK_URI); geo_store = Environment.get_variable ("SMART_SCOPES_GEO_STORE"); session = new Soup.SessionAsync (); session.ssl_use_system_ca_file = true; session.ssl_strict = true; session.user_agent = "Unity Home Scope v" + Config.VERSION; session.add_feature_by_type (typeof (SoupGNOME.ProxyResolverGNOME)); } public string create_session_id () { var sid = UUID.randomized_time_uuid (); return sid; } public async RemoteScopeInfo[]? remote_scopes (GLib.Cancellable? cancellable) throws Error { // wait for the platform info to have all properties ready if (!info.is_ready) { var sig_id = info.notify["is-ready"].connect (() => { if (info.is_ready) remote_scopes.callback (); }); yield; SignalHandler.disconnect (info, sig_id); } var uri = new Soup.URI (base_uri + REMOTE_SCOPES_URI); var query_dict = new HashTable (str_hash, str_equal); if (info.locale != null) { query_dict["locale"] = info.locale; } if (info.build_id != null) { query_dict["build_id"] = info.build_id; } if (info.country_code != null) { query_dict["country_code"] = info.country_code; } if (info.network_code != null) { query_dict["network_code"] = info.network_code; } uri.set_query_from_form (query_dict); debug ("Sending request: %s", uri.to_string (false)); var msg = new Soup.Message.from_uri ("GET", uri); msg = yield queue_soup_message (session, msg, cancellable); if (msg.status_code == 200) { var json_parser = new Json.Parser (); json_parser.load_from_data ((string)msg.response_body.data); var parser = new RemoteScopesParser (); return parser.parse (json_parser); } return null; } public async void search (string query, string form_factor, string session_id, string[] scopes, string[] disabled_scopes, owned SmartScopeResult result_cb, owned SmartScopeRecommendations recommend_cb, GLib.Cancellable? cancellable) throws Error { var uri = build_search_uri (query, form_factor, session_id, scopes, disabled_scopes); debug ("Dispatching query '%s' to %s", query, uri); if (search_requests.contains (session_id)) session.cancel_message (search_requests[session_id].msg, Soup.KnownStatusCode.CANCELLED); var searcher = new SearchMessage ((owned) result_cb, (owned) recommend_cb); search_requests[session_id] = searcher; try { yield searcher.do_search (session, uri, cancellable); } finally { search_requests.remove (session_id); } } public async Preview? preview (string server_sid, string session_id, string result_id, ScopeResult result, GLib.Cancellable? cancellable) throws Error { var uri = build_preview_uri (session_id, result_id); debug ("Dispatching preview request for session_id %s, server_sid %s, result_id %s to %s", session_id, server_sid, result_id, uri); if (search_requests.contains (session_id)) session.cancel_message (search_requests[session_id].msg, Soup.KnownStatusCode.CANCELLED); var req = new PreviewRequest (); preview_requests[session_id] = req; string? data = null; try { data = yield req.request_preview (session, uri, server_sid, result, cancellable); if (data != null) { if (verbose_debug) debug ("Got preview: %s", data); var json_parser = new Json.Parser (); json_parser.load_from_data (data); return preview_parser.parse (json_parser.get_root ().get_object ()); } else { warning ("No preview data"); } } finally { preview_requests.remove (session_id); } return null; } public int num_feedback_events () { return metrics.num_events; } public async void send_feedback (GLib.Cancellable? cancellable) throws Error { if (metrics.num_events > 0) { debug ("Sending metrics to %s, event count = %u", feedback_uri, metrics.num_events); var data = metrics.get_json (); if (Unity.HomeScope.verbose_debug) debug ("Metrics data: %s", data); var msg = new Soup.Message ("POST", feedback_uri); msg.set_request ("application/json", Soup.MemoryUse.COPY, data.data); try { msg = yield queue_soup_message (session, msg, cancellable); debug ("Metrics sending completed"); } finally { metrics.clear_events (); } } } public void add_click_event (string session_id, string server_sid, string scope_id, DateTime timestamp) { metrics.add_click_event (session_id, server_sid, scope_id, timestamp); } public void add_preview_event (string session_id, string server_sid, string scope_id, DateTime timestamp) { metrics.add_preview_event (session_id, server_sid, scope_id, timestamp); } public void add_found_event (string session_id, string server_sid, Gee.Map scope_results, DateTime timestamp) { metrics.add_found_event (session_id, server_sid, scope_results, timestamp); } internal string build_search_uri (string query, string form_factor, string session_id, string[] scopes = {}, string[] disabled_scopes = {}) { var sb = new StringBuilder (base_uri); sb.append (SEARCH_URI); sb.append ("?q="); sb.append (Uri.escape_string (query, "", false)); sb.append ("&platform="); sb.append (form_factor); sb.append_c ('-'); sb.append (info.platform); sb.append ("&session_id="); sb.append (session_id); if (info.locale != null) { sb.append ("&locale="); sb.append (info.locale); } if (info.build_id != null) { sb.append ("&build_id="); sb.append (info.build_id); } if (info.country_code != null) { sb.append ("&country_code="); sb.append (info.country_code); } if (info.network_code != null) { sb.append ("&network_code="); sb.append (info.network_code); } if (info.added_scopes.length > 0) { var added_scopes = string.joinv (",", info.added_scopes); sb.append ("&added_scopes="); sb.append (added_scopes); } if (info.removed_scopes.length > 0 || disabled_scopes.length > 0) { var rscopes = info.removed_scopes; foreach (var id in disabled_scopes) rscopes += id; var removed_scopes = string.joinv (",", rscopes); sb.append ("&removed_scopes="); sb.append (removed_scopes); } if (scopes.length > 0) { var scopes_str = string.joinv (",", scopes); sb.append ("&scopes="); sb.append (scopes_str); } if (geo_store != null && geo_store.length > 0) { sb.append ("&geo_store="); sb.append (geo_store); } return sb.str; } internal string build_preview_uri (string session_id, string result_id) { var sb = new StringBuilder (base_uri); sb.append (PREVIEW_URI); sb.append ("?session_id="); sb.append (Uri.escape_string (session_id, "", false)); sb.append ("&result_id="); sb.append (Uri.escape_string (result_id, "", false)); if (info.locale != null) { sb.append ("&locale="); sb.append (info.locale); } return sb.str; } } public async Soup.Message? queue_soup_message (Soup.Session session, Soup.Message msg, GLib.Cancellable? cancellable) throws Error { session.queue_message (msg, (session_, msg_) => { msg = msg_; queue_soup_message.callback (); }); var cancelled = false; ulong cancel_id = 0; if (cancellable != null) { cancel_id = cancellable.cancelled.connect (() => { cancelled = true; session.cancel_message (msg, Soup.KnownStatusCode.CANCELLED); }); } yield; if (cancelled) { // we can't disconnect right away, as that would deadlock (cause // cancel_message doesn't return before invoking the callback) Idle.add (queue_soup_message.callback); yield; cancellable.disconnect (cancel_id); throw new IOError.CANCELLED ("Cancelled"); } if (cancellable != null) cancellable.disconnect (cancel_id); if (msg.status_code < 100) throw new IOError.FAILED ("Request failed with error %u", msg.status_code); if (msg.status_code != 200) { warning ("Request returned status code %u", msg.status_code); if (msg.response_body.data != null) debug ("Response body: %s", (string)msg.response_body.data); } return msg; } } unity-scope-home-6.8.2+16.04.20160212.1/src/main.vala0000644000015600001650000000451312657432773022034 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2012 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ using GLib; using Config; namespace Unity.HomeScope { static Application? app = null; static HomeScope? scope = null; static bool verbose_debug = false; void log_handler (string? domain, GLib.LogLevelFlags levels, string message) { var time_val = TimeVal (); string cur_time = time_val.to_iso8601 ().substring (11); stdout.printf ("%s %s: %s\n", cur_time, domain, message); } public static int main (string[] args) { GLib.Environment.set_prgname ("unity-scope-home"); // if HOME_SCOPE_VERBOSE env var is set, enable custom log handler which prints out timestamps if (Environment.get_variable ("HOME_SCOPE_VERBOSE") != null) { verbose_debug = true; GLib.Log.set_handler ("unity-scope-home", GLib.LogLevelFlags.LEVEL_DEBUG, log_handler); } /* Sort up locale to get translations but also sorting and * punctuation right */ GLib.Intl.textdomain (Config.PACKAGE); GLib.Intl.bindtextdomain (Config.PACKAGE, Config.LOCALEDIR); GLib.Intl.bind_textdomain_codeset (Config.PACKAGE, "UTF-8"); GLib.Intl.setlocale(GLib.LocaleCategory.ALL, ""); try { // read scope files before initializing dbus HomeScope.discover_scopes_sync (); app = Extras.dbus_own_name ("com.canonical.Unity.Scope.Home", () => { scope = new HomeScope (); }); } catch (Error e) { warning ("Failed to start home lens daemon: %s\n", e.message); return 1; } if (app == null) { warning ("Another instance of the Unity Home Lens already appears to be running.\nBailing out.\n"); return 2; } return app.run (); } } /* namespace */ unity-scope-home-6.8.2+16.04.20160212.1/src/meta-scope-registry.vala0000644000015600001650000000504512657432773025014 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { /** * Helper scope registry that contains both local and remote scopes. */ public class MetaScopeRegistry { private static MetaScopeRegistry mreg = null; private Gee.HashMap> scopes = new Gee.HashMap> (); public static MetaScopeRegistry instance () { if (mreg == null) mreg = new MetaScopeRegistry (); return mreg; } public void update (ScopeRegistry local_registry, RemoteScopeRegistry? remote_registry) { scopes.clear (); foreach (var node in local_registry.scopes) { var subscopes = new Gee.TreeSet (); if (node.sub_scopes != null) { foreach (var scope in node.sub_scopes) { subscopes.add (scope.id); } } scopes[node.scope_info.id] = subscopes; } if (remote_registry != null) { foreach (var scope in remote_registry.get_scopes ()) { var master_id = SearchUtil.get_master_id_from_scope_id (scope.scope_id); if (scopes.has_key (master_id)) { scopes[master_id].add (scope.scope_id); } else { warning ("Master scope for %s doesn't exist", scope.scope_id); } } } debug ("Meta registry updated with %u master scopes", scopes.size); } public bool is_master (string scope_id) { return scopes.has_key (scope_id); } public Gee.Set? get_subscopes (string master_scope_id) { if (!scopes.has_key (master_scope_id)) return null; return scopes[master_scope_id]; } public bool has_subscopes (string master_scope_id) { if (!scopes.has_key (master_scope_id)) return false; return scopes[master_scope_id].size > 0; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/platform-info.vala0000644000015600001650000001131312657432773023661 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { public class PlatformInfo: Object { public string platform { get; set; } public string locale { get; set; } public string[] added_scopes { get; set; } public string[] removed_scopes { get; set; } public string build_id { get; set; } public string country_code { get; set; } public string network_code { get; set; } public bool is_ready { get; private set; default = true; } private PlatformInfo () { } public PlatformInfo.with_data (string platform, string? locale, string[] added, string[] removed) { this.platform = platform; this.locale = locale; this.added_scopes = added; this.removed_scopes = removed; } public static PlatformInfo gather_platform_info (ClientScopesInfo? client_scopes_info) { var info = new PlatformInfo (); info.platform = get_release_string (); var languages = GLib.Intl.get_language_names (); info.locale = languages[0]; //list is sorted from most desirable to least desirable, pick the first one if (client_scopes_info != null) { info.added_scopes = client_scopes_info.get_added_scopes ().to_array (); info.removed_scopes = client_scopes_info.get_removed_scopes ().to_array (); } /* TODO: switch to using hybris directly once it's on the desktop */ if (Environment.find_program_in_path ("getprop") != null) { try { string process_stdout; int exit_status; Process.spawn_command_line_sync ("getprop ro.build.id", out process_stdout, null, out exit_status); var stripped = process_stdout.strip (); if (exit_status == 0 && stripped != null && stripped.length > 0) { info.build_id = stripped; } } catch (Error err) { warning ("Error getting build ID: %s", err.message); } } // get the country_code and network_code from the sim card, note that // this is done asynchronously, so when this function returns, the props // won't be set yet info.read_sim_properties.begin (); return info; } private async void read_sim_properties () { try { is_ready = false; if (Environment.get_variable ("HOME_SCOPE_IGNORE_OFONO") == null) { var connection = yield Bus.get (BusType.SYSTEM, null); var reply = yield connection.call ("org.ofono", "/ril_0", "org.ofono.SimManager", "GetProperties", null, new VariantType ("(a{sv})"), DBusCallFlags.NONE, -1, null); reply = reply.get_child_value (0); var mcc_v = reply.lookup_value ("MobileCountryCode", VariantType.STRING); var mnc_v = reply.lookup_value ("MobileNetworkCode", VariantType.STRING); if (mcc_v != null) country_code = mcc_v.get_string (); if (mnc_v != null) network_code = mnc_v.get_string (); } } catch (Error err) { warning ("Unable to read SIM properties: %s", err.message); } finally { is_ready = true; } } internal static string get_release_string () { // obtain platform version from lsb_release. // the form-factor can be different per-search, so that is not saved here // ubuntu_version tool needs to be used instead when it's available. try { string data; if (GLib.Process.spawn_sync (null, {"lsb_release", "-s", "-r"}, null, SpawnFlags.SEARCH_PATH , null, out data, null, null)) { if (data != null) return data.strip (); } } catch (Error e) { warning ("Failed to process lsb-release info: %s", e.message); } return "unknown"; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-id-map.vala0000644000015600001650000000465312657432773024702 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { /** * Helper class that stores home_channel_id -> session_id and home_channel_id -> server_sid mappings. */ public class ChannelIdMap { private struct ServerIds { string session_id; string? server_sid; } private HashTable ids = new HashTable (str_hash, str_equal); public void remove_channel (string channel_id) { ids.remove (channel_id); } public bool has_session_id_for_channel (string channel_id) { return ids.contains (channel_id); } public bool has_server_sid_for_channel (string channel_id) { var id = ids.lookup (channel_id); if (id != null) return id.server_sid != null; return false; } public string? session_id_for_channel (string channel_id) { var id = ids.lookup (channel_id); if (id != null) return id.session_id; return null; } public string? server_sid_for_channel (string channel_id) { var id = ids.lookup (channel_id); if (id != null) return id.server_sid; return null; } public void map_session_id (string channel_id, string session_id) { var id = ServerIds () { session_id = session_id, server_sid = null }; ids[channel_id] = id; } public bool map_server_sid (string channel_id, string server_sid) { var id = ids.lookup (channel_id); if (id != null) { id.server_sid = server_sid; ids[channel_id] = id; } else { warning ("Can't map server_sid without session_id mapping for channel %s", channel_id); return false; } return true; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/category-manager.vala0000644000015600001650000005035112657432773024336 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { /** * Keeps the list of all categories displayed in Home Lens (i.e. list of master scopes ids). * Keeps results counts for all of them (personal / semi-personal / default results). */ public class CategoryManager: GLib.Object { public static const string APP_SCOPE_ID = "applications.scope"; public static const string MORE_SUGGESTIONS_SCOPE_ID = "more_suggestions.scope"; public static const int RESULT_CATEGORY_COLUMN = 2; public static const int RESULT_TYPE_COLUMN = 3; //result-type column in the model public static const int RESULT_NAME_COLUMN = 5; //title column in the results model public static const int RESULT_COMMENT_COLUMN = 6; //comment column in the results model public static const int RESULT_METADATA_COLUMN = 8; public static const int RESULT_TYPE_MAX = 2; //warning! keep in sync with ResultType enum private Dee.TextAnalyzer analyzer = new Dee.TextAnalyzer (); private Dee.ICUTermFilter icu_filter = new Dee.ICUTermFilter.ascii_folder (); private struct SessionKey { string home_channel_id; string scope_id; public static uint hash (SessionKey? k) { if (k != null) return str_hash ("%s:%s".printf (k.home_channel_id, k.scope_id)); return str_hash (""); } public static bool equal (SessionKey? k1, SessionKey? k2) { if (k1 != null && k2 != null) return k1.home_channel_id == k2.home_channel_id && k1.scope_id == k2.scope_id; return k1 == k2; } } //map result counts for each category, per session established by (home_channel_id+scope_id) private HashTable category_counts = new HashTable (SessionKey.hash, SessionKey.equal); //map scope id to its default index; this is static data that doesn't depend on session context private HashTable catidx = new HashTable (str_hash, str_equal); //all scope ids in the order they were registered on startup, provides reverse mapping to catidx private Gee.ArrayList categories = new Gee.ArrayList (); private int cat_cnt = 0; // maps home results model to home channel id, needed by row_removed handler. private HashTable home_models = new HashTable (direct_hash, direct_equal); private HashTable dconf_order = new HashTable (str_hash, str_equal); internal class CategoryData { public string scope_id { get; set; } public int num_default_results { get { return results[Unity.ResultType.DEFAULT]; } } public int num_semi_personal_results { get { return results[Unity.ResultType.SEMI_PERSONAL]; } } public int num_personal_results { get { return results[Unity.ResultType.PERSONAL]; } } public int num_total_results { get { return num_default_results + num_personal_results + num_semi_personal_results; } } public int recommended_order { get; set; default = -1; } public int dconf_order { get; set; default = -1; } internal int results[3]; //FIXME remove magic public CategoryData (string scope_id) { this.scope_id = scope_id; results = {0}; } } private static CategoryManager mgr; private CategoryManager () { analyzer.add_term_filter ((terms_in, terms_out) => { for (uint i = 0; i < terms_in.num_terms (); i++) terms_out.add_term (icu_filter.apply (terms_in.get_term (i))); }); } public static CategoryManager instance () { if (mgr == null) { mgr = new CategoryManager (); } return mgr; } public void set_default_sort_order (Gee.ArrayList scope_ids) { int cnt = 0; dconf_order.remove_all (); foreach (var id in scope_ids) dconf_order[id] = cnt++; } public void set_default_sort_order_from_dconf () { PreferencesManager preferences = PreferencesManager.get_default (); var scopes = new Gee.ArrayList (); foreach (var scope_id in preferences.home_lens_priority) scopes.add (scope_id); set_default_sort_order (scopes); } public void clear () { category_counts.remove_all (); catidx.remove_all (); categories.clear (); cat_cnt = 0; home_models.remove_all (); dconf_order.remove_all (); } public void register (string scope_id) { debug ("Registering category for %s with index %u", scope_id, cat_cnt); categories.add (scope_id); catidx[scope_id] = cat_cnt++; } internal HashTable create_recommended_order_map (List recommendations) { int cnt = 0; var recommended_order = new HashTable (str_hash, str_equal); foreach (var rec in recommendations) { var master_id = SearchUtil.get_master_id_from_scope_id (rec.scope_id); if (master_id != null && !recommended_order.contains (master_id)) recommended_order[master_id] = cnt++; } return recommended_order; } /** * Checks if words from the search_string match title/column of first five rows in the home_model in given category. * Used for application scope results. */ internal bool contains_visible_match (Dee.Model home_model, uint category, string search_string) { // based on Unity HomeLens implementation of the same function (almost 1:1 copy, modulo vala specific stuff) if (search_string.length == 0) return true; var model = new Dee.SequenceModel (); model.set_schema ("s", "s", null); // find first 5 rows of specific category of home model, copy title & comment into temporary model int checked_results = 5; var iter = binary_search (home_model, category); if (iter == null) return false; var end_iter = home_model.get_last_iter (); while (iter != end_iter && checked_results > 0) { var catidx = model.get_uint32 (iter, RESULT_CATEGORY_COLUMN); if (catidx != category) // results are sorted by categories, so break as soon as different category is has been reached break; var name = home_model.get_value (iter, RESULT_NAME_COLUMN); var comment = home_model.get_value (iter, RESULT_COMMENT_COLUMN); Variant data[2] = {name, comment}; model.append_row (data); --checked_results; iter = home_model.next (iter); } if (model.get_n_rows () == 0) return false; // model reader that concatenates title and comment, separated by newline var reader = Dee.ModelReader.new ((m, iter) => { return "%s\n%s".printf (m.get_string (iter, 0), m.get_string (iter, 1)); }); var index = new Dee.TreeIndex (model, analyzer, reader); // tokenize the search string, so this will work with multiple words Dee.TermList search_terms = Object.new (typeof (Dee.TermList)) as Dee.TermList; analyzer.tokenize (search_string, search_terms); var iters = new Gee.TreeSet (); // iterate over all terms from search query for (uint i = 0; i (); while (results.has_next ()) iters2.add (results.next ()); // intersect the sets of rows for 1st term and other terms, e.g. if query has three words: "foo bar baz" // then we get three sets of row iterators (for all rows that contain any of these words) that we intersect // to find out if all words are in same row. var it = iters.iterator (); while (it.has_next ()) { it.next (); if (iters2.contains (it.get ()) == false) it.remove (); } // no need to check more terms if the base set is already empty if (iters.is_empty) break; } } // there is a match if the iterator is isn't empty return !iters.is_empty; } internal static Dee.ModelIter? binary_search (Dee.Model model, uint category) { if (model.get_n_rows () == 0) return null; uint l = 0; uint r = model.get_n_rows () - 1; while (l <= r) { uint i = (l + r) / 2; var iter = model.get_iter_at_row (i); var cat = model.get_uint32 (iter, RESULT_CATEGORY_COLUMN); if (category < cat) { r = i - 1; } else if (category > cat) { l = i + 1; } else { // iterate back to find first row with that category while (i > 0) { var prev = model.get_iter_at_row (--i); cat = model.get_uint32 (prev, RESULT_CATEGORY_COLUMN); if (cat != category) break; iter = prev; } return iter; } } return null; } internal static int cmp_category_data (CategoryData a, CategoryData b) { // Sort categories by number of personal / semi-personal / default results and recommendations from Smart Scope Service. // Personal results have highest priority. if (a.num_personal_results == b.num_personal_results) { if (a.num_semi_personal_results == b.num_semi_personal_results) { // apply sorting defined by dconf unity-homelens-priority key // - if one of the two scopes has dconf order defined (and the other doesn't), it comes first // - if both scopes have dconf order defined, then just sort by it (compute the difference). // - if none of them have dconf order defined, proceed with sorting by recommendations if (a.dconf_order >= 0 && a.num_total_results > 0) { if (b.dconf_order >= 0 && b.num_total_results > 0) return a.dconf_order - b.dconf_order; return -1; // A comes first } // no dconf order for A if (b.dconf_order >= 0 && b.num_total_results > 0) return 1; // B comes first // apply sorting suggested by recommendations from the server: // - if scope A and B both have defined recommended order, just sort by it (return the difference) // - if only one of them has a defined recommended order, prefer it by returning -1 or 1 respectively // - if neiter A nor B has a defined recommended order, sort by number of default results. if (a.recommended_order >=0 && a.num_total_results > 0) { if (b.recommended_order >= 0 && b.num_total_results > 0) return a.recommended_order - b.recommended_order; return -1; // A comes first } if (b.recommended_order >= 0 && b.num_total_results > 0) return 1; // B comes first if (a.num_default_results == b.num_default_results) { if (a.recommended_order >= 0 || b.recommended_order >= 0) { int delta_rec = a.recommended_order - b.recommended_order; if (a.recommended_order < 0) return 1; // B comes first if (b.recommended_order < 0) return -1; // A comes first return delta_rec; } } // just sort by number of default results return b.num_default_results - a.num_default_results; } else { return b.num_semi_personal_results - a.num_semi_personal_results; } } return b.num_personal_results - a.num_personal_results; } public Gee.ArrayList sort_categories (string search_string, string home_channel, Dee.Model home_model, List recommendations) { var recommended_order = create_recommended_order_map (recommendations); var result = new Gee.ArrayList (); var cats = new List (); // iterate over all registered categories (scope ids), insert+sort them into cats list. foreach (unowned string id in catidx.get_keys ()) { // sort categories by number of personal / semi-personal / default results var key = SessionKey () { home_channel_id = home_channel, scope_id = id }; // // support the case when given subscope returned 0 results if (!category_counts.contains (key)) { var counts = new CategoryData (id); category_counts[key] = counts; } category_counts[key].recommended_order = (recommended_order.contains (id) ? recommended_order[id]: -1); category_counts[key].dconf_order = (dconf_order.contains (id) ? dconf_order[id]: -1); cats.insert_sorted (category_counts[key], (CompareFunc)cmp_category_data); }; foreach (var cat in cats) { result.add (cat.scope_id); } if (result.size > 0) { var app_key = SessionKey () { home_channel_id = home_channel, scope_id = APP_SCOPE_ID }; // apply special ordering of app scope if it contains visual match if (category_counts.contains (app_key) && category_counts[app_key].num_total_results > 0) { if (result[0] != APP_SCOPE_ID) // nothing to do if apps category is already 1st { var app_scope_cat = get_category_index (APP_SCOPE_ID); if (contains_visible_match (home_model, app_scope_cat, search_string)) { result.remove (APP_SCOPE_ID); result.insert (0, APP_SCOPE_ID); } } } // apply special ordering for 'more suggestions' (needs to be 3rd if not empty) if (result.size > 1) { var shopping_key = SessionKey () { home_channel_id = home_channel, scope_id = MORE_SUGGESTIONS_SCOPE_ID }; if (category_counts.contains (shopping_key) && category_counts[shopping_key].num_total_results > 0) { int idx = result.index_of (MORE_SUGGESTIONS_SCOPE_ID); if (idx >= 0 && idx != 2) { result.remove_at (idx); // sorting guarantees, that categories with 0 results are last, no matter if they are in default dconf order or recommendations if (result.size >= 3) result.insert (2, MORE_SUGGESTIONS_SCOPE_ID); else result.add (MORE_SUGGESTIONS_SCOPE_ID); } } } } return result; } public uint32[] get_category_order (string search_string, string home_channel, Dee.Model home_model, List recommendations) { var res = new Gee.LinkedList (); var cats = CategoryManager.instance ().sort_categories (search_string, home_channel, home_model, recommendations); foreach (var scope_id in cats) { var idx = CategoryManager.instance ().get_category_index (scope_id); res.add (idx); debug ("Category order: %s (index %d)", scope_id, idx); } return res.to_array (); } /** * Return static index of category associated with given scope in home scope, or -1 for uknown scope_id. */ public int get_category_index (string scope_id) { if (catidx.contains (scope_id)) return catidx[scope_id]; return -1; } public int get_result_count (string home_channel, string scope_id) { var key = SessionKey () { home_channel_id = home_channel, scope_id = scope_id }; var count = category_counts.lookup (key); if (count != null) return count.num_total_results; return -1; } public string? get_scope_id_by_category_index (uint32 idx) { if (idx < categories.size) return categories[(int)idx]; return null; } private void home_model_removed (Object obj) { debug ("Home model removed"); if (home_models.contains (obj)) { var channel = home_models[obj]; home_models.remove (obj); category_counts.foreach_remove ((k, v) => { return k.home_channel_id == channel; }); } } public void observe (string home_channel_id, Dee.Model home_model) { if (!home_models.contains (home_model)) { debug ("Observing home model for channel %s", home_channel_id); home_model.row_added.connect (on_row_added); home_model.row_removed.connect (on_row_removed); home_model.weak_ref (home_model_removed); home_models[home_model] = home_channel_id; } } /** * Increase personal/semi-personal/public results counters when row is added to home scope model. */ private void on_row_added (Dee.Model model, Dee.ModelIter iter) { update_result_counts (model, iter, 1); } /** * Decrease personal/semi-personal/public results counters when row is removed from home scope model. */ private void on_row_removed (Dee.Model model, Dee.ModelIter iter) { update_result_counts (model, iter, -1); } private void update_result_counts (Dee.Model model, Dee.ModelIter iter, int value) { var home_channel_id = home_models.lookup (model); if (home_channel_id == null) { warning ("No home channel mapping found for model"); return; } var result_type = model.get_uint32 (iter, RESULT_TYPE_COLUMN); var category_idx = model.get_uint32 (iter, RESULT_CATEGORY_COLUMN); var id = get_scope_id_by_category_index (category_idx); if (id != null) { // update result count for master scope category update_result_counts_for_scope_id (home_channel_id, id, result_type, value); } else { warning ("No scope_id mapping for category index %u", category_idx); } // update result count for subscope id stored in metadata column (needed by metrics) var metadata = model.get_value (iter, RESULT_METADATA_COLUMN); var content = metadata.lookup_value ("content", VariantType.VARDICT); if (content != null) { var scope_id_var = content.lookup_value ("scope-id", VariantType.STRING); if (scope_id_var != null) { update_result_counts_for_scope_id (home_channel_id, scope_id_var.get_string (), result_type, value); } } } private void update_result_counts_for_scope_id (string home_channel_id, string scope_id, uint result_type, int value) { var key = SessionKey () { home_channel_id = home_channel_id, scope_id = scope_id }; if (result_type <= RESULT_TYPE_MAX) { var counts = category_counts.lookup (key); if (counts == null) { counts = new CategoryData (scope_id); category_counts[key] = counts; } counts.results[result_type] += value; } else { warning ("Result type out of range for %s", scope_id); } } } } unity-scope-home-6.8.2+16.04.20160212.1/src/smart-scopes-preview-parser.vala0000644000015600001650000001473312657432773026506 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope.SmartScopes { public class SmartScopesPreviewParser { private HashTable handlers = new HashTable (str_hash, str_equal); public SmartScopesPreviewParser () { // preview-series is not used, so not supported here handlers["preview-generic"] = new PreviewGenericParser (); handlers["preview-music"] = new PreviewMusicParser (); } public Preview? parse (Json.Object obj) { unowned string renderer_name = obj.get_string_member ("renderer_name"); var parser = handlers.lookup (renderer_name); if (parser != null) { return parser.parse (obj); } warning ("No handler for preview type %s", renderer_name); return handlers["preview-generic"].parse (obj); } } public interface PreviewJsonParser: Object { internal void get_base_attributes (Json.Object obj, out string title, out string subtitle, out string description, out GLib.Icon? icon, out string? attribution) { title = obj.get_string_member ("title"); subtitle = obj.get_string_member ("subtitle"); description = ""; if (obj.has_member ("description_html")) description = MarkupCleaner.html_to_pango_markup (obj.get_string_member ("description_html")); if (description.length == 0 && obj.has_member ("description")) description = obj.get_string_member ("description"); if (obj.has_member ("attribution")) { attribution = obj.get_string_member ("attribution"); if (attribution != null && attribution != "") description += "\n──────────────\n" + Markup.escape_text (attribution) + ""; } if (obj.has_member ("image_hint")) { var icon_uri = obj.get_string_member ("image_hint"); var icon_file = File.new_for_uri (icon_uri); icon = new GLib.FileIcon (icon_file); } else { icon = null; } } internal void get_info_hints (Json.Object obj, Preview preview) { if (obj.has_member ("info")) { var info_array = obj.get_array_member ("info"); info_array.foreach_element ((_array, _index, _node) => { var info_obj = _node.get_object (); var info_hint_id = info_obj.get_string_member ("id"); var info_icon_hint = info_obj.get_string_member ("icon_hint"); var info_name = info_obj.get_string_member ("display_name"); var info_value = info_obj.get_string_member ("value"); GLib.Icon info_icon = null; if (info_icon_hint != null && info_icon_hint != "") { var icon_file = GLib.File.new_for_uri (info_icon_hint); info_icon = new GLib.FileIcon (icon_file); } var info = new Unity.InfoHint (info_hint_id, info_name, info_icon, info_value); preview.add_info (info); }); } } internal void get_actions (Json.Object obj, Preview preview) { if (obj.has_member ("actions")) { var actions_array = obj.get_array_member ("actions"); actions_array.foreach_element ((_array, _index, _node) => { var action_obj = _node.get_object (); var action_icon_hint = action_obj.get_string_member ("icon_hint"); var action_name = action_obj.get_string_member ("display_name"); var action_uri = action_obj.get_string_member ("activation_uri"); var action_icon_file = GLib.File.new_for_uri (action_icon_hint); var action_icon = new GLib.FileIcon (action_icon_file); // pass activation uri in action id; this is workaround for // https://bugs.launchpad.net/libunity/+bug/1243623 to intercept // action activation calls and have metrics for it; // PreviewAction.with_uri can't be used here because it's handled completly // in Unity and doesn't call into scope. var action = new Unity.PreviewAction (action_uri, action_name, action_icon); if (action_obj.has_member ("extra_text")) action.extra_text = action_obj.get_string_member ("extra_text"); preview.add_action (action); }); } } public abstract Preview? parse (Json.Object obj); } public class PreviewGenericParser: PreviewJsonParser, Object { public Preview? parse (Json.Object obj) { string title, subtitle, description; string? attribution; GLib.Icon? icon; get_base_attributes (obj, out title, out subtitle, out description, out icon, out attribution); var preview = new GenericPreview (title, description, icon); preview.subtitle = subtitle; get_actions (obj, preview); get_info_hints (obj, preview); return preview; } } public class PreviewMusicParser: PreviewJsonParser, Object { public Preview? parse (Json.Object obj) { string title, subtitle, description; string? attribution; GLib.Icon? icon; get_base_attributes (obj, out title, out subtitle, out description, out icon, out attribution); var preview = new MusicPreview (title, subtitle, icon); get_actions (obj, preview); get_info_hints (obj, preview); var tracks_array = obj.get_array_member ("tracks"); tracks_array.foreach_element ((_array, _index, _node) => { var track_obj = _node.get_object (); var track = new TrackMetadata (); track.uri = track_obj.get_string_member ("uri"); track.title = track_obj.get_string_member ("title"); track.track_no = (int)track_obj.get_int_member ("track_no"); track.length = (uint)track_obj.get_int_member ("length"); preview.add_track (track); }); return preview; } } } unity-scope-home-6.8.2+16.04.20160212.1/src/master-scopes.vala0000644000015600001650000001023112657432773023667 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { /** * Maintains master scopes instances. */ public class MasterScopesManager { private List master_scopes = new List (); public void start () { foreach (var node in ScopeRegistry.instance ().scopes) { var info = node.scope_info; // if NoExport is defined it's considered an external master scope if (info.is_master && !info.no_export) { Icon? main_icon = null; if (info.category_icon != null && info.category_icon.length > 0) { var icon_file = File.new_for_path (info.category_icon); main_icon = new GLib.FileIcon (icon_file); } else { main_icon = new GLib.ThemedIcon ("generic"); //fallback } var category_set = new Unity.CategorySet (); // all masters need to have 'global' category because of category merging in home scope bool defines_global_category = false; foreach (var cat_def in info.get_categories ()) { if (cat_def.id == "global") { defines_global_category = true; break; } } if (!defines_global_category) { var global_cat = new Unity.Category ("global", info.name, main_icon); category_set.add (global_cat); } foreach (var cat_def in info.get_categories ()) { var category = DefinitionsParser.parse_category_definition (cat_def, main_icon); if (category == null) continue; category_set.add (category); } var filters = new FilterSet (); foreach (var filter_def in info.get_filters ()) { var filter = DefinitionsParser.parse_filter_definition (filter_def); if (filter == null) continue; filters.add (filter); } var scope = new Unity.MasterScope (info.dbus_path, info.id); scope.search_hint = info.search_hint; scope.categories = category_set; scope.filters = filters; var schema = new Unity.Schema (); if (info.required_metadata != null) { var metadata_arr = info.required_metadata.columns; for (int i = 0; i < metadata_arr.length; i++) { var column = metadata_arr[i]; schema.add_field (column.name, column.type_id, Unity.Schema.FieldType.REQUIRED); } } if (info.optional_metadata != null) { var metadata_arr = info.optional_metadata.columns; for (int i = 0; i < metadata_arr.length; i++) { var column = metadata_arr[i]; schema.add_field (column.name, column.type_id, Unity.Schema.FieldType.OPTIONAL); } } scope.schema = schema; DefinitionsParser.apply_category_constraints (scope, info.get_categories ()); if (info.no_content_hint != null) { scope.no_content_hint = info.no_content_hint; } master_scopes.append (scope); debug ("Start master scope: %s", info.id); try { scope.export (); } catch (Error e) { warning ("Failed to export master scope %s: %s", node.scope_info.id, e.message); } } } } } } unity-scope-home-6.8.2+16.04.20160212.1/src/scope.vala0000644000015600001650000013165312657432773022227 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { const string ICON_PATH = Config.DATADIR + "/icons/unity-icon-theme/places/svg/"; const int ICON_COLUMN = 1; // metrics will get flushed and sent to the server after METRICS_MIN_NUM_EVENTS, or every METRICS_SEND_INTERVAL (whichever comes first). const int METRICS_SEND_INTERVAL_SECS = 600; const int METRICS_MIN_NUM_EVENTS = 10; const int REMOTE_SCOPES_INITIAL_RETRY_INTERVAL_SECS = 2; //initial one, it will get increased with each retry const int REMOTE_SCOPES_RETRY_INTERVAL_MAX_SECS = 600; //will retry after that many seconds tops const uint SMART_SCOPES_RECOMMENDATIONS_CUTOFF = 5; //how many of the recommended scopes should be taken into account const int SMART_SCOPES_QUERY_MIN_DELAY_MS = 50; const int SMART_SCOPES_QUERY_MAX_DELAY_MS = 100; const int CATEGORY_REORDER_TIME_MS = 1500; const uint FLUSH_DELAY_MS = 1500; const string[] ALWAYS_REORDER_SCOPE_IDS = {"applications.scope", "files.scope"}; const string SCOPES_QUERY_SCHEMA_PREFIX = "x-unity-no-preview-scopes-query://"; public class HomeScope : Unity.AggregatorScope { private ScopeManager scope_mgr = new ScopeManager (); private SearchQueryState query_state = new SearchQueryState (); private FilterState filter_state = new FilterState (); private KeywordSearch keywords_search = new KeywordSearch (); private bool smart_scopes_initialized = false; public bool smart_scopes_ready { get; internal set; default = false; } internal SmartScopes.SmartScopeClientInterface sss_client = null; private SmartScopes.ChannelIdMap channel_id_map = new SmartScopes.ChannelIdMap (); private uint metrics_timer; private int remote_scopes_retry_count = 0; private RemoteScopeRegistry remote_scope_registry; private uint category_reorder_time_ms = 0; // minimum time after which category reordering can happen (in milliseconds) private uint flush_delay_ms = 0; //minimum time after which results will be shown in the dash (in milliseconds) private NetworkMonitor? netmon = null; private bool remote_scopes_request_running = false; private ulong netmon_sig_id = 0; public HomeScope () { Object (dbus_path: "/com/canonical/unity/home", id:"home.scope", merge_mode: Unity.AggregatorScope.MergeMode.OWNER_SCOPE); } protected override void constructed () { base.constructed (); var reorder_time = Environment.get_variable ("HOME_SCOPE_REORDER_TIME"); //in milliseconds var flush_delay_ms_time = Environment.get_variable ("HOME_SCOPE_FLUSH_DELAY"); //in milliseconds if (flush_delay_ms_time != null) flush_delay_ms = int.parse (flush_delay_ms_time); if (flush_delay_ms == 0) flush_delay_ms = FLUSH_DELAY_MS; if (reorder_time != null) category_reorder_time_ms = int.parse (reorder_time); if (category_reorder_time_ms == 0) category_reorder_time_ms = CATEGORY_REORDER_TIME_MS; if (flush_delay_ms > category_reorder_time_ms) category_reorder_time_ms = flush_delay_ms; automatic_flushing = false; discover_scopes_sync (); CategoryManager.instance ().set_default_sort_order_from_dconf (); var reg = ScopeRegistry.instance (); if (reg.scopes == null) { critical ("No scopes found. Please check your installation"); return; } update_search_hint (); keywords_search.rebuild (); populate_filters (); populate_categories (); debug ("Starting master scopes"); scope_mgr.start_master_scopes (); debug ("Exporting home scope"); export (); scope_mgr.notify["remote-content-search"].connect ((obj, pspec) => { remote_content_search_changed (); }); scope_mgr.disabled_scopes_changed.connect (() => { debug ("The list of disabled scopes has changed."); populate_filters (); }); init_sss_client (); } private static bool discovery_started = false; public static void discover_scopes_sync () { if (discovery_started) return; discovery_started = true; debug ("Starting scope discovery"); var ml = new MainLoop (); var reg = ScopeRegistry.instance (); reg.find_scopes.begin ((obj, res) => { reg.find_scopes.end (res); ml.quit (); }); ml.run (); MetaScopeRegistry.instance ().update (reg, null); } internal void remote_content_search_changed () { debug ("remote-content-search flag changed"); update_search_hint (); if (!scope_mgr.remote_content_search) { if (smart_scopes_initialized) { debug ("Disabling Smart Scopes Server connectivity"); smart_scopes_initialized = false; smart_scopes_ready = false; if (metrics_timer > 0) { GLib.Source.remove (metrics_timer); metrics_timer = 0; } sss_client = null; } } // don't do anything if it got enabled - this will be handled on next search () } internal void update_search_hint () { debug ("Updating search hint"); if (!scope_mgr.remote_content_search) search_hint = _("Search your computer"); else search_hint = _("Search your computer and online sources"); } internal void init_sss_client () { if (scope_mgr.remote_content_search == false) return; if (!smart_scopes_initialized) { debug ("Initializing Smart Scopes client"); ClientScopesInfo client_scopes_info = null; try { client_scopes_info = ClientScopesInfo.from_file (ClientScopesInfo.CLIENT_SCOPES_FILE, ScopeRegistry.instance ().flatten ()); } catch (Error e) { warning ("Cannot determine client scopes: %s", e.message); } var platform_info = SmartScopes.PlatformInfo.gather_platform_info (client_scopes_info); smart_scopes_initialized = true; sss_client = new SmartScopes.SmartScopesClient (platform_info); request_remote_scopes.begin (); metrics_timer = GLib.Timeout.add_seconds (METRICS_SEND_INTERVAL_SECS, flush_metrics_async); } } internal async void request_remote_scopes () { if (remote_scopes_request_running || smart_scopes_ready) return; remote_scopes_request_running = true; SmartScopes.RemoteScopeInfo[]? scopes = null; // issue a request for remote_scopes list try { scopes = yield sss_client.remote_scopes (null); if (scopes != null) { debug ("Got %u remote scopes", scopes.length); foreach (var remote_scope in scopes) { if (remote_scope.keywords.length > 0) { var kw = new GLib.SList (); foreach (var k in remote_scope.keywords) { kw.append (k); } keywords_search.index_keywords (remote_scope.scope_id, kw); } } remote_scope_registry = new RemoteScopeRegistry.for_scopes (scopes); remote_scope_registry.create_model (); MetaScopeRegistry.instance ().update (ScopeRegistry.instance (), remote_scope_registry); smart_scopes_ready = true; populate_filters (); // recreate filters } } catch (Error e) { warning ("Failed to get list of remote scopes: %s", e.message); } remote_scopes_request_running = false; if (!smart_scopes_ready) { bool network_available = GLib.NetworkMonitor.get_default ().network_available; // if network not available, monitor it and re-try when it becomes available if (netmon == null && !network_available) { debug ("Network not available, creating netmon"); netmon = GLib.NetworkMonitor.get_default (); netmon_sig_id = netmon.network_changed.connect ((available) => { // note: we get network_changed notification multiple times, // remote_scopes_request_running ensures we retry once at a time. if (available && !smart_scopes_ready && !remote_scopes_request_running) { remote_scopes_retry_count = 0; // just retry after small initial delay (don't multiply by num of failures) GLib.Timeout.add_seconds (REMOTE_SCOPES_INITIAL_RETRY_INTERVAL_SECS, request_remote_scopes_retry); } }); } // network considered available but for some reason remote-scopes query failed - setup a retry after timeout if (network_available) { var retry_delay = int.min (REMOTE_SCOPES_INITIAL_RETRY_INTERVAL_SECS * ++remote_scopes_retry_count, REMOTE_SCOPES_RETRY_INTERVAL_MAX_SECS); debug ("Network is available, will retry in %d secs", retry_delay); GLib.Timeout.add_seconds (retry_delay, request_remote_scopes_retry); } } else { // once we got remote-scopes, disconnect and remove netmon if (netmon != null) { SignalHandler.disconnect (netmon, netmon_sig_id); netmon = null; netmon_sig_id = 0; } } } internal bool request_remote_scopes_retry () { request_remote_scopes.begin (); return false; // don't run this timeout callback anymore } internal void populate_filters () { var reg = ScopeRegistry.instance (); var scopes_lut = new Gee.TreeSet (); // lookup for scope ids that got added to sources filter var filter_list = new FilterSet (); var cat_filter = new Unity.CheckOptionFilter ("categories", _("Categories")); cat_filter.show_all_button = false; cat_filter.sort_type = Unity.OptionsFilter.SortType.DISPLAY_NAME; var src_filter = new Unity.CheckOptionFilter ("sources", _("Sources")); src_filter.sort_type = Unity.OptionsFilter.SortType.DISPLAY_NAME; var meta_reg = MetaScopeRegistry.instance (); foreach (var node in reg.scopes) { // display master scope in Category filter only if it has subscopes if (node.scope_info.visible && ( node.scope_info.is_master == false || meta_reg.has_subscopes (node.scope_info.id))) { debug ("Category filter add: %s", node.scope_info.id); cat_filter.add_option (node.scope_info.id, node.scope_info.name); //add master scope id to 'Categories' } foreach (var scope in node.sub_scopes) { scopes_lut.add (scope.id); if (scope.visible && !scope_mgr.is_disabled (scope.id)) // should we also hide subsope if master is not visible? { debug ("Sources filter add: %s", scope.id); src_filter.add_option (scope.id, scope.name); } } } if (smart_scopes_ready) { foreach (var scope in remote_scope_registry.get_scopes ()) { if (!reg.is_master (scope.scope_id) && !scopes_lut.contains (scope.scope_id) && !scope_mgr.is_disabled (scope.scope_id)) { debug ("Sources filter add: %s (remote scope)", scope.scope_id); src_filter.add_option (scope.scope_id, scope.name); } } } src_filter.show_all_button = false; filter_list.add (cat_filter); filter_list.add (src_filter); filters = filter_list; } /** * Populate home scope category list with master scopes. */ internal void populate_categories () { debug ("Setting home scope categorties"); var cats = new CategorySet (); foreach (var node in ScopeRegistry.instance ().scopes) { var scope_info = node.scope_info; var scope_id = scope_info.id; debug ("Adding home scope category: %s", scope_id); CategoryManager.instance ().register (scope_id); var icon = new GLib.FileIcon (File.new_for_path (scope_info.category_icon ?? scope_info.icon)); // if present, take the renderer from the .scope file's "global" category bool defines_global_category = false; foreach (var cat_def in scope_info.get_categories ()) { if (cat_def.id == "global") { var cat = DefinitionsParser.parse_category_definition (cat_def, icon); if (cat == null) continue; defines_global_category = true; // override id, name etc. var real_cat = new Unity.Category (scope_id, scope_info.name, cat.icon_hint, cat.default_renderer); real_cat.content_type = cat.content_type; cats.add (real_cat); break; } } if (!defines_global_category) { var cat = new Unity.Category (scope_id, scope_info.name, icon); cats.add (cat); } } categories = cats; //set scope categories } // TODO: hook it up once API support it private void on_home_channel_closed (string channel_id) { query_state.remove (channel_id); channel_id_map.remove_channel (channel_id); } /** * Creates new session id if search string changed considerably, or returns existing session id */ private string get_session_id (SearchQueryChange query_status, string home_channel_id) { string? session_id = null; if (query_status != SearchQueryChange.NEW_QUERY) { if (channel_id_map.has_session_id_for_channel (home_channel_id)) { session_id = channel_id_map.session_id_for_channel (home_channel_id); debug ("Using existing session id %s for channel %s", session_id, home_channel_id); } else { debug ("no smart scopes session mapping for channel %s", home_channel_id); } } if (session_id == null) { session_id = sss_client.create_session_id (); debug ("Creating new session id %s for channel %s", session_id, home_channel_id); channel_id_map.map_session_id (home_channel_id, session_id); } return session_id; } internal bool flush_metrics_async () { handle_feedback_sending.begin (true, (obj, res) => { handle_feedback_sending.end (res); }); return true; } internal void handle_metrics (uint action_type, string scope_id, string? session_id, string? server_sid) { // server_sid may be null if we didn't query smart scopes service if (server_sid == null) return; debug ("Adding activation metrics record for scope %s, action_type=%u", scope_id, action_type); var timestamp = new DateTime.now_utc (); if (action_type == Unity.Protocol.ActionType.ACTIVATE_RESULT || action_type == Unity.Protocol.ActionType.PREVIEW_ACTION) { sss_client.add_click_event (session_id, server_sid, scope_id, timestamp); } else if (action_type == Unity.Protocol.ActionType.PREVIEW_RESULT) { sss_client.add_preview_event (session_id, server_sid, scope_id, timestamp); } handle_feedback_sending.begin (); } internal void handle_found_metrics (string home_channel_id, string session_id, string server_sid, List recommendations) { var cat_mgr = CategoryManager.instance (); var result_counts = new Gee.HashMap (); foreach (var scope_rec in recommendations) { int count = cat_mgr.get_result_count (home_channel_id, scope_rec.scope_id); debug ("Result count for recommended scope %s, home channel %s: %d", scope_rec.scope_id, home_channel_id, count); // we may get count = -1 in two cases: // - server recommended a client scope which we don't have (shouldn't happen if server considers removed_scopes properly). // - recommended client scope didn't produce any results. if (count >= 0) result_counts[scope_rec.scope_id] = count; } var timestamp = new DateTime.now_utc (); sss_client.add_found_event (session_id, server_sid, result_counts, timestamp); handle_feedback_sending.begin (); } internal async void handle_feedback_sending (bool force = false) { if (force || sss_client.num_feedback_events () > METRICS_MIN_NUM_EVENTS) { try { yield sss_client.send_feedback (null); //TODO cancellable } catch (Error e) { warning ("Sending feedback failed: %s", e.message); } } } internal async ActivationResponse? handle_preview (Unity.AggregatorActivation activation, string session_id, string server_sid) throws Error { if (activation.scope_result == null) { warning ("activation on channel %s has no result", activation.channel_id); return null; } var content = activation.scope_result.metadata.lookup ("content"); //master scopes put 'metadata' in 'content' if (content == null) { warning ("'content' element not found"); //this shouldn't really happen return null; } var result_id = content.lookup_value ("id", VariantType.STRING); if (result_id == null) { warning ("result_id not present for activation request on channel %s, uri %s", activation.channel_id, activation.scope_result.uri); return null; } var preview = yield sss_client.preview (server_sid, session_id, result_id.get_string (), activation.scope_result, null); //TODO cancellable? if (preview == null) { warning ("Request for server preview for uri %s returned null", activation.scope_result.uri); return null; } return new ActivationResponse.with_preview (preview); } internal override async ActivationResponse? activate (Unity.AggregatorActivation activation) { debug ("Activation request for scope %s, action_type=%u", activation.scope_id, activation.action_type); // handle 'did you mean..' type of results (new search query provide via 'scopes-query://' results) if (activation.action_type == Unity.Protocol.ActionType.ACTIVATE_RESULT && activation.scope_result.uri.has_prefix (SCOPES_QUERY_SCHEMA_PREFIX)) { var query = activation.scope_result.uri.substring (SCOPES_QUERY_SCHEMA_PREFIX.length); var escaped_query = GLib.Uri.unescape_string (query); if (escaped_query == null) escaped_query = query; if (!escaped_query.validate ()) { escaped_query = "error: Invalid UTF8"; warning ("Invalid UTF8 received from the server"); } if (escaped_query != null) query = escaped_query; query_state.set_canned_query (activation.channel_id, query); return new ActivationResponse.with_search (query, null, null); } if (!scope_mgr.remote_content_search || sss_client == null || (activation.action_type != Unity.Protocol.ActionType.ACTIVATE_RESULT && activation.action_type != Unity.Protocol.ActionType.PREVIEW_RESULT && activation.action_type != Unity.Protocol.ActionType.PREVIEW_ACTION)) return null; //nothing to do string? server_sid = channel_id_map.server_sid_for_channel (activation.channel_id); string? session_id = channel_id_map.session_id_for_channel (activation.channel_id); var scope_id = activation.scope_id; //this is an id of master (or a standalone scope such as apps) var scope_id_var = activation.scope_result.metadata.lookup ("scope-id"); if (scope_id_var != null) scope_id = scope_id_var.get_string (); else debug ("No specific scope_id in the result from master '%s'", scope_id); bool is_local_scope = scope_mgr.is_client_scope (scope_id) || ScopeRegistry.instance ().is_master (scope_id); // special case for more suggestions in home lens: both lmb & rmb should display a preview if (scope_id.has_prefix ("more_suggestions-") && !is_local_scope && (activation.action_type == Unity.Protocol.ActionType.ACTIVATE_RESULT || activation.action_type == Unity.Protocol.ActionType.PREVIEW_RESULT)) { var preview = yield handle_preview (activation, session_id, server_sid); handle_metrics (Unity.Protocol.ActionType.PREVIEW_RESULT, scope_id, session_id, server_sid); return preview; } handle_metrics (activation.action_type, scope_id, session_id, server_sid); // do nothing for local scopes - activation will be handled by actual scope if (is_local_scope) { debug ("Scope %s is a local scope, passing request to it", scope_id); return null; } // at this point it can only be a remote scope activation request; // if it's a preview action, then take activation uri from the id // and make the Dash go there via goto_uri argument. if (activation.action_type == Unity.Protocol.ActionType.PREVIEW_ACTION) { string action_id = activation.hints["preview-action-id"].get_string (); return new ActivationResponse (Unity.HandledType.NOT_HANDLED, action_id); } if (activation.action_type == Unity.Protocol.ActionType.PREVIEW_RESULT) { try { var preview = yield handle_preview (activation, session_id, server_sid); return preview; } catch (Error e) { warning ("Preview request for scope %s failed: %s", activation.scope_id, e.message); } } return null; // activation will be handled by actual scope } public override async void search (Unity.AggregatedScopeSearch scope_search) { debug ("------------ Search query: %s, channel %s -----------", scope_search.search_string, scope_search.channel_id); init_sss_client (); GLib.Cancellable cancellable = scope_search.search_context.cancellable.get_gcancellable(); CategoryManager.instance ().observe (scope_search.channel_id, scope_search.results_model); bool wait_for_sss_query = false; bool wait_for_push = false; bool wait_for_search = false; bool sss_query_done = false; bool sss_query_started = false; uint num_scopes = 0; bool flushing_enabled = false; unowned string? form_factor = scope_search.search_context.search_metadata.form_factor; if (form_factor == null) form_factor = "unknown"; // set to 'unknown' as it's sent with smart scopes request bool disable_filter_updates = (form_factor != "desktop"); if (disable_filter_updates) debug ("Filter updates disabled, form factor is %s", form_factor); // ids of scopes recommended by Smart Scope Service var recommended_search_scopes = new List (); AsyncReadyCallback search_cb = (obj, res) => { var search_obj = obj as Unity.AggregatedScopeSearch; try { var reply_dict = search_obj.search_scope.end (res); var iter = HashTableIter (reply_dict); unowned string key; unowned Variant variant; while (iter.next (out key, out variant)) scope_search.set_reply_hint (key, variant); } catch (Error err) { if (!(err is IOError.CANCELLED)) warning ("Unable to search scope: %s", err.message); } if (--num_scopes == 0 && wait_for_search) search.callback (); }; // data models that are populated by Smart Scopes search and pushed to respective scopes var push_data = new HashTable (str_hash, str_equal); //scope_id -> model int pending_push_count = 0; AsyncReadyCallback push_results_cb = (obj, res) => { try { scope_search.push_results.end (res); } catch (Error err) { warning ("push_results failed: %s", err.message); } if (--pending_push_count == 0 && wait_for_push) search.callback (); }; SourceFunc push_data_idle_cb = ()=> { var iter = HashTableIter (push_data); unowned string scope_id; unowned Dee.SerializableModel model; while (iter.next (out scope_id, out model)) { debug ("Pushing results (%u rows) for scope %s", model.get_n_rows (), scope_id); ++pending_push_count; scope_search.push_results.begin (scope_id, model, {"global"}, push_results_cb); } push_data.remove_all (); return false; }; // compare current search string with previous one to see if it's filters change only; // this impacts how we treat online recommendations and filters. var search_query_changed = query_state.search_query_changed (scope_search.channel_id, scope_search.search_string); // maps ids of scopes to search (master scopes or top-level scopes) to sub-scopes (according to filter or direct keyword search); // may map to null, in that case there is no filtering based on sub-scopes (all subscopes of given master will be queried). var search_scopes = new HashTable?> (str_hash, str_equal); // filters are updated during the search, but may need to be updated again afterwards if we find out there wero no results for some categories bool needs_filter_update = false; // default filter view flag; default view is kind of a special case that kicks in when search query is empty: // user can modify filters at will, and they are stored in gsettings key. // this flag prevents filter updates based on what scopes were actually searched and if they had results, so that user can comfortably // edit the all the filters without loosing them every second. bool default_view = false; bool empty_query = (scope_search.search_string.strip ().length == 0); // set if this search is a filter update only (e.g. search string is the same) bool filter_change_only = false; if (search_query_changed == SearchQueryChange.NOT_CHANGED) { filter_change_only = true; debug ("Filter change only"); } // apply user filters only if search string is unchanged, the query is empty or smart scopes are disabled completly. // this mean user set filters *after* entering a query and we apply them; // otherwise smart scopes recommendations will take precedence. // if search query is empty, then apply default user's filters. if (filter_change_only || empty_query || sss_client == null) { unowned Unity.OptionsFilter categories_filter = scope_search.get_filter ("categories") as Unity.OptionsFilter; unowned Unity.OptionsFilter sources_filter = scope_search.get_filter ("sources") as Unity.OptionsFilter; bool relations_changed = false; // if query is empty but it's not just a filter change (i.e. state is REMOVES_FROM_PREVIOUS_QUERY or NEW_QUERY), // then apply default user filters. if (empty_query && search_query_changed != SearchQueryChange.NOT_CHANGED) { var default_filters = scope_mgr.home_lens_default_view; if (default_filters.length > 0) { debug ("Empty query, applying default filter view"); needs_filter_update |= FilterState.set_filters (categories_filter, sources_filter, default_filters); } default_view = true; } if (!disable_filter_updates) { debug ("Updating filter interrelationships"); relations_changed = filter_state.update_filter_relations (scope_search.channel_id, categories_filter, sources_filter); } else { needs_filter_update = false; //reset filter update flag if on the phone } // caution: push_filter_settings may get cancelled if we ever yield before; // in such case internal filter state must be updated later (at the end of search). if (relations_changed) push_filters_update (scope_search); debug ("Filter interrelationships changed: %s", relations_changed.to_string ()); SearchUtil.scopes_to_query_from_filters (scope_search.get_filter ("sources") as Unity.OptionsFilter, scope_search.get_filter ("categories") as Unity.OptionsFilter, search_scopes); // if this search request is a filter change only and query is empty, // update user's default view in gsettings if (filter_change_only && empty_query && relations_changed) { default_view = true; debug ("Updating user's default filter view"); scope_mgr.home_lens_default_view = SearchUtil.scopes_to_query_to_array (search_scopes); } } // handle keywords (direct search) string? search_string = null; bool direct_search = false; unowned Gee.Set? requested_scope_ids = keywords_search.process_query ( scope_search.search_string, out search_string); if (requested_scope_ids != null && search_string != null) { debug ("Direct search query, search_string = '%s'", search_string); direct_search = SearchUtil.scopes_to_query_from_requested_ids (requested_scope_ids, search_scopes); } if (search_string == null) search_string = scope_search.search_string; else empty_query = (search_string.strip ().length == 0); uint push_data_idle_src = 0; var reorder_timer = new Timer (); // flush results after flush_delay_ms elapses var forced_flush_timer = new SearchUtil.GLibSourceWrapper (flush_delay_ms, () => { if (scope_search.search_context.cancellable.is_cancelled ()) return false; debug ("Flush time reached"); flushing_enabled = true; signal_categories_order (scope_search, recommended_search_scopes); scope_search.search_context.result_set.flush (); return false; }); // flush results on end of each transaction if flush_delay_ms elapsed. var transaction_sig = new SearchUtil.SignalWrapper (scope_search, scope_search.transaction_complete.connect ((scope_id) => { if (scope_search.search_context.cancellable.is_cancelled ()) return; debug ("Transaction complete for scope %s, time = %f", scope_id, reorder_timer.elapsed ()); // only start flushing after the timeout; flush immediately if applications search finished so that // apps are immediately available, except for case when only filters changed (reduces flickering). if (flushing_enabled || (scope_id == "applications.scope" && search_query_changed != SearchQueryChange.NOT_CHANGED)) { debug ("Flushing"); bool reorder_enabled = reorder_timer.elapsed () * 1000 < category_reorder_time_ms; if (reorder_enabled || scope_id in ALWAYS_REORDER_SCOPE_IDS) signal_categories_order (scope_search, recommended_search_scopes); scope_search.search_context.result_set.flush (); } })); // initiate Smart Scopes Search (if enabled and query not empty) if (smart_scopes_ready) { if (empty_query) { if (channel_id_map.has_session_id_for_channel (scope_search.channel_id)) { debug ("Empty search, removing session mapping for channel %s", scope_search.channel_id); channel_id_map.remove_channel (scope_search.channel_id); } } else // only sent the query to smart scopes if it's not empty { var session_id = get_session_id (search_query_changed, scope_search.channel_id); AsyncReadyCallback sss_cb = (obj, res) => { try { sss_client.search.end (res); } catch (IOError.CANCELLED canc_err) { // silently ignore } catch (Error e) { warning ("Remote search failed: %s", e.message); } debug ("Smart scopes query finished"); sss_query_done = true; if (wait_for_sss_query) search.callback (); }; // apply filters to remote scopes query (either keyword-search filters or normal filters) string[] remote_scopes_to_query = new string [0]; var iter = HashTableIter> (search_scopes); string scope_id; Gee.Set subscopes; while (iter.next (out scope_id, out subscopes)) { // this is to support virtual more_suggestions-* scopes and // effectively filter out applications.scope and alike. if (subscopes == null && remote_scope_registry.has_scope (scope_id)) remote_scopes_to_query += scope_id; if (subscopes != null) { foreach (var subscope_id in subscopes) { if (remote_scope_registry.has_scope (subscope_id)) remote_scopes_to_query += subscope_id; } } }; // if specific scopes were requested via filters or direct search, then // only send the query to smart scopes server if // it has (some) of the requested scopes (will be passed via &scopes=) if ((direct_search || filter_change_only) && (remote_scopes_to_query == null || remote_scopes_to_query.length == 0)) { debug ("No remote scopes to query based on keyword search or filters"); } else { // timeout to give cancellable a chance for update; it depends on query length (the longer the string, the shorter timeout); // the formula is as follows: // min_delay + (max_delay - min_delay) / query_length // it gives the following values for lengths in 1..inf and min=50, max=100: // 100, 75, 66, 62, 60, 58, 57, 56, 55, 55, 54, 54, 53, 53, 53, 53, 52, 52, 52, ... 50 ... // // note: query is guaranteed to be non-empty at this point, so we're never dividing by 0! GLib.Timeout.add (SMART_SCOPES_QUERY_MIN_DELAY_MS + (SMART_SCOPES_QUERY_MAX_DELAY_MS - SMART_SCOPES_QUERY_MIN_DELAY_MS) / search_string.length, ()=> { if (wait_for_search == false && wait_for_push == false && wait_for_sss_query == false) search.callback (); return false; }); yield; if (cancellable.is_cancelled ()) { debug ("The search for '%s' on channel %s was cancelled", scope_search.search_string, scope_search.channel_id); return; } sss_query_started = true; sss_client.search.begin (search_string, form_factor, session_id, remote_scopes_to_query, scope_mgr.disabled_scopes, (scope_id, row) => { // note: we get one row at a time in this callback but when we push the results, // we need to push all of them at once (per-scope), otherwise we would overwrite previously pushed rows; // this is currently guaranteed because smart-scopes-parse just calls us back in parser loop // (and sss search results are grouped by scope). var pushed_model = push_data.lookup (scope_id); if (pushed_model == null) { pushed_model = new Dee.SequenceModel (); pushed_model.set_schema_full ({"s", "s", "u", "u", "s", "s", "s", "s", "a{sv}"}); push_data[scope_id] = pushed_model; } pushed_model.append_row (row); if (push_data_idle_src == 0) { push_data_idle_src = Idle.add (() => { push_data_idle_cb (); push_data_idle_src = 0; return false; }); } }, (server_sid, recommendations) => { if (server_sid != null) { channel_id_map.map_server_sid (scope_search.channel_id, server_sid); int rec_limit_count = 0; foreach (var scope_rec in recommendations) { if (scope_rec.scope_type == SmartScopes.ScopeType.ClientScope) { if (rec_limit_count == SMART_SCOPES_RECOMMENDATIONS_CUTOFF) continue; // past cut off, ignore this client scope ++rec_limit_count; } recommended_search_scopes.append (scope_rec); debug ("Got recommended scope: %s, %s", scope_rec.scope_id, scope_rec.scope_type.to_string ()); } } else { warning ("server_sid is null"); } }, cancellable, sss_cb); } } } else { // may happen if remote-scopes haven't finished or returned an error debug ("Smart scopes not ready or not enabled for this query"); } // no filters set / no scopes to search, use always-search scopes if (search_scopes.size () == 0) { debug ("No scopes to search based on filters. Defaulting to always-search-scopes"); SearchUtil.scopes_to_query_from_requested_ids (scope_mgr.get_always_search_scopes (), search_scopes); } num_scopes = search_scopes.size (); debug ("Dispatching search to %u scopes, home_channel=%s", num_scopes, scope_search.channel_id); // iterate over master scopes, dispatch search query foreach (var scope_id in search_scopes.get_keys ()) { debug ("Dispatching search to scope %s", scope_id); // apply Sources (subscopes) filtering; pass subscopes requested via filters to relevant masterscopes var hints = new HashTable (str_hash, str_equal); SearchUtil.set_subscopes_filter_hint (hints, search_scopes, scope_id); scope_search.search_scope.begin (scope_id, search_string, SearchType.GLOBAL, hints, search_cb); } bool use_recommended_scopes = false; // dispatch search to scopes recommended by smart scopes service if (smart_scopes_ready && !empty_query) { // wait for smart scopes service query to finish if (sss_query_started && !sss_query_done) { debug ("Waiting for Smart Scopes query to finish"); wait_for_sss_query = true; yield; } try { cancellable.set_error_if_cancelled (); // update category order for each scope search finish // note: recommended scopes list may be initially empty, as it arrives after we initiate first local searches. if (flushing_enabled && reorder_timer.elapsed () * 1000 < category_reorder_time_ms) { signal_categories_order (scope_search, recommended_search_scopes); } if (push_data_idle_src > 0) { debug ("Flushing push results"); push_data_idle_cb (); //make sure we flush all push_results if (pending_push_count > 0) { debug ("Waiting for results pushing to finish"); wait_for_push = true; yield; wait_for_push = false; } } } catch (Error e) { debug ("The search for '%s' on channel %s was cancelled", scope_search.search_string, scope_search.channel_id); return; } finally { if (push_data_idle_src > 0) Source.remove (push_data_idle_src); push_data_idle_src = 0; } debug ("Got %u recommended scopes from Smart Scope Service", recommended_search_scopes.length ()); } // only use recommended scopes if search query was changed (thus user-selected filters are reset). if (search_query_changed != SearchQueryChange.NOT_CHANGED && !direct_search && recommended_search_scopes.length () > 0) use_recommended_scopes = true; // only update filters with searched scopes when not in the default view, otherwise default view filters may get de-selected if (!default_view && !disable_filter_updates) { debug ("Updating filter state"); needs_filter_update |= SearchUtil.update_filters (search_scopes, use_recommended_scopes ? recommended_search_scopes : null, scope_search, false); } if (needs_filter_update) { var filters = FilterState.create_filter_set (scope_search); debug ("Sending updated filters"); scope_search.push_filter_settings (filters); } if (use_recommended_scopes) { debug ("Search query changed, querying recommended scopes"); // ids of recommended scopes var extra_search_scopes = new HashTable?> (str_hash, str_equal); var all_search_scopes = new HashTable?> (str_hash, str_equal); // build the list of master-scopes to search and subscopes_filter to pass to them based on recommendations. // note that recommendations may contains subscopes, so build_search_scopes_list will take care of all the magic. // also note that, because of that, we can't filter out master scopes that were already searched before (as part // of always-search list) just now - this happens below in the search loop. foreach (var scope_rec in recommended_search_scopes) { if (scope_rec.scope_type == SmartScopes.ScopeType.ClientScope) SearchUtil.build_search_scopes_list (scope_rec.scope_id, extra_search_scopes); SearchUtil.build_search_scopes_list (scope_rec.scope_id, all_search_scopes); } foreach (var scope_id in extra_search_scopes.get_keys ()) { // ignore this master if it was already searched before. if (!search_scopes.contains (scope_id)) { debug ("Dispatching search to recommended scope %s", scope_id); ++num_scopes; var hints = new HashTable (str_hash, str_equal); SearchUtil.set_subscopes_filter_hint (hints, all_search_scopes, scope_id); scope_search.search_scope.begin (scope_id, search_string, SearchType.GLOBAL, hints, search_cb); } } } else { debug ("Search query not changed or direct search active, ignoring recommended scopes"); } // wait for the results from recommended scopes if (num_scopes > 0) { debug ("Waiting for search (recommended scopes) to finish"); wait_for_search = true; yield; } if (cancellable.is_cancelled ()) { debug ("The search for '%s' on channel %s was cancelled", scope_search.search_string, scope_search.channel_id); return; } debug ("search finished"); if (scope_search.results_model.get_n_rows () == 0) { scope_search.set_reply_hint ("no-results-hint", _("Sorry, there is nothing that matches your search.")); } // update the category order only if we're in the reordering time slot, // or if this search was fast and we didn't even flush yet if (!scope_search.search_context.cancellable.is_cancelled ()) { if ((flushing_enabled && reorder_timer.elapsed () * 1000 < category_reorder_time_ms) || !flushing_enabled) { signal_categories_order (scope_search, recommended_search_scopes); } } // update filter state again, but this time check result counts and send the update only if any of the highlighted masters has no results if (!default_view && !disable_filter_updates) needs_filter_update |= SearchUtil.update_filters (search_scopes, use_recommended_scopes ? recommended_search_scopes : null, scope_search, filter_change_only == false); if (needs_filter_update) { unowned Unity.OptionsFilter categories_filter = scope_search.get_filter ("categories") as Unity.OptionsFilter; unowned Unity.OptionsFilter sources_filter = scope_search.get_filter ("sources") as Unity.OptionsFilter; filter_state.set_state_from_filters (scope_search.channel_id, categories_filter, sources_filter); push_filters_update (scope_search); } if (use_recommended_scopes) { debug ("Adding 'found' metrics"); string? server_sid = channel_id_map.server_sid_for_channel (scope_search.channel_id); string? session_id = channel_id_map.session_id_for_channel (scope_search.channel_id); // server_sid may be null if we didn't query smart scopes service if (server_sid != null) handle_found_metrics (scope_search.channel_id, session_id, server_sid, recommended_search_scopes); } debug ("All search activities finished"); } public override int category_index_for_scope_id (string scope_id) { return CategoryManager.instance ().get_category_index (scope_id); } private void push_filters_update (AggregatedScopeSearch scope_search) { var filters = FilterState.create_filter_set (scope_search); debug ("Sending updated filters"); scope_search.push_filter_settings (filters); } private void signal_categories_order (AggregatedScopeSearch search, List recommended_scopes) { var cats = CategoryManager.instance ().get_category_order (search.search_string, search.channel_id, search.results_model, recommended_scopes); debug ("Updating categories order"); search.category_order_changed (cats); //TODO only signal if order really changed? } } } unity-scope-home-6.8.2+16.04.20160212.1/src/filter-state.vala0000644000015600001650000002455712657432773023525 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { public class FilterState { // list of scopes that were recently active in filters (sources+categories filter combined), per home channel private HashTable?> filter_state = new HashTable?> (str_hash, str_equal); public void set_state_from_filters (string channel_id, Unity.OptionsFilter categories, Unity.OptionsFilter sources) { if (categories == null || sources == null) { warning ("Categories or Sources filters missing"); return; } // fill in current_state from categories and sources var new_state = new Gee.TreeSet (); get_selected_scopes (categories, new_state); get_selected_scopes (sources, new_state); filter_state[channel_id] = new_state; } /** * Update filters of current scope_search so that selecting a master scope automatically * highlights its subscopes, or deselecting last subscope deselects its master. * This methods stores filter state in order to be able to detect what was selected/deselected. */ public bool update_filter_relations (string channel_id, Unity.OptionsFilter categories, Unity.OptionsFilter sources) { bool changed = false; if (categories == null || sources == null) { warning ("Categories or Sources filters missing"); return false; } // 1. determine current filter state from categories+sources. // 2. compare previous and current state to find out what was selected or unselected // 3. select/unselect respective masters and subscopesaq // fill in current_state from categories and sources var current_state = new Gee.TreeSet (); get_selected_scopes (categories, current_state); get_selected_scopes (sources, current_state); debug ("Scopes in current_state: %d", current_state.size); if (verbose_debug) { foreach (var scope_id in current_state) debug ("Current active: %s", scope_id); } var previous_state = filter_state.lookup (channel_id); if (previous_state != null) { if (verbose_debug) { foreach (var scope_id in previous_state) debug ("Previous active: %s", scope_id); } var selected = new Gee.TreeSet (); var unselected = new Gee.TreeSet (); foreach (var scope_id in current_state) { if (!previous_state.contains (scope_id)) { debug ("Current filter state - selected scope: %s", scope_id); selected.add (scope_id); } } if (selected.size == 0) // there is no need to check unselected if we found out a newly selected scope. { foreach (var scope_id in previous_state) { if (!current_state.contains (scope_id)) { debug ("Current filter state - deselected scope: %s", scope_id); unselected.add (scope_id); } } } if (selected.size > 0) changed = update_filters_for_selected (categories, sources, selected); else if (unselected.size > 0) changed = update_filters_for_unselected (categories, sources, unselected); } else // first search - no previous state { debug ("No previous filter state"); changed = update_filters_for_selected (categories, sources, current_state); } var new_state = new Gee.TreeSet (); get_selected_scopes (categories, new_state); get_selected_scopes (sources, new_state); if (verbose_debug) { foreach (var scope_id in new_state) debug ("New active: %s", scope_id); } filter_state[channel_id] = new_state; if (changed) { sources.filtering = true; categories.filtering = true; } return changed; } internal void get_selected_scopes (Unity.OptionsFilter? filter, Gee.Set scopes) { foreach (var opt in filter.options) { if (opt.active) scopes.add (opt.id); } } internal bool update_filters_for_selected (Unity.OptionsFilter? categories, Unity.OptionsFilter? sources, Gee.Set selected_scopes) { bool changed = false; var reg = ScopeRegistry.instance (); var select_masters = new Gee.TreeSet (); var select_subscopes_of = new Gee.TreeSet (); // selected master - all subscopes need to be selected // selected subscope - master needs to be selected // find master scopes among selected scopes foreach (var scope_id in selected_scopes) { debug ("Scope %s in selected_scopes", scope_id); if (reg.is_master (scope_id)) { select_subscopes_of.add (scope_id); } else { var master_id = SearchUtil.get_master_id_from_scope_id (scope_id); if (master_id != null) { select_masters.add (master_id); } else { warning ("No master scope info for subscope %s", scope_id); // this shouldn't really happen } } } // actual filter update foreach (unowned Unity.FilterOption opt in categories.options) { if (select_masters.contains (opt.id)) { if (!opt.active) { opt.active = true; changed = true; debug ("Changing category filter %s: %s", opt.id, opt.active.to_string ()); } } } foreach (unowned Unity.FilterOption opt in sources.options) { var master_id = SearchUtil.get_master_id_from_scope_id (opt.id); if (master_id != null && select_subscopes_of.contains (master_id)) { if (!opt.active) { opt.active = true; changed = true; debug ("Changing source filter %s: %s", opt.id, opt.active.to_string ()); } } } return changed; } internal bool update_filters_for_unselected (Unity.OptionsFilter? categories, Unity.OptionsFilter? sources, Gee.Set unselected_scopes) { bool changed = false; var reg = MetaScopeRegistry.instance (); var unselect_masters = new HashTable (str_hash, str_equal); // master scope id -> number of unselected scopes var unselect_subscopes_of = new Gee.TreeSet (); // set of master scopes ids // find master scopes among unselected scopes foreach (var scope_id in unselected_scopes) { if (reg.is_master (scope_id)) unselect_subscopes_of.add (scope_id); } // unselected master makes - all subscopes need to be unselected // unselected subscope: unselect master if it was the last subscope foreach (unowned Unity.FilterOption opt in sources.options) { var master_id = SearchUtil.get_master_id_from_scope_id (opt.id); if (master_id != null) { if (unselect_subscopes_of.contains (master_id)) { if (opt.active) { opt.active = false; changed = true; debug ("Changing source filter %s: %s", opt.id, opt.active.to_string ()); } } if (unselected_scopes.contains (opt.id) || opt.active == false) { if (unselect_masters.contains (master_id)) unselect_masters[master_id] = unselect_masters[master_id] + 1; else unselect_masters[master_id] = 1; } } else { warning ("No master scope info for subscope %s", opt.id); // this shouldn't really happen } } // iterate over categories (masters), unselect masters if all its subscopes got unselected foreach (unowned Unity.FilterOption opt in categories.options) { if (unselect_masters.contains (opt.id)) { var sub_scopes = reg.get_subscopes (opt.id); if (sub_scopes != null) { debug ("unselect_masters: id=%s, num=%d, subs=%u", opt.id, unselect_masters[opt.id], sub_scopes.size); if (unselect_masters[opt.id] == sub_scopes.size) { if (opt.active) { opt.active = false; changed = true; debug ("Changing category filter %s: %s", opt.id, opt.active.to_string ()); } } } else { warning ("No subscopes for master scope %s", opt.id); // this shouldn't really happen } } } return changed; } public static bool set_filters (Unity.OptionsFilter categories, Unity.OptionsFilter sources, string[] enabled_scopes) { if (categories == null || sources == null) { warning ("Can't set filters, Categories or Sources filters missing"); return false; } // convert enabled_scopes to set for faster lookups var scopes = new Gee.TreeSet (); foreach (var scope in enabled_scopes) { scopes.add (scope); } bool changed = false; foreach (unowned Unity.FilterOption opt in categories.options) { bool value = scopes.contains (opt.id); if (opt.active != value) { opt.active = value; changed = true; debug ("Changing category filter %s: %s", opt.id, opt.active.to_string ()); } } foreach (unowned Unity.FilterOption opt in sources.options) { bool value = scopes.contains (opt.id); if (opt.active != value) { opt.active = value; changed = true; debug ("Changing sources filter %s: %s", opt.id, opt.active.to_string ()); } } return changed; } public static FilterSet create_filter_set (Unity.AggregatedScopeSearch scope_search) { var filters = new FilterSet (); var categories = scope_search.get_filter ("categories") as Unity.OptionsFilter; var sources = scope_search.get_filter ("sources") as Unity.OptionsFilter; filters.add (sources); filters.add (categories); return filters; } } }unity-scope-home-6.8.2+16.04.20160212.1/src/search-query-state.vala0000644000015600001650000000566512657432773024647 0ustar pbuserpbgroup00000000000000/* * Copyright (C) 2013 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authored by Pawel Stolowski * */ namespace Unity.HomeScope { public enum SearchQueryChange { NOT_CHANGED, NEW_QUERY, CANNED_QUERY, APPENDS_TO_PREVIOUS_QUERY, REMOVES_FROM_PREVIOUS_QUERY } public class SearchQueryState { private HashTable previous_query = new HashTable (str_hash, str_equal); private HashTable canned_query = new HashTable (str_hash, str_equal); public void remove (string home_channel_id) { previous_query.remove (home_channel_id); canned_query.remove (home_channel_id); } public bool has_channel (string home_channel_id) { return previous_query.contains (home_channel_id); } public void set_canned_query (string channel_id, string search_string) { canned_query[channel_id] = search_string; } /** * Compare search string with previous string for given home channel. Store new search string. */ public SearchQueryChange search_query_changed (string channel_id, string search_string) { var query = search_string.strip (); var changed = SearchQueryChange.NEW_QUERY; if (canned_query.contains(channel_id) && canned_query[channel_id] == query) { changed = SearchQueryChange.CANNED_QUERY; } else { if (previous_query.contains (channel_id)) { var prev = previous_query[channel_id]; if (query == "" && prev != "") // there was a query previously, but user removed all characters in new one { changed = SearchQueryChange.NEW_QUERY; } else { if (prev == query) changed = SearchQueryChange.NOT_CHANGED; else if (prev == "") changed = SearchQueryChange.NEW_QUERY; else if (query.has_prefix (prev)) changed = SearchQueryChange.APPENDS_TO_PREVIOUS_QUERY; else if (prev.has_prefix (query)) changed = SearchQueryChange.REMOVES_FROM_PREVIOUS_QUERY; } } } canned_query.remove (channel_id); previous_query[channel_id] = query; debug ("search_query_changed for channel %s: %s", channel_id, changed.to_string ()); return changed; } } } unity-scope-home-6.8.2+16.04.20160212.1/README0000644000015600001650000000000012657432773020317 0ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/po/0000755000015600001650000000000012657434047020063 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/po/POTFILES.skip0000644000015600001650000000021112657432773022176 0ustar pbuserpbgroup00000000000000src/main.c src/scope.c src/keyword-search.c src/category-manager.c src/scope-registry.c tests/unit/category-manager.c tests/unit/scope.c unity-scope-home-6.8.2+16.04.20160212.1/po/LINGUAS0000644000015600001650000000000012657432773021102 0ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/po/POTFILES.in0000644000015600001650000000234212657432773021645 0ustar pbuserpbgroup00000000000000[encoding: UTF-8] src/scope.vala src/category-manager.vala [type: gettext/ini]data/home.scope.in.in [type: gettext/ini]data/master-scopes/applications.scope.in.in [type: gettext/ini]data/master-scopes/books.scope.in.in [type: gettext/ini]data/master-scopes/boxes.scope.in.in [type: gettext/ini]data/master-scopes/calendar.scope.in.in [type: gettext/ini]data/master-scopes/code.scope.in.in [type: gettext/ini]data/master-scopes/files.scope.in.in [type: gettext/ini]data/master-scopes/graphics.scope.in.in [type: gettext/ini]data/master-scopes/help.scope.in.in [type: gettext/ini]data/master-scopes/info.scope.in.in [type: gettext/ini]data/master-scopes/more_suggestions.scope.in.in [type: gettext/ini]data/master-scopes/music.scope.in.in [type: gettext/ini]data/master-scopes/news.scope.in.in [type: gettext/ini]data/master-scopes/notes.scope.in.in [type: gettext/ini]data/master-scopes/photos.scope.in.in [type: gettext/ini]data/master-scopes/recipes.scope.in.in [type: gettext/ini]data/master-scopes/reference.scope.in.in [type: gettext/ini]data/master-scopes/searchin.scope.in.in [type: gettext/ini]data/master-scopes/video.scope.in.in [type: gettext/ini]data/master-scopes/weather.scope.in.in [type: gettext/ini]data/master-scopes/web.scope.in.in unity-scope-home-6.8.2+16.04.20160212.1/Makefile.am0000644000015600001650000000023512657432773021505 0ustar pbuserpbgroup00000000000000SUBDIRS = src tests/unit data po DISTCHECK_CONFIGURE_FLAGS = --enable-localinstall EXTRA_DIST = \ autogen.sh include $(top_srcdir)/Makefile.am.coverage unity-scope-home-6.8.2+16.04.20160212.1/COPYING0000644000015600001650000010451312657432773020510 0ustar pbuserpbgroup00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . unity-scope-home-6.8.2+16.04.20160212.1/AUTHORS0000644000015600001650000000000012657432773020507 0ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/m4/0000755000015600001650000000000012657434047017765 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/m4/gcov.m40000644000015600001650000000502012657432773021166 0ustar pbuserpbgroup00000000000000# Checks for existence of coverage tools: # * gcov # * lcov # * genhtml # * gcovr # # Sets ac_cv_check_gcov to yes if tooling is present # and reports the executables to the variables LCOV, GCOVR and GENHTML. AC_DEFUN([AC_TDD_GCOV], [ AC_ARG_ENABLE(gcov, AS_HELP_STRING([--enable-gcov], [enable coverage testing with gcov]), [use_gcov=$enableval], [use_gcov=no]) AM_CONDITIONAL(HAVE_GCOV, test "x$use_gcov" = "xyes") if test "x$use_gcov" = "xyes"; then # we need gcc: if test "$GCC" != "yes"; then AC_MSG_ERROR([GCC is required for --enable-gcov]) fi # Check if ccache is being used AC_CHECK_PROG(SHTOOL, shtool, shtool) if test "$SHTOOL"; then AS_CASE([`$SHTOOL path $CC`], [*ccache*], [gcc_ccache=yes], [gcc_ccache=no]) fi if test "$gcc_ccache" = "yes" && (test -z "$CCACHE_DISABLE" || test "$CCACHE_DISABLE" != "1"); then AC_MSG_ERROR([ccache must be disabled when --enable-gcov option is used. You can disable ccache by setting environment variable CCACHE_DISABLE=1.]) fi lcov_version_list="1.6 1.7 1.8 1.9 1.10 1.11" AC_CHECK_PROG(LCOV, lcov, lcov) AC_CHECK_PROG(GENHTML, genhtml, genhtml) if test "$LCOV"; then AC_CACHE_CHECK([for lcov version], glib_cv_lcov_version, [ glib_cv_lcov_version=invalid lcov_version=`$LCOV -v 2>/dev/null | $SED -e 's/^.* //'` for lcov_check_version in $lcov_version_list; do if test "$lcov_version" = "$lcov_check_version"; then glib_cv_lcov_version="$lcov_check_version (ok)" fi done ]) else lcov_msg="To enable code coverage reporting you must have one of the following lcov versions installed: $lcov_version_list" AC_MSG_ERROR([$lcov_msg]) fi case $glib_cv_lcov_version in ""|invalid[)] lcov_msg="You must have one of the following versions of lcov: $lcov_version_list (found: $lcov_version)." AC_MSG_ERROR([$lcov_msg]) LCOV="exit 0;" ;; esac if test -z "$GENHTML"; then AC_MSG_ERROR([Could not find genhtml from the lcov package]) fi ac_cv_check_gcov=yes ac_cv_check_lcov=yes # Remove all optimization flags from CFLAGS changequote({,}) CFLAGS=`echo "$CFLAGS" | $SED -e 's/-O[0-9]*//g'` changequote([,]) # Add the special gcc flags COVERAGE_CFLAGS="-O0 --coverage" COVERAGE_CXXFLAGS="-O0 --coverage" COVERAGE_LDFLAGS="-lgcov" # Check availability of gcovr AC_CHECK_PROG(GCOVR, gcovr, gcovr) if test -z "$GCOVR"; then ac_cv_check_gcovr=no else ac_cv_check_gcovr=yes fi fi ]) # AC_TDD_GCOV unity-scope-home-6.8.2+16.04.20160212.1/acinclude.m40000644000015600001650000000170712657432773021647 0ustar pbuserpbgroup00000000000000dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR) dnl dnl example dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir) dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local AC_DEFUN([AS_AC_EXPAND], [ EXP_VAR=[$1] FROM_VAR=[$2] dnl first expand prefix and exec_prefix if necessary prefix_save=$prefix exec_prefix_save=$exec_prefix dnl if no prefix given, then use /usr/local, the default prefix if test "x$prefix" = "xNONE"; then prefix=$ac_default_prefix fi dnl if no exec_prefix given, then use prefix if test "x$exec_prefix" = "xNONE"; then exec_prefix=$prefix fi full_var="$FROM_VAR" dnl loop until it doesn't change anymore while true; do new_full_var="`eval echo $full_var`" if test "x$new_full_var"="x$full_var"; then break; fi full_var=$new_full_var done dnl clean up full_var=$new_full_var AC_SUBST([$1], "$full_var") dnl restore prefix and exec_prefix prefix=$prefix_save exec_prefix=$exec_prefix_save ]) unity-scope-home-6.8.2+16.04.20160212.1/data/0000755000015600001650000000000012657434047020356 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/data/home.scope.in.in0000644000015600001650000000051412657432773023357 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/home Icon=@prefix@/share/unity/icons/lens-nav-home.svg RequiredMetadata= OptionalMetadata= Type=home _Name=Home Description=Unity Home Scope _SearchHint=Search your computer and online sources [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home unity-scope-home-6.8.2+16.04.20160212.1/data/unity-scope-home.service.in0000644000015600001650000000013212657432773025552 0ustar pbuserpbgroup00000000000000[D-BUS Service] Name=com.canonical.Unity.Scope.Home Exec=@pkglibexecdir@/unity-scope-home unity-scope-home-6.8.2+16.04.20160212.1/data/Makefile.am0000644000015600001650000000237412657432773022424 0ustar pbuserpbgroup00000000000000EXTRA_DIST = # # Install the home.scope # scope_in_files = \ home.scope.in \ master-scopes/applications.scope.in \ master-scopes/files.scope.in \ master-scopes/music.scope.in \ master-scopes/video.scope.in \ master-scopes/photos.scope.in \ master-scopes/more_suggestions.scope.in \ master-scopes/books.scope.in \ master-scopes/boxes.scope.in \ master-scopes/code.scope.in \ master-scopes/graphics.scope.in \ master-scopes/help.scope.in \ master-scopes/info.scope.in \ master-scopes/news.scope.in \ master-scopes/notes.scope.in \ master-scopes/reference.scope.in \ master-scopes/web.scope.in \ master-scopes/weather.scope.in \ master-scopes/recipes.scope.in \ master-scopes/calendar.scope.in \ master-scopes/searchin.scope.in \ $(NULL) scopedir = $(SCOPESDIR) scope_DATA = $(scope_in_files:.scope.in=.scope) icondir = $(datadir)/unity/themes @INTLTOOL_SCOPE_RULE@ dbus_servicesdir = $(DBUSSERVICEDIR) service_in_files = \ unity-scope-home.service.in \ $(NULL) dbus_services_DATA = $(service_in_files:.service.in=.service) %.service: %.service.in $(AM_V_GEN)sed -e "s|\@pkglibexecdir\@|$(pkglibexecdir)|" $< > $@ CLEANFILES = \ unity-scope-home.service \ home.scope \ $(NULL) EXTRA_DIST = \ $(scope_in_files) \ $(service_in_files) unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/0000755000015600001650000000000012657434047023143 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/music.scope.in.in0000644000015600001650000000374612657432773026346 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/music Icon=@prefix@/share/unity/icons/lens-nav-music.svg CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-music.svg _Name=Music _SearchHint=Search music Type=music IsMaster=true Shortcut=m _Keywords=music; _NoContentHint=There is no music currently available on this computer. [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category global] _Name=Music Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-music.svg ContentType=music [Category recent] _Name=Recently played Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-songs.svg ContentType=music Renderer=carousel [Category songs] _Name=Songs Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-songs.svg DedupField=uri ContentType=music Renderer=carousel [Category albums] _Name=Albums Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-albums.svg DedupField=uri ContentType=music [Category online] _Name=Online Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-internet.svg DedupField=uri ContentType=music [Category popular] _Name=Popular online Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-internet.svg DedupField=uri ContentType=music [Category radio] _Name=Radio Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-songs.svg DedupField=uri ContentType=music [Category more] _Name=More suggestions Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg DedupField=uri ContentType=music [Filter decade] _Name=Decade Type=filter-multirange OptionIDs=0;1960;1970;1980;1990;2000;2010; _OptionNames=Old;60s;70s;80s;90s;00s;10s; [Filter genre] _Name=Genre Type=filter-checkoption-compact OptionIDs=blues;classical;country;disco;funk;rock;metal;hip-hop;house;new-wave;r-and-b;punk;jazz;pop;reggae;soul;techno;other; _OptionNames=Blues;Classical;Country;Disco;Funk;Rock;Metal;Hip-hop;House;New-wave;R&B;Punk;Jazz;Pop;Reggae;Soul;Techno;Other; unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/calendar.scope.in.in0000644000015600001650000000064512657432773026772 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/calendar Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-calendar.svg _Name=Calendar Type=calendar IsMaster=true _Keywords=calendar; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category upcoming] _Name=Upcoming Icon= DedupField=uri [Category recent] _Name=Recent Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/notes.scope.in.in0000644000015600001650000000066012657432773026346 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/notes Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-info.svg _Name=Notes Type=notes IsMaster=true _Keywords=notes; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category recent] _Name=Recent Icon= DedupField=uri [Category most-used] _Name=Most used Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/news.scope.in.in0000644000015600001650000000075412657432773026176 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/news Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-news.svg _Name=News Type=news IsMaster=true _Keywords=news; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category recent] _Name=Recent Icon= DedupField=uri [Category top] _Name=Top Icon= DedupField=uri [Category local] _Name=Local Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/graphics.scope.in.in0000644000015600001650000000063312657432773027016 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/graphics Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-graphics.svg _Name=Graphics Type=graphics IsMaster=true _Keywords=graphics; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category top] _Name=Top Icon= DedupField=uri [Category recent] _Name=Recent Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/recipes.scope.in.in0000644000015600001650000000055112657432773026647 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/recipes Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-recipes.svg _Name=Recipes Type=recipes IsMaster=true _Keywords=recipes; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category recipes] _Name=Recipes Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/files.scope.in.in0000644000015600001650000000237312657432773026323 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/files Icon=@prefix@/share/unity/icons/lens-nav-file.svg CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-files.svg _Name=Files & Folders _SearchHint=Search files & folders Type=files IsMaster=true Shortcut=f _Keywords=files; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category recent] _Name=Recent Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-recent.svg DedupField=uri [Category downloads] _Name=Downloads Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-downloads.svg DedupField=uri [Category folders] _Name=Folders Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-folders.svg DedupField=uri [Filter modified] _Name=Last modified Type=filter-radiooption OptionIDs=last-7-days;last-30-days;last-year; _OptionNames=Last 7 days;Last 30 days;Last year; [Filter type] _Name=Type Type=filter-checkoption SortType=display-name OptionIDs=documents;folders;images;audio;videos;presentations;other; _OptionNames=Documents;Folders;Images;Audio;Videos;Presentations;Other; [Filter size] _Name=Size Type=filter-multirange OptionIDs=1KB;100KB;1MB;10MB;100MB;1GB;>1GB; _OptionNames=1KB;100KB;1MB;10MB;100MB;1GB;>1GB; unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/help.scope.in.in0000644000015600001650000000062312657432773026145 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/help Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-help.svg _Name=Help Type=help IsMaster=true _Keywords=help; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category help] _Name=Help Icon= DedupField=title [Category community] _Name=Community Icon= DedupField=title unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/boxes.scope.in.in0000644000015600001650000000053212657432773026334 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/boxes Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-boxes.svg _Name=Boxes Type=boxes IsMaster=true _Keywords=boxes; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category boxes] _Name=Boxes Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/video.scope.in.in0000644000015600001650000000227612657432773026331 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/video Icon=@prefix@/share/unity/icons/lens-nav-video.svg CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-videos.svg _Name=Videos _SearchHint=Search videos Type=video IsMaster=true Shortcut=v _Keywords=videos;video; _NoContentHint=There are no videos currently available on this computer. [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category global] _Name=Videos Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-videos.svg ContentType=video Renderer=carousel [Category local] _Name=My videos Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-videos.svg DedupField=uri ContentType=video Renderer=carousel [Category online] _Name=Online Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-internet.svg DedupField=uri ContentType=video [Category popular] _Name=Popular online Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-internet.svg DedupField=uri ContentType=video [Category more] _Name=More suggestions Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg DedupField=uri ContentType=video RendererHint=portrait unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/more_suggestions.scope.in.in0000644000015600001650000000057712657432773030621 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/moresuggestions Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg _Name=More suggestions Type=moresug IsMaster=true [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category more_suggestions] _Name=More suggestions Icon= SortField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/info.scope.in.in0000644000015600001650000000062512657432773026152 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/info Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-info.svg _Name=Info Type=info IsMaster=true _Keywords=info; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category results] _Name=Results Icon= DedupField=uri [Category locations] _Name=Locations Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/code.scope.in.in0000644000015600001650000000072112657432773026126 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/code Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-developer.svg _Name=Code Type=code IsMaster=true _Keywords=code; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category documentation] _Name=Documentation Icon= DedupField=uri [Category code] _Name=Code Icon= DedupField=uri [Category people] _Name=People Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/weather.scope.in.in0000644000015600001650000000070412657432773026654 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/weather Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-info.svg _Name=Weather Type=weather IsMaster=true _Keywords=weather; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category global] _Name=Weather Icon= DedupField=uri ContentType=weather [Category weather] _Name=Weather Icon= DedupField=uri ContentType=weather unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/web.scope.in.in0000644000015600001650000000062512657432773025774 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/web Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-internet.svg _Name=Web Type=web IsMaster=true _Keywords=web; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category history] _Name=History Icon= DedupField=uri [Category bookmarks] _Name=Bookmarks Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/books.scope.in.in0000644000015600001650000000104112657432773026325 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/books Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-books.svg _Name=Books Type=books IsMaster=true _Keywords=books; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category recent] _Name=Recent Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-favouritefolders.svg DedupField=uri [Category top] _Name=Top Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-favouritefolders.svg DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/applications.scope.in.in0000644000015600001650000000403412657432773027703 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/applications Icon=@prefix@/share/unity/icons/lens-nav-app.svg _Name=Applications _SearchHint=Search applications Type=applications IsMaster=true Shortcut=a OptionalMetadata=scope_disabled[u] [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category global] _Name=Applications Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-apps.svg DedupField=uri ContentType=apps [Category apps] _Name=Applications Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-apps.svg DedupField=uri ContentType=apps [Category recently-used] _Name=Recently used Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-apps.svg DedupField=uri ContentType=apps [Category recent] _Name=Recent apps Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-apps.svg DedupField=uri ContentType=apps Renderer=special [Category installed] _Name=Installed Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-installed.svg DedupField=uri ContentType=apps [Category more] _Name=More suggestions Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-treat-yourself.svg DedupField=uri ContentType=apps [Category updates] _Name=Updates Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-installed.svg DedupField=uri ContentType=apps [Category scopes] _Name=Dash plugins Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-installed.svg DedupField=uri SortField=scope_disabled;title; ContentType=apps RendererHint=toggled [Filter type] _Name=Type Type=filter-checkoption SortType=display-name OptionIDs=accessories;education;game;graphics;internet;fonts;office;media;customisation;accessibility;developer;science-and-engineering;scopes;system _OptionNames=Accessories;Education;Games;Graphics;Internet;Fonts;Office;Media;Customisation;Accessibility;Developer;Science & Engineering;Dash plugins;System [Filter unity-sources] _Name=Sources Type=filter-checkoption SortType=display-name OptionIDs=local;usc _OptionNames=Local apps;Software center unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/searchin.scope.in.in0000644000015600001650000000052612657432773027013 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/searchin Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-internet.svg _Name=Search in Type=web IsMaster=true [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category global] _Name=Search in Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/reference.scope.in.in0000644000015600001650000000105012657432773027146 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/reference Icon= CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-reference.svg _Name=Reference Type=reference IsMaster=true _Keywords=reference; [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category encyclopedia] _Name=Encyclopedia Icon= DedupField=uri [Category vocabulary] _Name=Vocabulary Icon= DedupField=uri [Category scholar] _Name=Scholar Icon= DedupField=uri [Category sources] _Name=Sources Icon= DedupField=uri unity-scope-home-6.8.2+16.04.20160212.1/data/master-scopes/photos.scope.in.in0000644000015600001650000000217512657432773026535 0ustar pbuserpbgroup00000000000000[Scope] GroupName=com.canonical.Unity.Scope.Home UniqueName=/com/canonical/unity/masterscope/photos Icon=@prefix@/share/unity/icons/lens-nav-photo.svg CategoryIcon=@prefix@/share/icons/unity-icon-theme/places/svg/group-photos.svg _Name=Photos _SearchHint=Search photos Type=photos IsMaster=true Shortcut=c _Keywords=photos; _NoContentHint=There are no photos currently available on this computer. [Desktop Entry] X-Ubuntu-Gettext-Domain=unity-scope-home [Category recent] _Name=Recent Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-recent.svg DedupField=uri SortField=comment [Category mine] _Name=My photos Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-photos.svg DedupField=uri SortField=comment [Category friends] _Name=Friends Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-friends.svg DedupField=uri SortField=comment [Category online] _Name=Online photos Icon=@prefix@/share/icons/unity-icon-theme/places/svg/group-internet.svg DedupField=uri SortField=comment [Filter date] _Name=Date Type=filter-radiooption OptionIDs=7;30;180;100000; _OptionNames=Last 7 days;Last 30 days;Last 6 months;Older; unity-scope-home-6.8.2+16.04.20160212.1/vapi/0000755000015600001650000000000012657434047020404 5ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/vapi/libsoup-gnome-2.4.vapi0000644000015600001650000000064712657432773024361 0ustar pbuserpbgroup00000000000000[CCode (cprefix = "Soup", gir_namespace = "SoupGNOME", gir_version = "2.4", lower_case_cprefix = "soup_")] namespace SoupGNOME { [CCode (cheader_filename = "libsoup/soup-gnome.h", type_id = "soup_proxy_resolver_gnome_get_type ()")] public class ProxyResolverGNOME : Soup.ProxyResolverDefault, Soup.ProxyURIResolver, Soup.SessionFeature { [CCode (has_construct_function = false)] protected ProxyResolverGNOME (); } } unity-scope-home-6.8.2+16.04.20160212.1/vapi/libuuid.vapi0000644000015600001650000000176312657432773022735 0ustar pbuserpbgroup00000000000000[CCode (cheader_filename = "uuid.h", lower_case_cprefix = "")] namespace UUID { private static void uuid_generate_time ([CCode (array_length=false)] uint8[] data); private static void uuid_generate_random ([CCode (array_length=false)] uint8[] data); private static void uuid_unparse ([CCode (array_length=false)] uint8[] uuid, [CCode (array_length=false)] char[] str); /** Generate Time-UUID with node (MAC) bytes replaced with random bytes, so that it doesn't contain any sensitive data. */ public static string randomized_time_uuid () { char[] res = new char[37]; uint8[] data1 = new uint8[16]; uuid_generate_time (data1); uint8[] data2 = new uint8[16]; uuid_generate_random (data2); // last 6 bytes are MAC, overwrite with random bytes and set multicast bit, see section 4.5 of RFC 4122. for (int i = 10; i<16; i++) data1[i] = data2[i]; data1[10] |= 1; uuid_unparse (data1, res); string uuid_str = (string)res; return uuid_str; } } unity-scope-home-6.8.2+16.04.20160212.1/INSTALL0000644000015600001650000003660012657432773020507 0ustar pbuserpbgroup00000000000000Installation Instructions ************************* Copyright (C) 1994-1996, 1999-2002, 2004-2011 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. Basic Installation ================== Briefly, the shell commands `./configure; make; make install' should configure, build, and install this package. The following more-detailed instructions are generic; see the `README' file for instructions specific to this package. Some packages provide this `INSTALL' file but do not implement all of the features documented below. The lack of an optional feature in a given package is not necessarily a bug. More recommendations for GNU packages can be found in *note Makefile Conventions: (standards)Makefile Conventions. The `configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a `Makefile' in each directory of the package. It may also create one or more `.h' files containing system-dependent definitions. Finally, it creates a shell script `config.status' that you can run in the future to recreate the current configuration, and a file `config.log' containing compiler output (useful mainly for debugging `configure'). It can also use an optional file (typically called `config.cache' and enabled with `--cache-file=config.cache' or simply `-C') that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how `configure' could check whether to do them, and mail diffs or instructions to the address given in the `README' so they can be considered for the next release. If you are using the cache, and at some point `config.cache' contains results you don't want to keep, you may remove or edit it. The file `configure.ac' (or `configure.in') is used to create `configure' by a program called `autoconf'. You need `configure.ac' if you want to change it or regenerate `configure' using a newer version of `autoconf'. The simplest way to compile this package is: 1. `cd' to the directory containing the package's source code and type `./configure' to configure the package for your system. Running `configure' might take a while. While running, it prints some messages telling which features it is checking for. 2. Type `make' to compile the package. 3. Optionally, type `make check' to run any self-tests that come with the package, generally using the just-built uninstalled binaries. 4. Type `make install' to install the programs and any data files and documentation. When installing into a prefix owned by root, it is recommended that the package be configured and built as a regular user, and only the `make install' phase executed with root privileges. 5. Optionally, type `make installcheck' to repeat any self-tests, but this time using the binaries in their final installed location. This target does not install anything. Running this target as a regular user, particularly if the prior `make install' required root privileges, verifies that the installation completed correctly. 6. You can remove the program binaries and object files from the source code directory by typing `make clean'. To also remove the files that `configure' created (so you can compile the package for a different kind of computer), type `make distclean'. There is also a `make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. 7. Often, you can also type `make uninstall' to remove the installed files again. In practice, not all packages have tested that uninstallation works correctly, even though it is required by the GNU Coding Standards. 8. Some packages, particularly those that use Automake, provide `make distcheck', which can by used by developers to test that all other targets like `make install' and `make uninstall' work correctly. This target is generally not run by end users. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the `configure' script does not know about. Run `./configure --help' for details on some of the pertinent environment variables. You can give `configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c99 CFLAGS=-g LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you can use GNU `make'. `cd' to the directory where you want the object files and executables to go and run the `configure' script. `configure' automatically checks for the source code in the directory that `configure' is in and in `..'. This is known as a "VPATH" build. With a non-GNU `make', it is safer to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use `make distclean' before reconfiguring for another architecture. On MacOS X 10.5 and later systems, you can create libraries and executables that work on multiple system types--known as "fat" or "universal" binaries--by specifying multiple `-arch' options to the compiler but only a single `-arch' option to the preprocessor. Like this: ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CPP="gcc -E" CXXCPP="g++ -E" This is not guaranteed to produce working output in all cases, you may have to build one architecture at a time and combine the results using the `lipo' tool if you have problems. Installation Names ================== By default, `make install' installs the package's commands under `/usr/local/bin', include files under `/usr/local/include', etc. You can specify an installation prefix other than `/usr/local' by giving `configure' the option `--prefix=PREFIX', where PREFIX must be an absolute file name. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option `--exec-prefix=PREFIX' to `configure', the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like `--bindir=DIR' to specify different values for particular kinds of files. Run `configure --help' for a list of the directories you can set and what kinds of files go in them. In general, the default for these options is expressed in terms of `${prefix}', so that specifying just `--prefix' will affect all of the other directory specifications that were not explicitly provided. The most portable way to affect installation locations is to pass the correct locations to `configure'; however, many packages provide one or both of the following shortcuts of passing variable assignments to the `make install' command line to change installation locations without having to reconfigure or recompile. The first method involves providing an override variable for each affected directory. For example, `make install prefix=/alternate/directory' will choose an alternate location for all directory configuration variables that were expressed in terms of `${prefix}'. Any directories that were specified during `configure', but not in terms of `${prefix}', must each be overridden at install time for the entire installation to be relocated. The approach of makefile variable overrides for each directory variable is required by the GNU Coding Standards, and ideally causes no recompilation. However, some platforms have known limitations with the semantics of shared libraries that end up requiring recompilation when using this method, particularly noticeable in packages that use GNU Libtool. The second method involves providing the `DESTDIR' variable. For example, `make install DESTDIR=/alternate/directory' will prepend `/alternate/directory' before all installation names. The approach of `DESTDIR' overrides is not required by the GNU Coding Standards, and does not work on platforms that have drive letters. On the other hand, it does better at avoiding recompilation issues, and works well even when some directory options were not specified in terms of `${prefix}' at `configure' time. Optional Features ================= If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving `configure' the option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. Some packages pay attention to `--enable-FEATURE' options to `configure', where FEATURE indicates an optional part of the package. They may also pay attention to `--with-PACKAGE' options, where PACKAGE is something like `gnu-as' or `x' (for the X Window System). The `README' should mention any `--enable-' and `--with-' options that the package recognizes. For packages that use the X Window System, `configure' can usually find the X include and library files automatically, but if it doesn't, you can use the `configure' options `--x-includes=DIR' and `--x-libraries=DIR' to specify their locations. Some packages offer the ability to configure how verbose the execution of `make' will be. For these packages, running `./configure --enable-silent-rules' sets the default to minimal output, which can be overridden with `make V=1'; while running `./configure --disable-silent-rules' sets the default to verbose, which can be overridden with `make V=0'. Particular systems ================== On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC is not installed, it is recommended to use the following options in order to use an ANSI C compiler: ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" and if that doesn't work, install pre-built binaries of GCC for HP-UX. HP-UX `make' updates targets which have the same time stamps as their prerequisites, which makes it generally unusable when shipped generated files such as `configure' are involved. Use GNU `make' instead. On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its `' header file. The option `-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended to try ./configure CC="cc" and if that doesn't work, try ./configure CC="cc -nodtk" On Solaris, don't put `/usr/ucb' early in your `PATH'. This directory contains several dysfunctional programs; working variants of these programs are available in `/usr/bin'. So, if you need `/usr/ucb' in your `PATH', put it _after_ `/usr/bin'. On Haiku, software installed for all users goes in `/boot/common', not `/usr/local'. It is recommended to use the following options: ./configure --prefix=/boot/common Specifying the System Type ========================== There may be some features `configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, `configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the `--build=TYPE' option. TYPE can either be a short name for the system type, such as `sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file `config.sub' for the possible values of each field. If `config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the option `--target=TYPE' to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with `--host=TYPE'. Sharing Defaults ================ If you want to set default values for `configure' scripts to share, you can create a site shell script called `config.site' that gives default values for variables like `CC', `cache_file', and `prefix'. `configure' looks for `PREFIX/share/config.site' if it exists, then `PREFIX/etc/config.site' if it exists. Or, you can set the `CONFIG_SITE' environment variable to the location of the site script. A warning: not all `configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to `configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the `configure' command line, using `VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified `gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for `CONFIG_SHELL' due to an Autoconf bug. Until the bug is fixed you can use this workaround: CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash `configure' Invocation ====================== `configure' recognizes the following options to control how it operates. `--help' `-h' Print a summary of all of the options to `configure', and exit. `--help=short' `--help=recursive' Print a summary of the options unique to this package's `configure', and exit. The `short' variant lists options used only in the top level, while the `recursive' variant lists options also present in any nested packages. `--version' `-V' Print the version of Autoconf used to generate the `configure' script, and exit. `--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally `config.cache'. FILE defaults to `/dev/null' to disable caching. `--config-cache' `-C' Alias for `--cache-file=config.cache'. `--quiet' `--silent' `-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to `/dev/null' (any error messages will still be shown). `--srcdir=DIR' Look for the package's source code in directory DIR. Usually `configure' can determine that directory automatically. `--prefix=DIR' Use DIR as the installation prefix. *note Installation Names:: for more details, including other options available for fine-tuning the installation locations. `--no-create' `-n' Run the configure checks, but stop before creating any output files. `configure' also accepts some other, not widely useful, options. Run `configure --help' for more details. unity-scope-home-6.8.2+16.04.20160212.1/configure.ac0000644000015600001650000001762112657432777021752 0ustar pbuserpbgroup00000000000000AC_INIT(unity-scope-home, 6.8.3, https://launchpad.net/unity-scope-home) AC_COPYRIGHT([Copyright 2012 Canonical]) AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION) ##################################################### # Silent build rules ##################################################### m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) AC_PREREQ(2.59) AC_CONFIG_HEADERS([config.h]) ##################################################### # Init the other things we depend on ##################################################### AM_MAINTAINER_MODE AM_PROG_VALAC([0.18.0]) AS_IF([test -z "$VALAC"], [AC_MSG_ERROR(["No valac compiler found."])]) AC_PROG_CC AM_PROG_CC_C_O AC_HEADER_STDC LT_INIT AC_CONFIG_MACRO_DIR([m4]) ############################################# # Gettext ############################################# GETTEXT_PACKAGE="$PACKAGE" AC_SUBST(GETTEXT_PACKAGE) AC_SUBST([CONFIG_STATUS_DEPENDENCIES],['$(top_srcdir)/po/LINGUAS']) AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [gettext domain]) AM_GLIB_GNU_GETTEXT # AM_GNOME_GETTEXT above substs $DATADIRNAME # this is the directory where the *.{mo,gmo} files are installed localedir='${prefix}/${DATADIRNAME}/locale' AC_SUBST(localedir) IT_PROG_INTLTOOL([0.40.0]) AC_DEFINE_UNQUOTED(LOCALE_DIR, "${PREFIX}/${DATADIRNAME}/locale",[Locale directory]) AC_DEFINE_UNQUOTED(DATADIR, "${PREFIX}/${DATADIRNAME}",[Data directory]) AC_DEFINE_UNQUOTED(PREFIXDIR, "${PREFIX}",[Prefix directory]) ###################################################### # intltool rule for generating translated .scope file ###################################################### INTLTOOL_SCOPE_RULE='%.scope: %.scope.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' AC_SUBST(INTLTOOL_SCOPE_RULE) ##################################################### # Check for module and library dependancies ##################################################### GLIB_REQUIRED=2.27 LIBUNITY_REQURED=7.1.3 PKG_CHECK_MODULES(HOME_SCOPE, glib-2.0 >= $GLIB_REQUIRED gobject-2.0 >= $GLIB_REQUIRED gio-2.0 >= $GLIB_REQUIRED gio-unix-2.0 >= $GLIB_REQUIRED dee-1.0 >= 1.0.7 gee-0.8 json-glib-1.0 libsoup-gnome-2.4 uuid >= 2.20.0 unity >= $LIBUNITY_REQURED unity-protocol-private >= $LIBUNITY_REQURED unity-extras >= $LIBUNITY_REQURED ) AC_SUBST(HOME_SCOPE_CFLAGS) AC_SUBST(HOME_SCOPE_LIBS) #################################################################### # C compiler warnings #################################################################### AC_ARG_ENABLE([c-warnings], AC_HELP_STRING([--enable-c-warnings=@<:@no/yes@:>@], [show warnings from the C compiler @<:@default=no@:>@]),, [enable_c_warnings=no]) if test "x$enable_c_warnings" = "xyes"; then AC_DEFINE(ENABLE_C_WARNINGS, 1, [show warnings from the C compiler]) fi AM_CONDITIONAL(ENABLE_C_WARNINGS, test "$enable_c_warnings" = "yes") ##################################################### # local install for distcheck and stand-alone running ##################################################### with_localinstall="no" AC_ARG_ENABLE(localinstall, AS_HELP_STRING([--enable-localinstall], [Install all of the files locally instead of in system directories (for distcheck)]), with_localinstall=$enableval, with_localinstall=no) AM_CONDITIONAL([HAVE_LOCALINSTALL], [test "x$with_localinstall" = "xyes"]) #################################################################### # Headless tests #################################################################### AC_ARG_ENABLE([headless-tests], AS_HELP_STRING([--enable-headless-tests=@<:@no/yes@:>@],[enable headless test suite (requires Xvfb) @<:@default=no@:>@]),, [enable_headless_tests=yes]) AM_CONDITIONAL([ENABLE_HEADLESS_TESTS],[test "x$enable_headless_tests" != "xno"]) if test "x$enable_headless_tests" = "xyes"; then AC_PATH_PROG([XVFB],[xvfb-run]) fi ########################### # gcov coverage reporting ########################### m4_include([m4/gcov.m4]) AC_TDD_GCOV AM_CONDITIONAL([HAVE_GCOV], [test "x$ac_cv_check_gcov" = xyes]) AM_CONDITIONAL([HAVE_LCOV], [test "x$ac_cv_check_lcov" = xyes]) AM_CONDITIONAL([HAVE_GCOVR], [test "x$ac_cv_check_gcovr" = xyes]) AC_SUBST(COVERAGE_CFLAGS) AC_SUBST(COVERAGE_LDFLAGS) ##################################################### # local install for distcheck and stand-alone running ##################################################### with_localinstall="no" AC_ARG_ENABLE(localinstall, AS_HELP_STRING([--enable-localinstall], [Install all of the files locally instead of in system directories (for distcheck)]), with_localinstall=$enableval, with_localinstall=no) AM_CONDITIONAL([HAVE_LOCALINSTALL], [test "x$with_localinstall" = "xyes"]) ##################################################### # Expand variables needed for config.vala ##################################################### AS_AC_EXPAND(PREFIX, $prefix) AC_SUBST(PREFIX) AS_AC_EXPAND(DATADIR, $datarootdir) AC_SUBST(DATADIR) ##################################################### # Look for protocol-private lib dir ##################################################### PROTOCOLPRIVATELIBDIR="`$PKG_CONFIG --variable=libdir unity-protocol-private`/libunity" AC_SUBST(PROTOCOLPRIVATELIBDIR) ##################################################### # Look for dbus service dir ##################################################### if test "x$with_localinstall" = "xyes"; then DBUSSERVICEDIR="${datadir}/dbus-1/services/" else DBUSSERVICEDIR=`$PKG_CONFIG --variable=session_bus_services_dir dbus-1` fi AC_SUBST(DBUSSERVICEDIR) ##################################################### # Look for correct Scopes dir ##################################################### if test "x$with_localinstall" = "xyes"; then SCOPESDIR="${datadir}/unity/scopes" else SCOPESDIR=`$PKG_CONFIG --variable=scopesdir unity` fi AC_SUBST(SCOPESDIR) ############################################# # GSettings macros ############################################# GLIB_GSETTINGS ##################################################### # Create the Makefiles ##################################################### AC_CONFIG_FILES([ Makefile src/Makefile data/Makefile tests/unit/Makefile src/config.vala tests/unit/config-tests.vala data/home.scope.in data/master-scopes/applications.scope.in data/master-scopes/files.scope.in data/master-scopes/music.scope.in data/master-scopes/video.scope.in data/master-scopes/photos.scope.in data/master-scopes/more_suggestions.scope.in data/master-scopes/books.scope.in data/master-scopes/boxes.scope.in data/master-scopes/code.scope.in data/master-scopes/graphics.scope.in data/master-scopes/help.scope.in data/master-scopes/info.scope.in data/master-scopes/news.scope.in data/master-scopes/notes.scope.in data/master-scopes/reference.scope.in data/master-scopes/weather.scope.in data/master-scopes/web.scope.in data/master-scopes/recipes.scope.in data/master-scopes/calendar.scope.in data/master-scopes/searchin.scope.in po/Makefile.in ]) AC_OUTPUT ##################################################### # Output the results ##################################################### AC_MSG_NOTICE([ Unity Home Scope Daemon $VERSION ---------------------------------- Prefix : ${prefix} Local install : ${with_localinstall} Extra CFlags : ${CPPFLAGS} $MAINTAINER_CFLAGS Extra ValaFlags : ${CPPFLAGS} $MAINTAINER_VALAFLAGS Scopes Directory: ${SCOPESDIR} Testing Headless tests : ${enable_headless_tests} Coverage reporting : ${use_gcov} ]) unity-scope-home-6.8.2+16.04.20160212.1/NEWS0000644000015600001650000000000012657432773020136 0ustar pbuserpbgroup00000000000000unity-scope-home-6.8.2+16.04.20160212.1/Makefile.decl0000644000015600001650000000540712657432773022025 0ustar pbuserpbgroup00000000000000# GLIB - Library of useful C routines # # This file is copied almost verbatim from the GLib-2.0 distribution # GTESTER = gtester GTESTER_REPORT = gtester-report # initialize variables for unconditional += appending EXTRA_DIST = TEST_PROGS = ### testing rules # test: run all tests in cwd and subdirs test: test-nonrecursive @ for subdir in $(SUBDIRS) . ; do \ test "$$subdir" = "." -o "$$subdir" = "po" || \ ( cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $@ ) || exit $? ; \ done # test-nonrecursive: run tests only in cwd test-nonrecursive: ${TEST_PROGS} @test -z "${TEST_PROGS}" || G_DEBUG=gc-friendly MALLOC_CHECK_=2 MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256)) ${GTESTER} --verbose ${TEST_PROGS} # test-report: run tests in subdirs and generate report # perf-report: run tests in subdirs with -m perf and generate report # full-report: like test-report: with -m perf and -m slow test-report perf-report full-report: ${TEST_PROGS} @test -z "${TEST_PROGS}" || { \ case $@ in \ test-report) test_options="-k";; \ perf-report) test_options="-k -m=perf";; \ full-report) test_options="-k -m=perf -m=slow";; \ esac ; \ if test -z "$$GTESTER_LOGDIR" ; then \ ${GTESTER} --verbose $$test_options -o test-report.xml ${TEST_PROGS} ; \ elif test -n "${TEST_PROGS}" ; then \ ${GTESTER} --verbose $$test_options -o `mktemp "$$GTESTER_LOGDIR/log-XXXXXX"` ${TEST_PROGS} ; \ fi ; \ } @ ignore_logdir=true ; \ if test -z "$$GTESTER_LOGDIR" ; then \ GTESTER_LOGDIR=`mktemp -d "\`pwd\`/.testlogs-XXXXXX"`; export GTESTER_LOGDIR ; \ ignore_logdir=false ; \ fi ; \ REVISION=$(VERSION) ; \ for subdir in $(SUBDIRS) . ; do \ test "$$subdir" = "." -o "$$subdir" = "po" || \ ( cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) $@ ) || exit $? ; \ done ; \ $$ignore_logdir || { \ echo '' > $@.xml ; \ echo '' >> $@.xml ; \ echo '' >> $@.xml ; \ echo ' $(PACKAGE)' >> $@.xml ; \ echo ' $(VERSION)' >> $@.xml ; \ echo " $$REVISION" >> $@.xml ; \ echo '' >> $@.xml ; \ for lf in `ls -L "$$GTESTER_LOGDIR"/.` ; do \ sed '1,1s/^?]*?>//' <"$$GTESTER_LOGDIR"/"$$lf" >> $@.xml ; \ done ; \ echo >> $@.xml ; \ echo '' >> $@.xml ; \ rm -rf "$$GTESTER_LOGDIR"/ ; \ ${GTESTER_REPORT} --version 2>/dev/null 1>&2 ; test "$$?" != 0 || ${GTESTER_REPORT} $@.xml >$@.html ; \ } .PHONY: test test-report perf-report full-report test-nonrecursive # run tests in cwd as part of make check if ENABLE_HEADLESS_TESTS check-local: test-headless else check-local: test-nonrecursive endif unity-scope-home-6.8.2+16.04.20160212.1/ChangeLog0000644000015600001650000000000012657432773021211 0ustar pbuserpbgroup00000000000000