3
# Copyright 2012 Canonical Ltd.
5
# Contact: Alberto Mardegan <alberto.mardegan@canonical.com>
11
from gi.repository import GLib, GObject, Gio
12
from gi.repository import Accounts, Signon
13
from gi.repository import GData
14
from gi.repository import Unity, UnityExtras
16
from datetime import datetime, timedelta
20
APP_NAME = "unity-scope-gdrive"
21
LOCAL_PATH = "/usr/share/locale/"
23
gettext.bindtextdomain(APP_NAME, LOCAL_PATH)
24
gettext.textdomain(APP_NAME)
28
# The primary bus name we grab *must* match what we specify in our .scope file
30
BUS_NAME = "com.canonical.Unity.Scope.File.Gdrive"
31
# Map Google Docs types to the values of the "type" filter
33
"document": "documents",
37
"presentation": "presentations"
39
THEME = "/usr/share/icons/unity-icon-theme/places/svg/"
44
self._scope = Unity.DeprecatedScope.new ("/com/canonical/unity/scope/file/gdrive", "gdrive")
45
self._scope.search_in_global = True;
46
self._preferences = Unity.PreferencesManager.get_default()
47
self._preferences.connect ("notify::remote-content-search",
48
self._on_filters_or_preferences_changed);
50
self._gdocs_accounts = []
52
self._account_manager = Accounts.Manager.new_for_service_type("documents")
53
except TypeError as e:
54
print ("Couldn't start account manager, not initialising: %s" % e)
56
self._account_manager.connect("enabled-event", self._on_enabled_event);
57
for account in self._account_manager.get_enabled_account_services():
58
self.add_account_service(account)
60
# Listen for changes and requests
61
self._scope.connect ("search-changed", self._on_search_changed)
63
# This allows us to re-do the search if any parameter on a filter has changed
64
# Though it's possible to connect to a more-specific changed signal on each
65
# Fitler, as we re-do the search anyway, this catch-all signal is perfect for
67
self._scope.connect ("notify::filtering", self._on_filters_or_preferences_changed);
69
filters = Unity.FilterSet.new()
70
f = Unity.RadioOptionFilter.new ("modified", _("Last modified"), Gio.ThemedIcon.new("input-keyboard-symbolic"), False)
71
f.add_option ("last-7-days", _("Last 7 days"), None)
72
f.add_option ("last-30-days", _("Last 30 days"), None)
73
f.add_option ("last-year", _("Last year"), None);
75
f2 = Unity.CheckOptionFilter.new ("type", _("Type"), Gio.ThemedIcon.new("input-keyboard-symbolic"), False)
76
f2.add_option ("documents", _("Documents"), None)
77
f2.add_option ("folders", _("Folders"), None)
78
f2.add_option ("images", _("Images"), None)
79
f2.add_option ("audio", _("Audio"), None)
80
f2.add_option ("videos", _("Videos"), None)
81
f2.add_option ("presentations", _("Presentations"), None)
82
f2.add_option ("other", _("Other"), None)
84
self._scope.props.filters = filters
85
cats = Unity.CategorySet.new()
86
cats.add (Unity.Category.new ('global',
88
Gio.ThemedIcon.new(THEME + "group-folders.svg"),
89
Unity.CategoryRenderer.VERTICAL_TILE))
90
cats.add (Unity.Category.new ('recent',
92
Gio.ThemedIcon.new(THEME + "group-recent.svg"),
93
Unity.CategoryRenderer.VERTICAL_TILE))
94
cats.add (Unity.Category.new ('downloads',
96
Gio.ThemedIcon.new(THEME + "group-downloads.svg"),
97
Unity.CategoryRenderer.VERTICAL_TILE))
98
cats.add (Unity.Category.new ('folders',
100
Gio.ThemedIcon.new(THEME + "group-folders.svg"),
101
Unity.CategoryRenderer.VERTICAL_TILE))
102
self._scope.props.categories = cats
105
def _on_enabled_event (self, account_manager, account_id):
106
account = self._account_manager.get_account(account_id)
107
for service in account.list_services():
108
account_service = Accounts.AccountService.new(account, service)
109
if account_service.get_enabled():
110
self.add_account_service(account_service)
112
def add_account_service(self, account_service):
113
for gdocs_account in self._gdocs_accounts:
114
if gdocs_account.get_account_service() == account_service:
116
gdocs_account = GDocsAccount(self._scope, account_service);
117
self._gdocs_accounts.append(gdocs_account)
119
def _on_search_changed (self, scope, search, search_type, cancellable):
120
search_string = search.props.search_string
121
results = search.props.results_model
124
if self._preferences.props.remote_content_search != Unity.PreferencesManagerRemoteContent.ALL:
125
search.emit("finished")
128
if search_type == Unity.SearchType.GLOBAL:
133
print("Search changed to: '%s'" % search_string)
135
for gdocs_account in self._gdocs_accounts:
136
gdocs_account.update_results_model (search_string, results, search, is_global)
137
search.emit("finished")
139
def _on_filters_or_preferences_changed (self, *_):
140
self._scope.queue_search_changed(Unity.SearchType.DEFAULT)
143
class SignOnAuthorizer(GObject.Object, GData.Authorizer):
144
__g_type_name__ = "SignOnAuthorizer"
145
def __init__(self, account_service):
146
GObject.Object.__init__(self)
147
self._account_service = account_service
148
self._main_loop = None
151
def do_process_request(self, domain, message):
152
message.props.request_headers.replace('Authorization', 'OAuth %s' % (self._token, ))
154
def do_is_authorized_for_domain(self, domain):
155
return True if self._token else False
157
def do_refresh_authorization(self, cancellable):
159
print("Authorization already in progress")
162
old_token = self._token
163
# Get the global account settings
164
auth_data = self._account_service.get_auth_data()
165
identity = auth_data.get_credentials_id()
166
session_data = auth_data.get_parameters()
167
self._auth_session = Signon.AuthSession.new(identity, auth_data.get_method())
168
self._main_loop = GLib.MainLoop()
169
self._auth_session.process(session_data,
170
auth_data.get_mechanism(),
173
self._main_loop.run()
174
if self._token == old_token:
175
print("Got the same token")
178
print("Got token: %s" % (self._token, ))
181
def login_cb(self, session, reply, error, user_data):
182
print("login finished")
183
self._main_loop.quit()
184
self._main_loop = None
186
print("Got authentication error:", error.message)
188
if "AuthToken" in reply:
189
self._token = reply["AuthToken"]
190
elif "AccessToken" in reply:
191
self._token = reply["AccessToken"]
193
print("Didn't find token in session:", reply)
196
# Encapsulates searching a single user's GDocs
198
def __init__ (self, scope, account_service):
200
self._account_service = account_service
201
self._account_service.connect("enabled", self._on_account_enabled)
202
self._enabled = self._account_service.get_enabled()
203
self._authenticating = False
204
authorizer = SignOnAuthorizer(self._account_service)
205
authorizer.refresh_authorization(None)
206
self._client = GData.DocumentsService(authorizer=authorizer)
208
def get_account_service (self):
209
return self._account_service
211
def _on_account_enabled (self, account, enabled):
212
print("account %s, enabled %s" % (account, enabled))
213
self._enabled = enabled
215
def update_results_model (self, search, model, s, is_global=False):
216
if not self._enabled:
219
# Get the list of documents
220
feed = self.get_doc_list(search, s, is_global);
222
rtype = entry.get_resource_id().split(":")[0]
227
if rtype == "folder":
232
model.append(uri=entry.look_up_link(GData.LINK_ALTERNATE).get_uri(),
233
icon_hint=self.icon_for_type(rtype),
235
mimetype="text/html",
236
title=entry.props.title,
238
dnd_uri=entry.props.content_uri,
239
result_type=Unity.ResultType.PERSONAL);
241
# This is where we do the actual search for documents
242
def get_doc_list (self, search, s, is_global):
243
query = GData.DocumentsQuery(q=search)
245
# We do not want filters to effect global results
247
self.apply_filters(query, s)
249
print("Searching for: " + query.props.q)
251
if not self._client.is_authorized():
252
if not self._client.props.authorizer.refresh_authorization(None):
255
feed = self._client.query_documents(query, None, None, None).get_entries()
256
except GObject.GError as e:
261
feed = self.filter_results(feed, s)
264
def apply_filters (self, query, s):
265
f = s.get_filter("modified")
267
o = f.get_active_option()
270
if o.props.id == "last-year":
272
elif o.props.id == "last-30-days":
274
elif o.props.id == "last-7-days":
277
last_time = datetime.now() - timedelta(age)
278
query.set_updated_min(time.mktime(last_time.timetuple()))
280
def filter_results (self, feed, s):
281
f = s.get_filter("type")
282
if not f: return feed
283
if not f.props.filtering:
287
rtype = entry.get_resource_id().split(":")[0]
288
filter_type = TYPE_MAP.get(rtype, "other")
289
if f.get_option(filter_type).props.active:
293
# Send back a useful icon depending on the document type
294
def icon_for_type (self, doc_type):
295
ret = "text-x-preview"
297
if doc_type == "pdf":
298
ret = "gnome-mime-application-pdf"
299
elif doc_type == "drawing":
300
ret = "x-office-drawing"
301
elif doc_type == "document":
302
ret = "x-office-document"
303
elif doc_type == "presentation":
304
ret = "libreoffice-oasis-presentation"
305
elif doc_type == "spreadsheet" or doc_type == "text/xml":
306
ret = "x-office-spreadsheet"
307
elif doc_type == "folder":
309
elif doc_type == "file":
310
ret = "gnome-fs-regular"
312
print("Unhandled icon type: ", doc_type)
316
if __name__ == '__main__':
317
daemon = UnityExtras.dbus_own_name(BUS_NAME, Daemon, None)
319
GLib.unix_signal_add(0, 2, lambda x: daemon.quit(), None)