Merge lp:~mardy/account-polld/external-plugins into lp:~ubuntu-push-hackers/account-polld/trunk
- external-plugins
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~mardy/account-polld/external-plugins |
Merge into: | lp:~ubuntu-push-hackers/account-polld/trunk |
Diff against target: |
8508 lines (+2883/-5145) (has conflicts) 78 files modified
.bzrignore (+12/-0) .qmake.conf (+2/-0) account-polld.pro (+11/-0) account-polld/account-polld.pro (+40/-0) account-polld/account_manager.cpp (+283/-0) account-polld/account_manager.h (+78/-0) account-polld/app_manager.cpp (+118/-0) account-polld/app_manager.h (+60/-0) account-polld/debug.cpp (+29/-0) account-polld/debug.h (+47/-0) account-polld/main.cpp (+63/-0) account-polld/plugin.cpp (+155/-0) account-polld/plugin.h (+55/-0) account-polld/poll_service.cpp (+190/-0) account-polld/poll_service.h (+65/-0) account-polld/push_client.cpp (+112/-0) account-polld/push_client.h (+48/-0) accounts/account-watcher.c (+0/-302) accounts/account-watcher.h (+0/-47) accounts/accounts.c (+0/-26) accounts/accounts.go (+0/-130) click-hook/account-polld.hook.in (+4/-0) click-hook/click-hook (+108/-0) click-hook/click-hook.pro (+20/-0) cmd/account-polld/account_service.go (+0/-186) cmd/account-polld/main.go (+0/-260) cmd/account-watcher-test/main.go (+0/-17) cmd/qtcontact-test/main.go (+0/-36) common-installs-config.pri (+43/-0) common-pkgconfig.pri (+12/-0) common-project-config.pri (+36/-0) common-vars.pri (+18/-0) coverage.pri (+48/-0) debian/control (+12/-17) debian/rules (+2/-33) gettext/LICENSE (+0/-20) gettext/README.md (+0/-94) gettext/gettext.go (+0/-207) plugins/caldav/api.go (+0/-54) plugins/caldav/caldav.go (+0/-201) plugins/dekko/api.go (+0/-127) plugins/dekko/dekko.go (+0/-346) plugins/gcalendar/api.go (+0/-54) plugins/gcalendar/gcalendar.go (+0/-189) plugins/gmail/api.go (+0/-127) plugins/gmail/gmail.go (+0/-346) plugins/plugins.go (+0/-239) plugins/twitter/oauth/README.markdown (+0/-22) plugins/twitter/oauth/examples_test.go (+0/-55) plugins/twitter/oauth/oauth.go (+0/-456) plugins/twitter/oauth/oauth_test.go (+0/-172) plugins/twitter/twitter.go (+0/-304) plugins/twitter/twitter_test.go (+0/-500) po/account-polld.pot (+0/-88) pollbus/bus.go (+0/-93) qtcontact/contacts.go (+0/-56) qtcontact/qtcontacts.cpp (+0/-70) qtcontact/qtcontacts.h (+0/-31) qtcontact/qtcontacts.hpp (+0/-32) qtcontact/qtcontacts.moc (+0/-88) syncmonitor/syncmonitor.go (+0/-93) tests/account-polld/account-polld.pro (+3/-0) tests/account-polld/data/com.ubuntu.tests_application.application (+13/-0) tests/account-polld/data/com.ubuntu.tests_coolshare.service (+7/-0) tests/account-polld/data/cool.provider (+6/-0) tests/account-polld/data/coolmail.service (+21/-0) tests/account-polld/data/mailer.application (+12/-0) tests/account-polld/fake_push_client.h (+60/-0) tests/account-polld/fake_signond.h (+52/-0) tests/account-polld/push_client.py (+39/-0) tests/account-polld/signond.py (+93/-0) tests/account-polld/test_plugin.py (+43/-0) tests/account-polld/tst_account_polld.cpp (+475/-0) tests/account-polld/tst_account_polld.pro (+36/-0) tests/click-hook/click-hook.pro (+21/-0) tests/click-hook/tst_click_hook.cpp (+327/-0) tests/tests.pro (+4/-0) update_translations.sh (+0/-27) Conflict: can't delete po because it is not empty. Not deleting. Conflict because po is not versioned, but has versioned children. Versioned directory. Contents conflict in po/aa.po Contents conflict in po/am.po Contents conflict in po/ast.po Contents conflict in po/az.po Contents conflict in po/br.po Contents conflict in po/bs.po Contents conflict in po/ca.po Contents conflict in po/ca@valencia.po Contents conflict in po/cs.po Contents conflict in po/da.po Contents conflict in po/de.po Contents conflict in po/el.po Contents conflict in po/en_AU.po Contents conflict in po/en_GB.po Contents conflict in po/es.po Contents conflict in po/eu.po Contents conflict in po/fa.po Contents conflict in po/fi.po Contents conflict in po/fr.po Contents conflict in po/gd.po Contents conflict in po/gl.po Contents conflict in po/he.po Contents conflict in po/hr.po Contents conflict in po/hu.po Contents conflict in po/is.po Contents conflict in po/it.po Contents conflict in po/ja.po Contents conflict in po/km.po Contents conflict in po/ko.po Contents conflict in po/lt.po Contents conflict in po/lv.po Contents conflict in po/nb.po Contents conflict in po/ne.po Contents conflict in po/nl.po Contents conflict in po/pl.po Contents conflict in po/pt.po Contents conflict in po/pt_BR.po Contents conflict in po/ro.po Contents conflict in po/ru.po Contents conflict in po/sl.po Contents conflict in po/sr.po Contents conflict in po/sv.po Contents conflict in po/ug.po Contents conflict in po/uk.po Contents conflict in po/vi.po Contents conflict in po/zh_CN.po Contents conflict in po/zh_TW.po |
To merge this branch: | bzr merge lp:~mardy/account-polld/external-plugins |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Push Hackers | Pending | ||
Review via email: mp+305223@code.launchpad.net |
Commit message
Support out-of-process plugins
Existing Go plugins have been moved into the account-
Description of the change
Support out-of-process plugins
Existing Go plugins have been moved into the account-
- 198. By Alberto Mardegan
-
Merge from trunk
- 199. By Alberto Mardegan
-
Update build dependencies
- 200. By Alberto Mardegan
-
Add python deps
- 201. By Alberto Mardegan
-
comma
- 202. By Alberto Mardegan
-
Enable C++11 for all components
- 203. By Alberto Mardegan
-
Hook: create dir if missing
- 204. By Alberto Mardegan
-
Rename output file
- 205. By Alberto Mardegan
-
fix creation of dir
- 206. By Alberto Mardegan
-
Fix hook
- 207. By Alberto Mardegan
-
Skip invalid applications
- 208. By Alberto Mardegan
-
Pass oauth client app data to plugin
- 209. By Alberto Mardegan
-
Make tests more robust
- 210. By Alberto Mardegan
-
Fix push object path
- 211. By Alberto Mardegan
-
Tests: increase plugin timeout
- 212. By Alberto Mardegan
-
more of the same
- 213. By Alberto Mardegan
-
Tests: wait till method returns
- 214. By Alberto Mardegan
-
Update VCS link
- 215. By Alberto Mardegan
-
Update dependencies and description
- 216. By Alberto Mardegan
-
Avoid cyclical deps
- 217. By Alberto Mardegan
-
Handle sigterm signal
This allows us to collect coverage results
- 218. By Alberto Mardegan
-
Fix reauthentication logic, add a test for that
Unmerged revisions
- 218. By Alberto Mardegan
-
Fix reauthentication logic, add a test for that
- 217. By Alberto Mardegan
-
Handle sigterm signal
This allows us to collect coverage results
- 216. By Alberto Mardegan
-
Avoid cyclical deps
- 215. By Alberto Mardegan
-
Update dependencies and description
- 214. By Alberto Mardegan
-
Update VCS link
- 213. By Alberto Mardegan
-
Tests: wait till method returns
- 212. By Alberto Mardegan
-
more of the same
- 211. By Alberto Mardegan
-
Tests: increase plugin timeout
- 210. By Alberto Mardegan
-
Fix push object path
- 209. By Alberto Mardegan
-
Make tests more robust
Preview Diff
1 | === added file '.bzrignore' |
2 | --- .bzrignore 1970-01-01 00:00:00 +0000 |
3 | +++ .bzrignore 2016-09-19 11:35:53 +0000 |
4 | @@ -0,0 +1,12 @@ |
5 | +*.moc |
6 | +Makefile* |
7 | +account-polld/account-polld |
8 | +click-hook/account-polld.hook |
9 | +debian/*.log |
10 | +debian/*.substvars |
11 | +debian/*-stamp |
12 | +debian/account-polld/ |
13 | +debian/files |
14 | +moc_* |
15 | +tests/account-polld/tst_account_polld |
16 | +tests/click-hook/tst_click_hook |
17 | |
18 | === added file '.qmake.conf' |
19 | --- .qmake.conf 1970-01-01 00:00:00 +0000 |
20 | +++ .qmake.conf 2016-09-19 11:35:53 +0000 |
21 | @@ -0,0 +1,2 @@ |
22 | +TOP_SRC_DIR = $$PWD |
23 | +TOP_BUILD_DIR = $$shadowed($$PWD) |
24 | |
25 | === added directory 'account-polld' |
26 | === added file 'account-polld.pro' |
27 | --- account-polld.pro 1970-01-01 00:00:00 +0000 |
28 | +++ account-polld.pro 2016-09-19 11:35:53 +0000 |
29 | @@ -0,0 +1,11 @@ |
30 | +include(common-vars.pri) |
31 | +include(common-project-config.pri) |
32 | + |
33 | +TEMPLATE = subdirs |
34 | +SUBDIRS = \ |
35 | + account-polld \ |
36 | + click-hook \ |
37 | + tests |
38 | +CONFIG += ordered |
39 | + |
40 | +include(common-installs-config.pri) |
41 | |
42 | === added file 'account-polld/account-polld.pro' |
43 | --- account-polld/account-polld.pro 1970-01-01 00:00:00 +0000 |
44 | +++ account-polld/account-polld.pro 2016-09-19 11:35:53 +0000 |
45 | @@ -0,0 +1,40 @@ |
46 | +include(../common-project-config.pri) |
47 | +include($${TOP_SRC_DIR}/common-vars.pri) |
48 | + |
49 | +TEMPLATE = app |
50 | +TARGET = account-polld |
51 | + |
52 | +CONFIG += \ |
53 | + link_pkgconfig \ |
54 | + no_keywords \ |
55 | + qt |
56 | + |
57 | +QT += \ |
58 | + dbus |
59 | + |
60 | +PKGCONFIG += \ |
61 | + accounts-qt5 \ |
62 | + libsignon-qt5 |
63 | + |
64 | +DEFINES += \ |
65 | + DEBUG_ENABLED \ |
66 | + PLUGIN_DATA_FILE=\\\"$${PLUGIN_DATA_FILE}\\\" |
67 | + |
68 | +SOURCES += \ |
69 | + account_manager.cpp \ |
70 | + app_manager.cpp \ |
71 | + debug.cpp \ |
72 | + main.cpp \ |
73 | + plugin.cpp \ |
74 | + poll_service.cpp \ |
75 | + push_client.cpp |
76 | + |
77 | +HEADERS += \ |
78 | + account_manager.h \ |
79 | + app_manager.h \ |
80 | + debug.h \ |
81 | + plugin.h \ |
82 | + poll_service.h \ |
83 | + push_client.h |
84 | + |
85 | +include($${TOP_SRC_DIR}/common-installs-config.pri) |
86 | |
87 | === added file 'account-polld/account_manager.cpp' |
88 | --- account-polld/account_manager.cpp 1970-01-01 00:00:00 +0000 |
89 | +++ account-polld/account_manager.cpp 2016-09-19 11:35:53 +0000 |
90 | @@ -0,0 +1,283 @@ |
91 | +/* |
92 | + * Copyright (C) 2016 Canonical Ltd. |
93 | + * |
94 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
95 | + * |
96 | + * This file is part of account-polld |
97 | + * |
98 | + * This program is free software: you can redistribute it and/or modify it |
99 | + * under the terms of the GNU General Public License version 3, as published |
100 | + * by the Free Software Foundation. |
101 | + * |
102 | + * This program is distributed in the hope that it will be useful, but |
103 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
104 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
105 | + * PURPOSE. See the GNU General Public License for more details. |
106 | + * |
107 | + * You should have received a copy of the GNU General Public License along |
108 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
109 | + */ |
110 | + |
111 | +#include "account_manager.h" |
112 | + |
113 | +#include "app_manager.h" |
114 | +#include "debug.h" |
115 | + |
116 | +#include <Accounts/Account> |
117 | +#include <Accounts/AccountService> |
118 | +#include <Accounts/Application> |
119 | +#include <Accounts/Manager> |
120 | +#include <Accounts/Service> |
121 | +#include <QMetaObject> |
122 | +#include <SignOn/AuthSession> |
123 | +#include <SignOn/Identity> |
124 | +#include <SignOn/SessionData> |
125 | + |
126 | +using namespace AccountPolld; |
127 | + |
128 | +namespace AccountPolld { |
129 | + |
130 | +class AccountManagerPrivate: public QObject |
131 | +{ |
132 | + Q_OBJECT |
133 | + Q_DECLARE_PUBLIC(AccountManager) |
134 | + |
135 | + struct AuthState { |
136 | + QVariantMap lastAuthReply; |
137 | + bool needNewToken; |
138 | + }; |
139 | + |
140 | +public: |
141 | + AccountManagerPrivate(AccountManager *q, AppManager *appManager); |
142 | + ~AccountManagerPrivate() {}; |
143 | + |
144 | + void loadApplications(); |
145 | + void activateAccount(Accounts::AccountService *as, |
146 | + const QString &appKey); |
147 | + void accountReady(Accounts::AccountService *as, const QString &appKey, |
148 | + const QVariantMap &auth = QVariantMap()); |
149 | + static QString accountServiceKey(Accounts::AccountService *as); |
150 | + static QString accountServiceKey(uint accountId, const QString &serviceId); |
151 | + |
152 | + void markAuthFailure(const AccountData &data); |
153 | + QVariantMap formatAuthReply(const Accounts::AuthData &authData, |
154 | + const QVariantMap &reply) const; |
155 | + |
156 | +public Q_SLOTS: |
157 | + void operationFinished(); |
158 | + |
159 | +private: |
160 | + Accounts::Manager m_manager; |
161 | + AppManager *m_appManager; |
162 | + Applications m_apps; |
163 | + QHash<QString,Accounts::Application> m_accountApps; |
164 | + QHash<QString,AuthState> m_authStates; |
165 | + int m_pendingOperations; |
166 | + AccountManager *q_ptr; |
167 | +}; |
168 | + |
169 | +uint qHash(const AccountData &data) { |
170 | + return ::qHash(data.pluginId) + ::qHash(data.accountId) + |
171 | + ::qHash(data.serviceId); |
172 | +} |
173 | + |
174 | +} // namespace |
175 | + |
176 | +AccountManagerPrivate::AccountManagerPrivate(AccountManager *q, |
177 | + AppManager *appManager): |
178 | + QObject(q), |
179 | + m_appManager(appManager), |
180 | + m_pendingOperations(0), |
181 | + q_ptr(q) |
182 | +{ |
183 | + qRegisterMetaType<AccountData>("AccountData"); |
184 | +} |
185 | + |
186 | +QString AccountManagerPrivate::accountServiceKey(Accounts::AccountService *as) |
187 | +{ |
188 | + return accountServiceKey(as->account()->id(), as->service().name()); |
189 | +} |
190 | + |
191 | +QString AccountManagerPrivate::accountServiceKey(uint accountId, const QString &serviceId) |
192 | +{ |
193 | + return QString("%1-%2").arg(accountId).arg(serviceId); |
194 | +} |
195 | + |
196 | +void AccountManagerPrivate::loadApplications() |
197 | +{ |
198 | + m_accountApps.clear(); |
199 | + |
200 | + m_apps = m_appManager->applications(); |
201 | + for (auto i = m_apps.constBegin(); i != m_apps.constEnd(); i++) { |
202 | + Accounts::Application app = m_manager.application(i.value().appId); |
203 | + if (app.isValid()) { |
204 | + m_accountApps.insert(i.key(), app); |
205 | + } else { |
206 | + DEBUG() << "Application not found:" << i.value().appId; |
207 | + } |
208 | + } |
209 | +} |
210 | + |
211 | +QVariantMap AccountManagerPrivate::formatAuthReply(const Accounts::AuthData &authData, |
212 | + const QVariantMap &reply) const |
213 | +{ |
214 | + QVariantMap formattedReply(reply); |
215 | + |
216 | + QString mechanism = authData.mechanism(); |
217 | + const QVariantMap ¶meters = authData.parameters(); |
218 | + if (mechanism == "HMAC-SHA1" || mechanism == "PLAINTEXT") { |
219 | + /* For OAuth 1.0, let's return also the Consumer key and secret along |
220 | + * with the reply. */ |
221 | + formattedReply["ClientId"] = parameters.value("ConsumerKey"); |
222 | + formattedReply["ClientSecret"] = parameters.value("ConsumerSecret"); |
223 | + } else if (mechanism == "web_server" || mechanism == "user_agent") { |
224 | + formattedReply["ClientId"] = parameters.value("ClientId"); |
225 | + formattedReply["ClientSecret"] = parameters.value("ClientId"); |
226 | + } |
227 | + |
228 | + return formattedReply; |
229 | +} |
230 | + |
231 | +void AccountManagerPrivate::accountReady(Accounts::AccountService *as, |
232 | + const QString &appKey, |
233 | + const QVariantMap &auth) |
234 | +{ |
235 | + Q_Q(AccountManager); |
236 | + AccountData accountData; |
237 | + accountData.pluginId = appKey; |
238 | + accountData.accountId = as->account()->id(); |
239 | + accountData.serviceId = as->service().name(); |
240 | + accountData.auth = auth; |
241 | + QMetaObject::invokeMethod(q, "accountReady", Qt::QueuedConnection, |
242 | + Q_ARG(AccountData, accountData)); |
243 | +} |
244 | + |
245 | +void AccountManagerPrivate::activateAccount(Accounts::AccountService *as, |
246 | + const QString &appKey) |
247 | +{ |
248 | + const AppData &data = m_apps[appKey]; |
249 | + if (data.needsAuthData) { |
250 | + Accounts::AuthData authData = as->authData(); |
251 | + QString key = accountServiceKey(as); |
252 | + |
253 | + auto identity = |
254 | + SignOn::Identity::existingIdentity(authData.credentialsId(), as); |
255 | + auto authSession = identity->createSession(authData.method()); |
256 | + QObject::connect(authSession, &SignOn::AuthSession::response, |
257 | + [this,as,appKey](const SignOn::SessionData &reply) { |
258 | + as->deleteLater(); |
259 | + |
260 | + QVariantMap authReply = reply.toMap(); |
261 | + AuthState &authState = m_authStates[accountServiceKey(as)]; |
262 | + if (authState.needNewToken && authReply == authState.lastAuthReply) { |
263 | + /* This account won't work, don't even check it */ |
264 | + operationFinished(); |
265 | + return; |
266 | + } |
267 | + |
268 | + authState.needNewToken = false; |
269 | + authState.lastAuthReply = authReply; |
270 | + accountReady(as, appKey, formatAuthReply(as->authData(), authReply)); |
271 | + operationFinished(); |
272 | + }); |
273 | + QObject::connect(authSession, &SignOn::AuthSession::error, |
274 | + [this,as](const SignOn::Error &error) { |
275 | + as->deleteLater(); |
276 | + operationFinished(); |
277 | + DEBUG() << "authentication error:" << error.message(); |
278 | + }); |
279 | + |
280 | + AuthState &authState = m_authStates[key]; |
281 | + |
282 | + QVariantMap sessionData = authData.parameters(); |
283 | + sessionData["UiPolicy"] = SignOn::NoUserInteractionPolicy; |
284 | + if (authState.needNewToken) { |
285 | + sessionData["ForceTokenRefresh"] = true; |
286 | + } |
287 | + m_pendingOperations++; |
288 | + authSession->process(sessionData, authData.mechanism()); |
289 | + } else { |
290 | + accountReady(as, appKey); |
291 | + } |
292 | +} |
293 | + |
294 | +void AccountManagerPrivate::markAuthFailure(const AccountData &data) |
295 | +{ |
296 | + QString key = accountServiceKey(data.accountId, data.serviceId); |
297 | + AuthState &authState = m_authStates[key]; |
298 | + authState.lastAuthReply = data.auth; |
299 | + authState.needNewToken = true; |
300 | +} |
301 | + |
302 | +void AccountManagerPrivate::operationFinished() |
303 | +{ |
304 | + Q_Q(AccountManager); |
305 | + m_pendingOperations--; |
306 | + if (m_pendingOperations == 0) { |
307 | + /* since the accountReady signal is sent in a queued connection, this |
308 | + * signal must also be sent in that way, in order to be delivered after |
309 | + * all the accountReady signals. */ |
310 | + QMetaObject::invokeMethod(q, "finished", Qt::QueuedConnection); |
311 | + } |
312 | +} |
313 | + |
314 | +AccountManager::AccountManager(AppManager *appManager, QObject *parent): |
315 | + QObject(parent), |
316 | + d_ptr(new AccountManagerPrivate(this, appManager)) |
317 | +{ |
318 | +} |
319 | + |
320 | +AccountManager::~AccountManager() |
321 | +{ |
322 | + delete d_ptr; |
323 | +} |
324 | + |
325 | +void AccountManager::listAccounts() |
326 | +{ |
327 | + Q_D(AccountManager); |
328 | + |
329 | + d->loadApplications(); |
330 | + |
331 | + d->m_pendingOperations++; |
332 | + |
333 | + Accounts::AccountIdList accountIds = d->m_manager.accountListEnabled(); |
334 | + for (Accounts::AccountId accountId: accountIds) { |
335 | + Accounts::Account *account = d->m_manager.account(accountId); |
336 | + if (Q_UNLIKELY(!account)) continue; |
337 | + |
338 | + Accounts::ServiceList services = account->enabledServices(); |
339 | + |
340 | + /* check if we have some plugins registered for this service */ |
341 | + for (auto i = d->m_accountApps.constBegin(); |
342 | + i != d->m_accountApps.constEnd(); i++) { |
343 | + for (Accounts::Service &service: services) { |
344 | + /* Check if the application can use this service */ |
345 | + if (i.value().serviceUsage(service).isEmpty()) { |
346 | + continue; |
347 | + } |
348 | + |
349 | + /* Check if the plugin manifest allows using this service */ |
350 | + const AppData &appData = d->m_apps[i.key()]; |
351 | + if (!appData.services.isEmpty() && |
352 | + !appData.services.contains(service.name())) { |
353 | + DEBUG() << "Skipping service" << service.name() << |
354 | + "for plugin" << i.key(); |
355 | + continue; |
356 | + } |
357 | + |
358 | + auto *as = new Accounts::AccountService(account, service); |
359 | + d->activateAccount(as, i.key()); |
360 | + } |
361 | + } |
362 | + } |
363 | + |
364 | + d->operationFinished(); |
365 | +} |
366 | + |
367 | +void AccountManager::markAuthFailure(const AccountData &data) |
368 | +{ |
369 | + Q_D(AccountManager); |
370 | + d->markAuthFailure(data); |
371 | +} |
372 | + |
373 | +#include "account_manager.moc" |
374 | |
375 | === added file 'account-polld/account_manager.h' |
376 | --- account-polld/account_manager.h 1970-01-01 00:00:00 +0000 |
377 | +++ account-polld/account_manager.h 2016-09-19 11:35:53 +0000 |
378 | @@ -0,0 +1,78 @@ |
379 | +/* |
380 | + * Copyright (C) 2016 Canonical Ltd. |
381 | + * |
382 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
383 | + * |
384 | + * This file is part of account-polld |
385 | + * |
386 | + * This program is free software: you can redistribute it and/or modify it |
387 | + * under the terms of the GNU General Public License version 3, as published |
388 | + * by the Free Software Foundation. |
389 | + * |
390 | + * This program is distributed in the hope that it will be useful, but |
391 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
392 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
393 | + * PURPOSE. See the GNU General Public License for more details. |
394 | + * |
395 | + * You should have received a copy of the GNU General Public License along |
396 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
397 | + */ |
398 | + |
399 | +#ifndef AP_ACCOUNT_MANAGER_H |
400 | +#define AP_ACCOUNT_MANAGER_H |
401 | + |
402 | +#include <QObject> |
403 | +#include <QVariantMap> |
404 | + |
405 | +namespace AccountPolld { |
406 | + |
407 | +struct AccountData { |
408 | + QString pluginId; |
409 | + uint accountId; |
410 | + QString serviceId; |
411 | + QVariantMap auth; |
412 | + |
413 | + /* This is needed for using the struct as a QHash key; the "auth" map is |
414 | + * intentionally omitted from the comparison as we don't want to use that |
415 | + * as a key, too. */ |
416 | + bool operator==(const AccountData &other) const { |
417 | + return pluginId == other.pluginId && accountId == other.accountId && |
418 | + serviceId == other.serviceId; |
419 | + } |
420 | +}; |
421 | + |
422 | +uint qHash(const AccountData &data); |
423 | + |
424 | +class AppManager; |
425 | + |
426 | +class AccountManagerPrivate; |
427 | +class AccountManager: public QObject |
428 | +{ |
429 | + Q_OBJECT |
430 | + |
431 | +public: |
432 | + explicit AccountManager(AppManager *appManager, QObject *parent = 0); |
433 | + ~AccountManager(); |
434 | + |
435 | + /* Scan for accounts; for each valid account, the accountReady() signal |
436 | + * will be emitted. A finished() signal will be emitted last. */ |
437 | + void listAccounts(); |
438 | + |
439 | + /* Call when the authentication data for an account is refused by the |
440 | + * server because of token expiration */ |
441 | + void markAuthFailure(const AccountData &data); |
442 | + |
443 | +Q_SIGNALS: |
444 | + void accountReady(const AccountData &data); |
445 | + void finished(); |
446 | + |
447 | +private: |
448 | + AccountManagerPrivate *d_ptr; |
449 | + Q_DECLARE_PRIVATE(AccountManager) |
450 | +}; |
451 | + |
452 | +} // namespace |
453 | + |
454 | +Q_DECLARE_METATYPE(AccountPolld::AccountData) |
455 | + |
456 | +#endif // AP_ACCOUNT_MANAGER_H |
457 | |
458 | === added file 'account-polld/app_manager.cpp' |
459 | --- account-polld/app_manager.cpp 1970-01-01 00:00:00 +0000 |
460 | +++ account-polld/app_manager.cpp 2016-09-19 11:35:53 +0000 |
461 | @@ -0,0 +1,118 @@ |
462 | +/* |
463 | + * Copyright (C) 2016 Canonical Ltd. |
464 | + * |
465 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
466 | + * |
467 | + * This file is part of account-polld |
468 | + * |
469 | + * This program is free software: you can redistribute it and/or modify it |
470 | + * under the terms of the GNU General Public License version 3, as published |
471 | + * by the Free Software Foundation. |
472 | + * |
473 | + * This program is distributed in the hope that it will be useful, but |
474 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
475 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
476 | + * PURPOSE. See the GNU General Public License for more details. |
477 | + * |
478 | + * You should have received a copy of the GNU General Public License along |
479 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
480 | + */ |
481 | + |
482 | +#include "debug.h" |
483 | +#include "app_manager.h" |
484 | + |
485 | +#include <QFile> |
486 | +#include <QJsonArray> |
487 | +#include <QJsonDocument> |
488 | +#include <QJsonObject> |
489 | +#include <QStandardPaths> |
490 | + |
491 | +using namespace AccountPolld; |
492 | + |
493 | +namespace AccountPolld { |
494 | + |
495 | +class AppManagerPrivate: public QObject |
496 | +{ |
497 | + Q_OBJECT |
498 | + Q_DECLARE_PUBLIC(AppManager) |
499 | + |
500 | +public: |
501 | + AppManagerPrivate(AppManager *q); |
502 | + ~AppManagerPrivate() {}; |
503 | + |
504 | + Applications readPluginData() const; |
505 | + |
506 | +private: |
507 | + QString m_dataFilePath; |
508 | + AppManager *q_ptr; |
509 | +}; |
510 | + |
511 | +} // namespace |
512 | + |
513 | +AppManagerPrivate::AppManagerPrivate(AppManager *q): |
514 | + QObject(q), |
515 | + q_ptr(q) |
516 | +{ |
517 | + const QString localShare = |
518 | + QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); |
519 | + m_dataFilePath = localShare + "/" PLUGIN_DATA_FILE; |
520 | +} |
521 | + |
522 | +Applications AppManagerPrivate::readPluginData() const |
523 | +{ |
524 | + Applications apps; |
525 | + |
526 | + QFile file(m_dataFilePath); |
527 | + if (!file.open(QIODevice::ReadOnly)) return apps; |
528 | + |
529 | + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); |
530 | + file.close(); |
531 | + |
532 | + QJsonObject mainObject = doc.object(); |
533 | + for (auto i = mainObject.begin(); i != mainObject.end(); i++) { |
534 | + QJsonObject appObject = i.value().toObject(); |
535 | + |
536 | + AppData data; |
537 | + data.profile = appObject.value("profile").toString(); |
538 | + data.execLine = appObject.value("exec").toString(); |
539 | + data.appId = appObject.value("appId").toString(); |
540 | + QJsonArray services = appObject.value("services").toArray(); |
541 | + for (const QJsonValue &v: services) { |
542 | + data.services.append(v.toString()); |
543 | + } |
544 | + data.interval = appObject.value("interval").toInt(); |
545 | + data.needsAuthData = appObject.value("needsAuthData").toBool(); |
546 | + |
547 | + if (data.profile.isEmpty() || |
548 | + data.execLine.isEmpty() || |
549 | + data.appId.isEmpty()) { |
550 | + qWarning() << "Incomplete plugin data:" << |
551 | + QJsonDocument(appObject).toJson(QJsonDocument::Compact); |
552 | + continue; |
553 | + } |
554 | + |
555 | + apps.insert(i.key(), data); |
556 | + } |
557 | + |
558 | + return apps; |
559 | +} |
560 | + |
561 | +AppManager::AppManager(QObject *parent): |
562 | + QObject(parent), |
563 | + d_ptr(new AppManagerPrivate(this)) |
564 | +{ |
565 | +} |
566 | + |
567 | +AppManager::~AppManager() |
568 | +{ |
569 | + delete d_ptr; |
570 | +} |
571 | + |
572 | +Applications AppManager::applications() const |
573 | +{ |
574 | + Q_D(const AppManager); |
575 | + |
576 | + return d->readPluginData(); |
577 | +} |
578 | + |
579 | +#include "app_manager.moc" |
580 | |
581 | === added file 'account-polld/app_manager.h' |
582 | --- account-polld/app_manager.h 1970-01-01 00:00:00 +0000 |
583 | +++ account-polld/app_manager.h 2016-09-19 11:35:53 +0000 |
584 | @@ -0,0 +1,60 @@ |
585 | +/* |
586 | + * Copyright (C) 2016 Canonical Ltd. |
587 | + * |
588 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
589 | + * |
590 | + * This file is part of account-polld |
591 | + * |
592 | + * This program is free software: you can redistribute it and/or modify it |
593 | + * under the terms of the GNU General Public License version 3, as published |
594 | + * by the Free Software Foundation. |
595 | + * |
596 | + * This program is distributed in the hope that it will be useful, but |
597 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
598 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
599 | + * PURPOSE. See the GNU General Public License for more details. |
600 | + * |
601 | + * You should have received a copy of the GNU General Public License along |
602 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
603 | + */ |
604 | + |
605 | +#ifndef AP_APP_MANAGER_H |
606 | +#define AP_APP_MANAGER_H |
607 | + |
608 | +#include <QObject> |
609 | +#include <QStringList> |
610 | +#include <QHash> |
611 | + |
612 | +namespace AccountPolld { |
613 | + |
614 | +struct AppData { |
615 | + QString profile; // apparmor label for the plugin process |
616 | + QString execLine; |
617 | + QString appId; // appId, for matching with OA |
618 | + QStringList services; |
619 | + int interval; |
620 | + bool needsAuthData; |
621 | +}; |
622 | + |
623 | +typedef QHash<QString,AppData> Applications; |
624 | + |
625 | +class AppManagerPrivate; |
626 | + |
627 | +class AppManager: public QObject |
628 | +{ |
629 | + Q_OBJECT |
630 | + |
631 | +public: |
632 | + explicit AppManager(QObject *parent = 0); |
633 | + ~AppManager(); |
634 | + |
635 | + Applications applications() const; |
636 | + |
637 | +private: |
638 | + AppManagerPrivate *d_ptr; |
639 | + Q_DECLARE_PRIVATE(AppManager) |
640 | +}; |
641 | + |
642 | +} // namespace |
643 | + |
644 | +#endif // AP_APP_MANAGER_H |
645 | |
646 | === added file 'account-polld/debug.cpp' |
647 | --- account-polld/debug.cpp 1970-01-01 00:00:00 +0000 |
648 | +++ account-polld/debug.cpp 2016-09-19 11:35:53 +0000 |
649 | @@ -0,0 +1,29 @@ |
650 | +/* |
651 | + * Copyright (C) 2016 Canonical Ltd. |
652 | + * |
653 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
654 | + * |
655 | + * This file is part of account-polld |
656 | + * |
657 | + * This program is free software: you can redistribute it and/or modify it |
658 | + * under the terms of the GNU General Public License version 3, as published |
659 | + * by the Free Software Foundation. |
660 | + * |
661 | + * This program is distributed in the hope that it will be useful, but |
662 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
663 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
664 | + * PURPOSE. See the GNU General Public License for more details. |
665 | + * |
666 | + * You should have received a copy of the GNU General Public License along |
667 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
668 | + */ |
669 | + |
670 | +#include "debug.h" |
671 | + |
672 | +int appLoggingLevel = 1; // criticals |
673 | + |
674 | +void setLoggingLevel(int level) |
675 | +{ |
676 | + appLoggingLevel = level; |
677 | +} |
678 | + |
679 | |
680 | === added file 'account-polld/debug.h' |
681 | --- account-polld/debug.h 1970-01-01 00:00:00 +0000 |
682 | +++ account-polld/debug.h 2016-09-19 11:35:53 +0000 |
683 | @@ -0,0 +1,47 @@ |
684 | +/* |
685 | + * Copyright (C) 2016 Canonical Ltd. |
686 | + * |
687 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
688 | + * |
689 | + * This file is part of account-polld |
690 | + * |
691 | + * This program is free software: you can redistribute it and/or modify it |
692 | + * under the terms of the GNU General Public License version 3, as published |
693 | + * by the Free Software Foundation. |
694 | + * |
695 | + * This program is distributed in the hope that it will be useful, but |
696 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
697 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
698 | + * PURPOSE. See the GNU General Public License for more details. |
699 | + * |
700 | + * You should have received a copy of the GNU General Public License along |
701 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
702 | + */ |
703 | +#ifndef AP_DEBUG_H |
704 | +#define AP_DEBUG_H |
705 | + |
706 | +#include <QDebug> |
707 | + |
708 | +/* 0 - fatal, 1 - critical(default), 2 - info/debug */ |
709 | +extern int appLoggingLevel; |
710 | + |
711 | +static inline bool debugEnabled() |
712 | +{ |
713 | + return appLoggingLevel >= 2; |
714 | +} |
715 | + |
716 | +static inline int loggingLevel() |
717 | +{ |
718 | + return appLoggingLevel; |
719 | +} |
720 | + |
721 | +void setLoggingLevel(int level); |
722 | + |
723 | +#ifdef DEBUG_ENABLED |
724 | + #define DEBUG() \ |
725 | + if (debugEnabled()) qDebug() << __FILE__ << __LINE__ << __func__ |
726 | +#else |
727 | + #define DEBUG() while (0) qDebug() |
728 | +#endif |
729 | + |
730 | +#endif // AP_DEBUG_H |
731 | |
732 | === added file 'account-polld/main.cpp' |
733 | --- account-polld/main.cpp 1970-01-01 00:00:00 +0000 |
734 | +++ account-polld/main.cpp 2016-09-19 11:35:53 +0000 |
735 | @@ -0,0 +1,63 @@ |
736 | +/* |
737 | + * Copyright (C) 2016 Canonical Ltd. |
738 | + * |
739 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
740 | + * |
741 | + * This file is part of account-polld |
742 | + * |
743 | + * This program is free software: you can redistribute it and/or modify it |
744 | + * under the terms of the GNU General Public License version 3, as published |
745 | + * by the Free Software Foundation. |
746 | + * |
747 | + * This program is distributed in the hope that it will be useful, but |
748 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
749 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
750 | + * PURPOSE. See the GNU General Public License for more details. |
751 | + * |
752 | + * You should have received a copy of the GNU General Public License along |
753 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
754 | + */ |
755 | + |
756 | +#include <QCoreApplication> |
757 | +#include <QDBusConnection> |
758 | +#include <QProcessEnvironment> |
759 | +#include <QSettings> |
760 | + |
761 | +#include "debug.h" |
762 | +#include "poll_service.h" |
763 | + |
764 | + |
765 | +int main(int argc, char **argv) |
766 | +{ |
767 | + QCoreApplication app(argc, argv); |
768 | + |
769 | + QSettings settings("account-polld"); |
770 | + |
771 | + /* read environment variables */ |
772 | + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); |
773 | + if (environment.contains(QLatin1String("AP_LOGGING_LEVEL"))) { |
774 | + bool isOk; |
775 | + int value = environment.value( |
776 | + QLatin1String("AP_LOGGING_LEVEL")).toInt(&isOk); |
777 | + if (isOk) |
778 | + setLoggingLevel(value); |
779 | + } else { |
780 | + setLoggingLevel(settings.value("LoggingLevel", 1).toInt()); |
781 | + } |
782 | + |
783 | + QDBusConnection connection = QDBusConnection::sessionBus(); |
784 | + |
785 | + auto service = new AccountPolld::PollService(); |
786 | + connection.registerObject(ACCOUNT_POLLD_OBJECT_PATH, service, |
787 | + QDBusConnection::ExportAllContents); |
788 | + connection.registerService(ACCOUNT_POLLD_SERVICE_NAME); |
789 | + |
790 | + int ret = app.exec(); |
791 | + |
792 | + connection.unregisterService(ACCOUNT_POLLD_SERVICE_NAME); |
793 | + connection.unregisterObject(ACCOUNT_POLLD_OBJECT_PATH); |
794 | + delete service; |
795 | + |
796 | + return ret; |
797 | +} |
798 | + |
799 | |
800 | === added file 'account-polld/plugin.cpp' |
801 | --- account-polld/plugin.cpp 1970-01-01 00:00:00 +0000 |
802 | +++ account-polld/plugin.cpp 2016-09-19 11:35:53 +0000 |
803 | @@ -0,0 +1,155 @@ |
804 | +/* |
805 | + * Copyright (C) 2016 Canonical Ltd. |
806 | + * |
807 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
808 | + * |
809 | + * This file is part of account-polld |
810 | + * |
811 | + * This program is free software: you can redistribute it and/or modify it |
812 | + * under the terms of the GNU General Public License version 3, as published |
813 | + * by the Free Software Foundation. |
814 | + * |
815 | + * This program is distributed in the hope that it will be useful, but |
816 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
817 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
818 | + * PURPOSE. See the GNU General Public License for more details. |
819 | + * |
820 | + * You should have received a copy of the GNU General Public License along |
821 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
822 | + */ |
823 | + |
824 | +#include "plugin.h" |
825 | + |
826 | +#include "debug.h" |
827 | + |
828 | +#include <QByteArray> |
829 | +#include <QJsonDocument> |
830 | +#include <QJsonObject> |
831 | +#include <QJsonParseError> |
832 | +#include <QProcess> |
833 | +#include <QTimer> |
834 | +#include <signal.h> |
835 | +#include <sys/types.h> |
836 | + |
837 | +using namespace AccountPolld; |
838 | + |
839 | +namespace AccountPolld { |
840 | + |
841 | +class PluginPrivate: public QProcess |
842 | +{ |
843 | + Q_OBJECT |
844 | + Q_DECLARE_PUBLIC(Plugin) |
845 | + |
846 | +public: |
847 | + PluginPrivate(Plugin *q, const QString &execLine, const QString &profile); |
848 | + ~PluginPrivate() {}; |
849 | + |
850 | +public Q_SLOTS: |
851 | + void onReadyRead(); |
852 | + void killPlugin(); |
853 | + |
854 | +private: |
855 | + QString m_execLine; |
856 | + QString m_profile; |
857 | + QTimer m_timer; |
858 | + QByteArray m_inputBuffer; |
859 | + bool m_sigtermSent; |
860 | + Plugin *q_ptr; |
861 | +}; |
862 | + |
863 | +} // namespace |
864 | + |
865 | +PluginPrivate::PluginPrivate(Plugin *q, |
866 | + const QString &execLine, |
867 | + const QString &profile): |
868 | + QProcess(q), |
869 | + m_execLine(execLine), |
870 | + m_profile(profile), |
871 | + m_sigtermSent(false), |
872 | + q_ptr(q) |
873 | +{ |
874 | + int killTime = 10; |
875 | + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); |
876 | + if (environment.contains("AP_PLUGIN_TIMEOUT")) { |
877 | + killTime = environment.value("AP_PLUGIN_TIMEOUT").toInt(); |
878 | + } |
879 | + m_timer.setInterval(killTime * 1000); |
880 | + m_timer.setSingleShot(true); |
881 | + |
882 | + setProcessChannelMode(QProcess::ForwardedErrorChannel); |
883 | + QObject::connect(this, SIGNAL(started()), &m_timer, SLOT(start())); |
884 | + QObject::connect(this, SIGNAL(started()), q, SIGNAL(ready())); |
885 | + QObject::connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), |
886 | + q, SIGNAL(finished())); |
887 | + QObject::connect(this, SIGNAL(readyReadStandardOutput()), |
888 | + this, SLOT(onReadyRead())); |
889 | + QObject::connect(&m_timer, SIGNAL(timeout()), this, SLOT(killPlugin())); |
890 | +} |
891 | + |
892 | +void PluginPrivate::onReadyRead() |
893 | +{ |
894 | + Q_Q(Plugin); |
895 | + |
896 | + m_inputBuffer.append(readAllStandardOutput()); |
897 | + QJsonParseError error; |
898 | + auto doc = QJsonDocument::fromJson(m_inputBuffer, &error); |
899 | + if (error.error == QJsonParseError::NoError) { |
900 | + m_inputBuffer.clear(); |
901 | + Q_EMIT q->response(doc.object()); |
902 | + } |
903 | + |
904 | + /* otherwise continue reasing, the object is probably uncomplete */ |
905 | +} |
906 | + |
907 | +void PluginPrivate::killPlugin() |
908 | +{ |
909 | + pid_t pid = processId(); |
910 | + DEBUG() << "killing plugin" << pid; |
911 | + if (!m_sigtermSent) { |
912 | + ::kill(pid, SIGTERM); |
913 | + m_sigtermSent = true; |
914 | + m_timer.setInterval(1 * 1000); |
915 | + m_timer.start(); |
916 | + } else { |
917 | + ::kill(pid, SIGKILL); |
918 | + } |
919 | +} |
920 | + |
921 | +Plugin::Plugin(const QString &execLine, const QString &profile, |
922 | + QObject *parent): |
923 | + QObject(parent), |
924 | + d_ptr(new PluginPrivate(this, execLine, profile)) |
925 | +{ |
926 | +} |
927 | + |
928 | +Plugin::~Plugin() |
929 | +{ |
930 | + delete d_ptr; |
931 | +} |
932 | + |
933 | +void Plugin::run() |
934 | +{ |
935 | + Q_D(Plugin); |
936 | + |
937 | + QString command; |
938 | + |
939 | + if (d->m_profile != "unconfined") { |
940 | + command = QString("aa-exec-click -p %1 -- ").arg(d->m_profile); |
941 | + } |
942 | + |
943 | + command.append(d->m_execLine); |
944 | + |
945 | + DEBUG() << "Starting" << command; |
946 | + d->start(command); |
947 | +} |
948 | + |
949 | +void Plugin::poll(const QJsonObject &pollData) |
950 | +{ |
951 | + Q_D(Plugin); |
952 | + |
953 | + DEBUG() << "Plugin input:" << pollData; |
954 | + d->write(QJsonDocument(pollData).toJson(QJsonDocument::Compact)); |
955 | + d->write("\n"); |
956 | +} |
957 | + |
958 | +#include "plugin.moc" |
959 | |
960 | === added file 'account-polld/plugin.h' |
961 | --- account-polld/plugin.h 1970-01-01 00:00:00 +0000 |
962 | +++ account-polld/plugin.h 2016-09-19 11:35:53 +0000 |
963 | @@ -0,0 +1,55 @@ |
964 | +/* |
965 | + * Copyright (C) 2016 Canonical Ltd. |
966 | + * |
967 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
968 | + * |
969 | + * This file is part of account-polld |
970 | + * |
971 | + * This program is free software: you can redistribute it and/or modify it |
972 | + * under the terms of the GNU General Public License version 3, as published |
973 | + * by the Free Software Foundation. |
974 | + * |
975 | + * This program is distributed in the hope that it will be useful, but |
976 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
977 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
978 | + * PURPOSE. See the GNU General Public License for more details. |
979 | + * |
980 | + * You should have received a copy of the GNU General Public License along |
981 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
982 | + */ |
983 | + |
984 | +#ifndef AP_PLUGIN_H |
985 | +#define AP_PLUGIN_H |
986 | + |
987 | +#include <QObject> |
988 | + |
989 | +class QJsonObject; |
990 | + |
991 | +namespace AccountPolld { |
992 | + |
993 | +class PluginPrivate; |
994 | +class Plugin: public QObject |
995 | +{ |
996 | + Q_OBJECT |
997 | + |
998 | +public: |
999 | + explicit Plugin(const QString &execLine, const QString &profile, |
1000 | + QObject *parent = 0); |
1001 | + ~Plugin(); |
1002 | + |
1003 | + void run(); |
1004 | + void poll(const QJsonObject &pollData); |
1005 | + |
1006 | +Q_SIGNALS: |
1007 | + void ready(); |
1008 | + void response(const QJsonObject &resp); |
1009 | + void finished(); |
1010 | + |
1011 | +private: |
1012 | + PluginPrivate *d_ptr; |
1013 | + Q_DECLARE_PRIVATE(Plugin) |
1014 | +}; |
1015 | + |
1016 | +} // namespace |
1017 | + |
1018 | +#endif // AP_PLUGIN_H |
1019 | |
1020 | === added file 'account-polld/poll_service.cpp' |
1021 | --- account-polld/poll_service.cpp 1970-01-01 00:00:00 +0000 |
1022 | +++ account-polld/poll_service.cpp 2016-09-19 11:35:53 +0000 |
1023 | @@ -0,0 +1,190 @@ |
1024 | +/* |
1025 | + * Copyright (C) 2016 Canonical Ltd. |
1026 | + * |
1027 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
1028 | + * |
1029 | + * This file is part of account-polld |
1030 | + * |
1031 | + * This program is free software: you can redistribute it and/or modify it |
1032 | + * under the terms of the GNU General Public License version 3, as published |
1033 | + * by the Free Software Foundation. |
1034 | + * |
1035 | + * This program is distributed in the hope that it will be useful, but |
1036 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1037 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1038 | + * PURPOSE. See the GNU General Public License for more details. |
1039 | + * |
1040 | + * You should have received a copy of the GNU General Public License along |
1041 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
1042 | + */ |
1043 | + |
1044 | +#include "debug.h" |
1045 | +#include "account_manager.h" |
1046 | +#include "app_manager.h" |
1047 | +#include "poll_service.h" |
1048 | +#include "plugin.h" |
1049 | +#include "push_client.h" |
1050 | + |
1051 | +#include <QDateTime> |
1052 | +#include <QDBusArgument> |
1053 | +#include <QDBusConnection> |
1054 | +#include <QJsonArray> |
1055 | +#include <QJsonObject> |
1056 | +#include <QVariantMap> |
1057 | + |
1058 | +using namespace AccountPolld; |
1059 | + |
1060 | +namespace AccountPolld { |
1061 | + |
1062 | +class PollServicePrivate: public QObject |
1063 | +{ |
1064 | + Q_OBJECT |
1065 | + Q_DECLARE_PUBLIC(PollService) |
1066 | + |
1067 | + struct PollData { |
1068 | + QDateTime lastPolled; |
1069 | + }; |
1070 | + |
1071 | +public: |
1072 | + PollServicePrivate(PollService *q); |
1073 | + ~PollServicePrivate() {}; |
1074 | + |
1075 | + QJsonObject preparePluginInput(const AccountData &accountData, |
1076 | + const AppData &appData); |
1077 | + void handleResponse(const QJsonObject &response, const QString &appId, |
1078 | + const AccountData &accountData); |
1079 | + |
1080 | +private Q_SLOTS: |
1081 | + void poll(); |
1082 | + void onAccountReady(const AccountData &data); |
1083 | + void operationFinished(); |
1084 | + |
1085 | +private: |
1086 | + AppManager m_appManager; |
1087 | + AccountManager m_accountManager; |
1088 | + PushClient m_pushClient; |
1089 | + QHash<AccountData,PollData> m_polls; |
1090 | + int m_pendingOperations; |
1091 | + PollService *q_ptr; |
1092 | +}; |
1093 | + |
1094 | +} // namespace |
1095 | + |
1096 | +PollServicePrivate::PollServicePrivate(PollService *q): |
1097 | + QObject(q), |
1098 | + m_accountManager(&m_appManager), |
1099 | + m_pendingOperations(0), |
1100 | + q_ptr(q) |
1101 | +{ |
1102 | + QObject::connect(&m_accountManager, |
1103 | + SIGNAL(accountReady(const AccountData&)), |
1104 | + this, |
1105 | + SLOT(onAccountReady(const AccountData&))); |
1106 | + QObject::connect(&m_accountManager, SIGNAL(finished()), |
1107 | + this, SLOT(operationFinished())); |
1108 | +} |
1109 | + |
1110 | +void PollServicePrivate::operationFinished() |
1111 | +{ |
1112 | + Q_Q(PollService); |
1113 | + m_pendingOperations--; |
1114 | + if (m_pendingOperations == 0) { |
1115 | + Q_EMIT q->Done(); |
1116 | + } |
1117 | +} |
1118 | + |
1119 | +QJsonObject |
1120 | +PollServicePrivate::preparePluginInput(const AccountData &accountData, |
1121 | + const AppData &appData) |
1122 | +{ |
1123 | + QJsonObject object; |
1124 | + object["helperId"] = accountData.pluginId; |
1125 | + object["appId"] = appData.appId; |
1126 | + object["accountId"] = int(accountData.accountId); |
1127 | + if (appData.needsAuthData) { |
1128 | + object["auth"] = QJsonObject::fromVariantMap(accountData.auth); |
1129 | + } |
1130 | + return object; |
1131 | +} |
1132 | + |
1133 | +void PollServicePrivate::handleResponse(const QJsonObject &response, |
1134 | + const QString &appId, |
1135 | + const AccountData &accountData) |
1136 | +{ |
1137 | + DEBUG() << "Plugin response:" << response; |
1138 | + QJsonObject error = response["error"].toObject(); |
1139 | + if (error["code"].toString() == "ERR_INVALID_AUTH") { |
1140 | + m_accountManager.markAuthFailure(accountData); |
1141 | + return; |
1142 | + } |
1143 | + |
1144 | + QJsonArray notifications = response["notifications"].toArray(); |
1145 | + for (const QJsonValue &v: notifications) { |
1146 | + m_pushClient.post(appId, v.toObject()); |
1147 | + } |
1148 | +} |
1149 | + |
1150 | +void PollServicePrivate::poll() |
1151 | +{ |
1152 | + m_pendingOperations++; |
1153 | + m_accountManager.listAccounts(); |
1154 | +} |
1155 | + |
1156 | +void PollServicePrivate::onAccountReady(const AccountData &accountData) |
1157 | +{ |
1158 | + Applications apps = m_appManager.applications(); |
1159 | + const auto i = apps.find(accountData.pluginId); |
1160 | + if (i == apps.end()) { |
1161 | + qWarning() << "Got account for plugin, but no app linked:" << accountData.pluginId; |
1162 | + return; |
1163 | + } |
1164 | + |
1165 | + const AppData &appData = i.value(); |
1166 | + |
1167 | + /* Check that we are not polling more often than what the application |
1168 | + * wishes to */ |
1169 | + PollData &pollData = m_polls[accountData]; |
1170 | + QDateTime now = QDateTime::currentDateTime(); |
1171 | + if (pollData.lastPolled.isValid() && |
1172 | + pollData.lastPolled.secsTo(now) < appData.interval) { |
1173 | + DEBUG() << "Skipping poll, interval not yet expired:" << accountData.pluginId; |
1174 | + return; |
1175 | + } |
1176 | + pollData.lastPolled = now; |
1177 | + |
1178 | + QJsonObject pluginInput = preparePluginInput(accountData, appData); |
1179 | + |
1180 | + Plugin *plugin = new Plugin(appData.execLine, appData.profile, this); |
1181 | + QObject::connect(plugin, SIGNAL(finished()), plugin, SLOT(deleteLater())); |
1182 | + QObject::connect(plugin, SIGNAL(finished()), this, SLOT(operationFinished())); |
1183 | + QObject::connect(plugin, &Plugin::ready, |
1184 | + [plugin, pluginInput]() { plugin->poll(pluginInput); }); |
1185 | + QObject::connect(plugin, &Plugin::response, |
1186 | + [this, accountData, appData](const QJsonObject &resp) { |
1187 | + handleResponse(resp, appData.appId, accountData); |
1188 | + }); |
1189 | + |
1190 | + m_pendingOperations++; |
1191 | + plugin->run(); |
1192 | +} |
1193 | + |
1194 | +PollService::PollService(QObject *parent): |
1195 | + QObject(parent), |
1196 | + d_ptr(new PollServicePrivate(this)) |
1197 | +{ |
1198 | +} |
1199 | + |
1200 | +PollService::~PollService() |
1201 | +{ |
1202 | + delete d_ptr; |
1203 | +} |
1204 | + |
1205 | +void PollService::Poll() |
1206 | +{ |
1207 | + Q_D(PollService); |
1208 | + |
1209 | + DEBUG() << "Got Poll"; |
1210 | + QMetaObject::invokeMethod(d, "poll", Qt::QueuedConnection); |
1211 | +} |
1212 | + |
1213 | +#include "poll_service.moc" |
1214 | |
1215 | === added file 'account-polld/poll_service.h' |
1216 | --- account-polld/poll_service.h 1970-01-01 00:00:00 +0000 |
1217 | +++ account-polld/poll_service.h 2016-09-19 11:35:53 +0000 |
1218 | @@ -0,0 +1,65 @@ |
1219 | +/* |
1220 | + * Copyright (C) 2016 Canonical Ltd. |
1221 | + * |
1222 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
1223 | + * |
1224 | + * This file is part of account-polld |
1225 | + * |
1226 | + * This program is free software: you can redistribute it and/or modify it |
1227 | + * under the terms of the GNU General Public License version 3, as published |
1228 | + * by the Free Software Foundation. |
1229 | + * |
1230 | + * This program is distributed in the hope that it will be useful, but |
1231 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1232 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1233 | + * PURPOSE. See the GNU General Public License for more details. |
1234 | + * |
1235 | + * You should have received a copy of the GNU General Public License along |
1236 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
1237 | + */ |
1238 | + |
1239 | +#ifndef AP_POLL_SERVICE_H |
1240 | +#define AP_POLL_SERVICE_H |
1241 | + |
1242 | +#include <QDBusContext> |
1243 | +#include <QDBusMessage> |
1244 | +#include <QObject> |
1245 | + |
1246 | +namespace AccountPolld { |
1247 | + |
1248 | +#define ACCOUNT_POLLD_OBJECT_PATH \ |
1249 | + QStringLiteral("/com/ubuntu/AccountPolld") |
1250 | +#define ACCOUNT_POLLD_SERVICE_NAME \ |
1251 | + QStringLiteral("com.ubuntu.AccountPolld") |
1252 | + |
1253 | +class PollServicePrivate; |
1254 | + |
1255 | +class PollService: public QObject, protected QDBusContext |
1256 | +{ |
1257 | + Q_OBJECT |
1258 | + Q_CLASSINFO("D-Bus Interface", "com.ubuntu.AccountPolld") |
1259 | + Q_CLASSINFO("D-Bus Introspection", "" |
1260 | +" <interface name=\"com.ubuntu.AccountPolld\">\n" |
1261 | +" <method name=\"Poll\" />\n" |
1262 | +" <signal name=\"Done\" />\n" |
1263 | +" </interface>\n" |
1264 | + "") |
1265 | + |
1266 | +public: |
1267 | + explicit PollService(QObject *parent = 0); |
1268 | + ~PollService(); |
1269 | + |
1270 | +public Q_SLOTS: |
1271 | + void Poll(); |
1272 | + |
1273 | +Q_SIGNALS: |
1274 | + void Done(); |
1275 | + |
1276 | +private: |
1277 | + PollServicePrivate *d_ptr; |
1278 | + Q_DECLARE_PRIVATE(PollService) |
1279 | +}; |
1280 | + |
1281 | +} // namespace |
1282 | + |
1283 | +#endif // AP_POLL_SERVICE_H |
1284 | |
1285 | === added file 'account-polld/push_client.cpp' |
1286 | --- account-polld/push_client.cpp 1970-01-01 00:00:00 +0000 |
1287 | +++ account-polld/push_client.cpp 2016-09-19 11:35:53 +0000 |
1288 | @@ -0,0 +1,112 @@ |
1289 | +/* |
1290 | + * Copyright (C) 2016 Canonical Ltd. |
1291 | + * |
1292 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
1293 | + * |
1294 | + * This file is part of account-polld |
1295 | + * |
1296 | + * This program is free software: you can redistribute it and/or modify it |
1297 | + * under the terms of the GNU General Public License version 3, as published |
1298 | + * by the Free Software Foundation. |
1299 | + * |
1300 | + * This program is distributed in the hope that it will be useful, but |
1301 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1302 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1303 | + * PURPOSE. See the GNU General Public License for more details. |
1304 | + * |
1305 | + * You should have received a copy of the GNU General Public License along |
1306 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
1307 | + */ |
1308 | + |
1309 | +#include "push_client.h" |
1310 | + |
1311 | +#include "debug.h" |
1312 | + |
1313 | +#include <QByteArray> |
1314 | +#include <QDBusConnection> |
1315 | +#include <QDBusMessage> |
1316 | +#include <QJsonDocument> |
1317 | +#include <QJsonObject> |
1318 | + |
1319 | +using namespace AccountPolld; |
1320 | + |
1321 | +namespace AccountPolld { |
1322 | + |
1323 | +class PushClientPrivate: public QObject |
1324 | +{ |
1325 | + Q_OBJECT |
1326 | + Q_DECLARE_PUBLIC(PushClient) |
1327 | + |
1328 | +public: |
1329 | + PushClientPrivate(PushClient *q); |
1330 | + ~PushClientPrivate() {}; |
1331 | + |
1332 | + static QByteArray makeObjectPath(const QString &appId); |
1333 | + |
1334 | +private: |
1335 | + QDBusConnection m_conn; |
1336 | + PushClient *q_ptr; |
1337 | +}; |
1338 | + |
1339 | +} // namespace |
1340 | + |
1341 | +PushClientPrivate::PushClientPrivate(PushClient *q): |
1342 | + QObject(q), |
1343 | + m_conn(QDBusConnection::sessionBus()), |
1344 | + q_ptr(q) |
1345 | +{ |
1346 | +} |
1347 | + |
1348 | +QByteArray PushClientPrivate::makeObjectPath(const QString &appId) |
1349 | +{ |
1350 | + QByteArray path(QByteArrayLiteral("/com/ubuntu/Postal/")); |
1351 | + |
1352 | + QByteArray pkg = appId.split('_').first().toUtf8(); |
1353 | + for (int i = 0; i < pkg.count(); i++) { |
1354 | + char buffer[10]; |
1355 | + char c = pkg[i]; |
1356 | + switch (c) { |
1357 | + case '+': |
1358 | + case '.': |
1359 | + case '-': |
1360 | + case ':': |
1361 | + case '~': |
1362 | + case '_': |
1363 | + sprintf(buffer, "_%.2x", c); |
1364 | + path += buffer; |
1365 | + break; |
1366 | + default: |
1367 | + path += c; |
1368 | + } |
1369 | + } |
1370 | + return path; |
1371 | +} |
1372 | + |
1373 | +PushClient::PushClient(QObject *parent): |
1374 | + QObject(parent), |
1375 | + d_ptr(new PushClientPrivate(this)) |
1376 | +{ |
1377 | +} |
1378 | + |
1379 | +PushClient::~PushClient() |
1380 | +{ |
1381 | + delete d_ptr; |
1382 | +} |
1383 | + |
1384 | +void PushClient::post(const QString &appId, const QJsonObject &message) |
1385 | +{ |
1386 | + Q_D(PushClient); |
1387 | + |
1388 | + QByteArray objectPath = d->makeObjectPath(appId); |
1389 | + QDBusMessage msg = QDBusMessage::createMethodCall("com.ubuntu.Postal", |
1390 | + objectPath, |
1391 | + "com.ubuntu.Postal", |
1392 | + "Post"); |
1393 | + msg << appId; |
1394 | + QByteArray data = QJsonDocument(message).toJson(QJsonDocument::Compact); |
1395 | + msg << QString::fromUtf8(data); |
1396 | + |
1397 | + d->m_conn.send(msg); |
1398 | +} |
1399 | + |
1400 | +#include "push_client.moc" |
1401 | |
1402 | === added file 'account-polld/push_client.h' |
1403 | --- account-polld/push_client.h 1970-01-01 00:00:00 +0000 |
1404 | +++ account-polld/push_client.h 2016-09-19 11:35:53 +0000 |
1405 | @@ -0,0 +1,48 @@ |
1406 | +/* |
1407 | + * Copyright (C) 2016 Canonical Ltd. |
1408 | + * |
1409 | + * Contact: Alberto Mardegan <alberto.mardegan@canonical.com> |
1410 | + * |
1411 | + * This file is part of account-polld |
1412 | + * |
1413 | + * This program is free software: you can redistribute it and/or modify it |
1414 | + * under the terms of the GNU General Public License version 3, as published |
1415 | + * by the Free Software Foundation. |
1416 | + * |
1417 | + * This program is distributed in the hope that it will be useful, but |
1418 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
1419 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1420 | + * PURPOSE. See the GNU General Public License for more details. |
1421 | + * |
1422 | + * You should have received a copy of the GNU General Public License along |
1423 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
1424 | + */ |
1425 | + |
1426 | +#ifndef AP_PUSH_CLIENT_H |
1427 | +#define AP_PUSH_CLIENT_H |
1428 | + |
1429 | +#include <QObject> |
1430 | + |
1431 | +class QJsonObject; |
1432 | + |
1433 | +namespace AccountPolld { |
1434 | + |
1435 | +class PushClientPrivate; |
1436 | +class PushClient: public QObject |
1437 | +{ |
1438 | + Q_OBJECT |
1439 | + |
1440 | +public: |
1441 | + explicit PushClient(QObject *parent = 0); |
1442 | + ~PushClient(); |
1443 | + |
1444 | + void post(const QString &appId, const QJsonObject &message); |
1445 | + |
1446 | +private: |
1447 | + PushClientPrivate *d_ptr; |
1448 | + Q_DECLARE_PRIVATE(PushClient) |
1449 | +}; |
1450 | + |
1451 | +} // namespace |
1452 | + |
1453 | +#endif // AP_PUSH_CLIENT_H |
1454 | |
1455 | === removed directory 'accounts' |
1456 | === removed file 'accounts/account-watcher.c' |
1457 | --- accounts/account-watcher.c 2016-08-02 14:34:52 +0000 |
1458 | +++ accounts/account-watcher.c 1970-01-01 00:00:00 +0000 |
1459 | @@ -1,302 +0,0 @@ |
1460 | -/* |
1461 | - Copyright 2014 Canonical Ltd. |
1462 | - |
1463 | - This program is free software: you can redistribute it and/or modify it |
1464 | - under the terms of the GNU General Public License version 3, as published |
1465 | - by the Free Software Foundation. |
1466 | - |
1467 | - This program is distributed in the hope that it will be useful, but |
1468 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
1469 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1470 | - PURPOSE. See the GNU General Public License for more details. |
1471 | - |
1472 | - You should have received a copy of the GNU General Public License along |
1473 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
1474 | -*/ |
1475 | -#include <stdio.h> |
1476 | - |
1477 | -#include <glib.h> |
1478 | -#include <libaccounts-glib/accounts-glib.h> |
1479 | -#include <libsignon-glib/signon-glib.h> |
1480 | - |
1481 | -#include "account-watcher.h" |
1482 | - |
1483 | -/* #define DEBUG */ |
1484 | -#ifdef DEBUG |
1485 | -# define trace(...) fprintf(stderr, __VA_ARGS__) |
1486 | -#else |
1487 | -# define trace(...) |
1488 | -#endif |
1489 | - |
1490 | -struct _AccountWatcher { |
1491 | - AgManager *manager; |
1492 | - /* A hash table of the enabled accounts we know of. |
1493 | - * Keys are "<accountId>/<serviceName>", and AccountInfo structs as values. |
1494 | - */ |
1495 | - GHashTable *services; |
1496 | - |
1497 | - /* List of supported services' IDs */ |
1498 | - GSList *supported_services; |
1499 | - |
1500 | - AccountEnabledCallback callback; |
1501 | - void *user_data; |
1502 | -}; |
1503 | - |
1504 | -typedef struct _AccountInfo AccountInfo; |
1505 | -struct _AccountInfo { |
1506 | - AccountWatcher *watcher; |
1507 | - /* Manage signin session for account */ |
1508 | - AgAccountService *account_service; |
1509 | - SignonAuthSession *session; |
1510 | - GVariant *auth_params; |
1511 | - GVariant *session_data; |
1512 | - |
1513 | - AgAccountId account_id; |
1514 | -}; |
1515 | - |
1516 | -static void account_info_clear_login(AccountInfo *info) { |
1517 | - if (info->session_data) { |
1518 | - g_variant_unref(info->session_data); |
1519 | - info->session_data = NULL; |
1520 | - } |
1521 | - if (info->auth_params) { |
1522 | - g_variant_unref(info->auth_params); |
1523 | - info->auth_params = NULL; |
1524 | - } |
1525 | - if (info->session) { |
1526 | - signon_auth_session_cancel(info->session); |
1527 | - g_object_unref(info->session); |
1528 | - info->session = NULL; |
1529 | - } |
1530 | -} |
1531 | - |
1532 | -static void account_info_free(AccountInfo *info) { |
1533 | - account_info_clear_login(info); |
1534 | - if (info->account_service) { |
1535 | - g_object_unref(info->account_service); |
1536 | - info->account_service = NULL; |
1537 | - } |
1538 | - g_free(info); |
1539 | -} |
1540 | - |
1541 | -static void account_info_notify(AccountInfo *info, GError *error) { |
1542 | - AgService *service = ag_account_service_get_service(info->account_service); |
1543 | - const char *service_name = ag_service_get_name(service); |
1544 | - const char *service_type = ag_service_get_service_type(service); |
1545 | - char *client_id = NULL; |
1546 | - char *client_secret = NULL; |
1547 | - char *access_token = NULL; |
1548 | - char *token_secret = NULL; |
1549 | - char *secret = NULL; |
1550 | - char *user_name = NULL; |
1551 | - |
1552 | - if (info->auth_params != NULL) { |
1553 | - /* Look up OAuth 2 parameters, falling back to OAuth 1 names */ |
1554 | - g_variant_lookup(info->auth_params, "ClientId", "&s", &client_id); |
1555 | - g_variant_lookup(info->auth_params, "ClientSecret", "&s", &client_secret); |
1556 | - if (client_id == NULL) { |
1557 | - g_variant_lookup(info->auth_params, "ConsumerKey", "&s", &client_id); |
1558 | - } |
1559 | - if (client_secret == NULL) { |
1560 | - g_variant_lookup(info->auth_params, "ConsumerSecret", "&s", &client_secret); |
1561 | - } |
1562 | - } |
1563 | - if (info->session_data != NULL) { |
1564 | - g_variant_lookup(info->session_data, "AccessToken", "&s", &access_token); |
1565 | - g_variant_lookup(info->session_data, "TokenSecret", "&s", &token_secret); |
1566 | - g_variant_lookup(info->session_data, "Secret", "&s", &secret); |
1567 | - g_variant_lookup(info->session_data, "UserName", "&s", &user_name); |
1568 | - } |
1569 | - |
1570 | - info->watcher->callback(info->watcher, |
1571 | - info->account_id, |
1572 | - service_type, |
1573 | - service_name, |
1574 | - error, |
1575 | - TRUE, |
1576 | - client_id, |
1577 | - client_secret, |
1578 | - access_token, |
1579 | - token_secret, |
1580 | - user_name, |
1581 | - secret, |
1582 | - info->watcher->user_data); |
1583 | -} |
1584 | - |
1585 | -static void account_info_login_cb(GObject *source, GAsyncResult *result, void *user_data) { |
1586 | - SignonAuthSession *session = (SignonAuthSession *)source; |
1587 | - AccountInfo *info = (AccountInfo *)user_data; |
1588 | - |
1589 | - trace("Authentication for account %u complete\n", info->account_id); |
1590 | - |
1591 | - GError *error = NULL; |
1592 | - info->session_data = signon_auth_session_process_finish(session, result, &error); |
1593 | - account_info_notify(info, error); |
1594 | - |
1595 | - if (error != NULL) { |
1596 | - trace("Authentication failed: %s\n", error->message); |
1597 | - g_error_free(error); |
1598 | - } |
1599 | -} |
1600 | - |
1601 | -static void account_info_login(AccountInfo *info) { |
1602 | - account_info_clear_login(info); |
1603 | - |
1604 | - AgAuthData *auth_data = ag_account_service_get_auth_data(info->account_service); |
1605 | - GError *error = NULL; |
1606 | - trace("Starting authentication session for account %u\n", info->account_id); |
1607 | - info->session = signon_auth_session_new( |
1608 | - ag_auth_data_get_credentials_id(auth_data), |
1609 | - ag_auth_data_get_method(auth_data), &error); |
1610 | - if (error != NULL) { |
1611 | - trace("Could not set up auth session: %s\n", error->message); |
1612 | - account_info_notify(info, error); |
1613 | - g_error_free(error); |
1614 | - g_object_unref(auth_data); |
1615 | - return; |
1616 | - } |
1617 | - |
1618 | - /* Tell libsignon-glib not to open a trust session as we have no UI */ |
1619 | - GVariantBuilder builder; |
1620 | - g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); |
1621 | - g_variant_builder_add(&builder, "{sv}", |
1622 | - SIGNON_SESSION_DATA_UI_POLICY, |
1623 | - g_variant_new_int32(SIGNON_POLICY_NO_USER_INTERACTION)); |
1624 | - |
1625 | - info->auth_params = g_variant_ref_sink( |
1626 | - ag_auth_data_get_login_parameters( |
1627 | - auth_data, |
1628 | - g_variant_builder_end(&builder))); |
1629 | - |
1630 | - signon_auth_session_process_async( |
1631 | - info->session, |
1632 | - info->auth_params, |
1633 | - ag_auth_data_get_mechanism(auth_data), |
1634 | - NULL, /* cancellable */ |
1635 | - account_info_login_cb, info); |
1636 | - ag_auth_data_unref(auth_data); |
1637 | -} |
1638 | - |
1639 | -static AccountInfo *account_info_new(AccountWatcher *watcher, AgAccountService *account_service) { |
1640 | - AccountInfo *info = g_new0(AccountInfo, 1); |
1641 | - info->watcher = watcher; |
1642 | - info->account_service = g_object_ref(account_service); |
1643 | - |
1644 | - AgAccount *account = ag_account_service_get_account(account_service); |
1645 | - g_object_get(account, "id", &info->account_id, NULL); |
1646 | - |
1647 | - return info; |
1648 | -} |
1649 | - |
1650 | -static gboolean service_is_supported(AccountWatcher *watcher, |
1651 | - const char *service_id) |
1652 | -{ |
1653 | - GSList *node = g_slist_find_custom(watcher->supported_services, |
1654 | - service_id, |
1655 | - (GCompareFunc)g_strcmp0); |
1656 | - return node != NULL; |
1657 | -} |
1658 | - |
1659 | -static gboolean account_watcher_setup(void *user_data) { |
1660 | - AccountWatcher *watcher = (AccountWatcher *)user_data; |
1661 | - |
1662 | - /* Now check initial state */ |
1663 | - GList *enabled_accounts = |
1664 | - ag_manager_get_enabled_account_services(watcher->manager); |
1665 | - GList *old_services = g_hash_table_get_keys(watcher->services); |
1666 | - |
1667 | - /* Update the services table */ |
1668 | - GList *l; |
1669 | - for (l = enabled_accounts; l != NULL; l = l->next) { |
1670 | - AgAccountService *account_service = l->data; |
1671 | - AgAccountId id = ag_account_service_get_account(account_service)->id; |
1672 | - AgService *service = ag_account_service_get_service(account_service); |
1673 | - const char *service_id = ag_service_get_name(service); |
1674 | - |
1675 | - if (!service_is_supported(watcher, service_id)) continue; |
1676 | - |
1677 | - char *key = g_strdup_printf("%d/%s", id, service_id); |
1678 | - |
1679 | - AccountInfo *info = g_hash_table_lookup(watcher->services, key); |
1680 | - if (info) { |
1681 | - GList *node = g_list_find_custom(old_services, key, |
1682 | - (GCompareFunc)g_strcmp0); |
1683 | - old_services = g_list_remove_link(old_services, node); |
1684 | - g_free(key); |
1685 | - } else { |
1686 | - trace("adding account %s\n", key); |
1687 | - info = account_info_new(watcher, account_service); |
1688 | - g_hash_table_insert(watcher->services, key, info); |
1689 | - } |
1690 | - account_info_login(info); |
1691 | - } |
1692 | - g_list_free_full(enabled_accounts, g_object_unref); |
1693 | - |
1694 | - /* Remove from the table the accounts which are no longer enabled */ |
1695 | - for (l = old_services; l != NULL; l = l->next) { |
1696 | - char *key = l->data; |
1697 | - trace("removing account %s\n", key); |
1698 | - g_hash_table_remove(watcher->services, key); |
1699 | - } |
1700 | - g_list_free(old_services); |
1701 | - |
1702 | - return G_SOURCE_REMOVE; |
1703 | -} |
1704 | - |
1705 | -AccountWatcher *account_watcher_new(AccountEnabledCallback callback, |
1706 | - void *user_data) { |
1707 | - AccountWatcher *watcher = g_new0(AccountWatcher, 1); |
1708 | - |
1709 | - watcher->manager = ag_manager_new(); |
1710 | - watcher->services = g_hash_table_new_full( |
1711 | - g_str_hash, g_str_equal, g_free, (GDestroyNotify)account_info_free); |
1712 | - watcher->supported_services = NULL; |
1713 | - watcher->callback = callback; |
1714 | - watcher->user_data = user_data; |
1715 | - |
1716 | - return watcher; |
1717 | -} |
1718 | - |
1719 | -void account_watcher_add_service(AccountWatcher *watcher, |
1720 | - char *serviceId) { |
1721 | - watcher->supported_services = |
1722 | - g_slist_prepend(watcher->supported_services, serviceId); |
1723 | -} |
1724 | - |
1725 | -void account_watcher_run(AccountWatcher *watcher) { |
1726 | - /* Make sure main setup occurs within the mainloop thread */ |
1727 | - g_idle_add(account_watcher_setup, watcher); |
1728 | -} |
1729 | - |
1730 | -struct refresh_info { |
1731 | - AccountWatcher *watcher; |
1732 | - AgAccountId account_id; |
1733 | - char *service_name; |
1734 | -}; |
1735 | - |
1736 | -static void refresh_info_free(struct refresh_info *data) { |
1737 | - g_free(data->service_name); |
1738 | - g_free(data); |
1739 | -} |
1740 | - |
1741 | -static gboolean account_watcher_refresh_cb(void *user_data) { |
1742 | - struct refresh_info *data = (struct refresh_info *)user_data; |
1743 | - |
1744 | - char *key = g_strdup_printf("%d/%s", data->account_id, data->service_name); |
1745 | - AccountInfo *info = g_hash_table_lookup(data->watcher->services, key); |
1746 | - if (info != NULL) { |
1747 | - account_info_login(info); |
1748 | - } |
1749 | - |
1750 | - return G_SOURCE_REMOVE; |
1751 | -} |
1752 | - |
1753 | -void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id, |
1754 | - const char *service_name) { |
1755 | - struct refresh_info *data = g_new(struct refresh_info, 1); |
1756 | - data->watcher = watcher; |
1757 | - data->account_id = account_id; |
1758 | - data->service_name = g_strdup(service_name); |
1759 | - g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, account_watcher_refresh_cb, |
1760 | - data, (GDestroyNotify)refresh_info_free); |
1761 | -} |
1762 | |
1763 | === removed file 'accounts/account-watcher.h' |
1764 | --- accounts/account-watcher.h 2016-08-02 14:34:52 +0000 |
1765 | +++ accounts/account-watcher.h 1970-01-01 00:00:00 +0000 |
1766 | @@ -1,47 +0,0 @@ |
1767 | -/* |
1768 | - Copyright 2014 Canonical Ltd. |
1769 | - |
1770 | - This program is free software: you can redistribute it and/or modify it |
1771 | - under the terms of the GNU General Public License version 3, as published |
1772 | - by the Free Software Foundation. |
1773 | - |
1774 | - This program is distributed in the hope that it will be useful, but |
1775 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
1776 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1777 | - PURPOSE. See the GNU General Public License for more details. |
1778 | - |
1779 | - You should have received a copy of the GNU General Public License along |
1780 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
1781 | -*/ |
1782 | -#ifndef ACCOUNT_WATCHER_H |
1783 | -#define ACCOUNT_WATCHER_H |
1784 | - |
1785 | -#include <glib.h> |
1786 | - |
1787 | -typedef struct _AccountWatcher AccountWatcher; |
1788 | - |
1789 | -typedef void (*AccountEnabledCallback)(AccountWatcher *watcher, |
1790 | - unsigned int account_id, |
1791 | - const char *service_type, |
1792 | - const char *service_name, |
1793 | - GError *error, |
1794 | - int enabled, |
1795 | - const char *client_id, |
1796 | - const char *client_secret, |
1797 | - const char *access_token, |
1798 | - const char *token_secret, |
1799 | - const char *user_name, |
1800 | - const char *secret, |
1801 | - void *user_data); |
1802 | - |
1803 | -AccountWatcher *account_watcher_new(AccountEnabledCallback callback, |
1804 | - void *user_data); |
1805 | -void account_watcher_add_service(AccountWatcher *watcher, |
1806 | - char *serviceId); |
1807 | -void account_watcher_run(AccountWatcher *watcher); |
1808 | - |
1809 | -void account_watcher_refresh(AccountWatcher *watcher, |
1810 | - unsigned int account_id, |
1811 | - const char *service_name); |
1812 | - |
1813 | -#endif |
1814 | |
1815 | === removed file 'accounts/accounts.c' |
1816 | --- accounts/accounts.c 2016-08-02 14:34:52 +0000 |
1817 | +++ accounts/accounts.c 1970-01-01 00:00:00 +0000 |
1818 | @@ -1,26 +0,0 @@ |
1819 | -#include "_cgo_export.h" |
1820 | - |
1821 | -AccountWatcher *watch() { |
1822 | - /* Transfer service names to hash table */ |
1823 | - if (FALSE) { |
1824 | - /* The Go callback doesn't quite match the |
1825 | - * AccountEnabledCallback function prototype, so we cast the |
1826 | - * argument in the account_watcher_new() call below. |
1827 | - * |
1828 | - * This is just a check to see that the function still has the |
1829 | - * prototype we expect. |
1830 | - */ |
1831 | - void (*unused)(void *watcher, |
1832 | - unsigned int account_id, |
1833 | - char *service_type, char *service_name, |
1834 | - GError *error, int enabled, |
1835 | - char *client_id, char *client_secret, |
1836 | - char *access_token, char *token_secret, |
1837 | - char *user_name, char *secret, |
1838 | - void *user_data) = authCallback; |
1839 | - } |
1840 | - |
1841 | - AccountWatcher *watcher = account_watcher_new( |
1842 | - (AccountEnabledCallback)authCallback, NULL); |
1843 | - return watcher; |
1844 | -} |
1845 | |
1846 | === removed file 'accounts/accounts.go' |
1847 | --- accounts/accounts.go 2016-08-02 14:34:52 +0000 |
1848 | +++ accounts/accounts.go 1970-01-01 00:00:00 +0000 |
1849 | @@ -1,130 +0,0 @@ |
1850 | -/* |
1851 | - Copyright 2014 Canonical Ltd. |
1852 | - |
1853 | - This program is free software: you can redistribute it and/or modify it |
1854 | - under the terms of the GNU General Public License version 3, as published |
1855 | - by the Free Software Foundation. |
1856 | - |
1857 | - This program is distributed in the hope that it will be useful, but |
1858 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
1859 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1860 | - PURPOSE. See the GNU General Public License for more details. |
1861 | - |
1862 | - You should have received a copy of the GNU General Public License along |
1863 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
1864 | -*/ |
1865 | - |
1866 | -package accounts |
1867 | - |
1868 | -/* |
1869 | -#cgo pkg-config: glib-2.0 libaccounts-glib libsignon-glib |
1870 | -#include <stdlib.h> |
1871 | -#include <glib.h> |
1872 | -#include "account-watcher.h" |
1873 | - |
1874 | -AccountWatcher *watch(); |
1875 | -*/ |
1876 | -import "C" |
1877 | -import ( |
1878 | - "errors" |
1879 | - "sync" |
1880 | - "unsafe" |
1881 | -) |
1882 | - |
1883 | -type Watcher struct { |
1884 | - C <-chan AuthData |
1885 | - watcher *C.AccountWatcher |
1886 | -} |
1887 | - |
1888 | -type AuthData struct { |
1889 | - AccountId uint |
1890 | - ServiceName string |
1891 | - ServiceType string |
1892 | - Error error |
1893 | - Enabled bool |
1894 | - |
1895 | - ClientId string |
1896 | - ClientSecret string |
1897 | - AccessToken string |
1898 | - TokenSecret string |
1899 | - Secret string |
1900 | - UserName string |
1901 | -} |
1902 | - |
1903 | -var ( |
1904 | - authChannels = make(map[*C.AccountWatcher]chan<- AuthData) |
1905 | - authChannelsLock sync.Mutex |
1906 | -) |
1907 | - |
1908 | -// NewWatcher creates a new account watcher |
1909 | -func NewWatcher() *Watcher { |
1910 | - w := new(Watcher) |
1911 | - w.watcher = C.watch() |
1912 | - |
1913 | - ch := make(chan AuthData) |
1914 | - w.C = ch |
1915 | - authChannelsLock.Lock() |
1916 | - authChannels[w.watcher] = ch |
1917 | - authChannelsLock.Unlock() |
1918 | - |
1919 | - return w |
1920 | -} |
1921 | - |
1922 | -func (w *Watcher) AddService(serviceId string) { |
1923 | - C.account_watcher_add_service(w.watcher, C.CString(serviceId)) |
1924 | -} |
1925 | - |
1926 | -// Walk through the enabled accounts, and get auth tokens for each of them. |
1927 | -// The new access token will be delivered over the watcher's channel. |
1928 | -func (w *Watcher) Run() { |
1929 | - C.account_watcher_run(w.watcher) |
1930 | -} |
1931 | - |
1932 | -// Refresh requests that the token for the given account be refreshed. |
1933 | -// The new access token will be delivered over the watcher's channel. |
1934 | -func (w *Watcher) Refresh(accountId uint, serviceName string) { |
1935 | - C.account_watcher_refresh(w.watcher, C.uint(accountId), C.CString(serviceName)) |
1936 | -} |
1937 | - |
1938 | -//export authCallback |
1939 | -func authCallback(watcher unsafe.Pointer, accountId C.uint, serviceType *C.char, serviceName *C.char, error *C.GError, enabled C.int, clientId, clientSecret, accessToken, tokenSecret *C.char, userName *C.char, secret *C.char, userData unsafe.Pointer) { |
1940 | - // Ideally the first argument would be of type |
1941 | - // *C.AccountWatcher, but that fails with Go 1.2. |
1942 | - authChannelsLock.Lock() |
1943 | - ch := authChannels[(*C.AccountWatcher)(watcher)] |
1944 | - authChannelsLock.Unlock() |
1945 | - if ch == nil { |
1946 | - // Log the error |
1947 | - return |
1948 | - } |
1949 | - |
1950 | - var data AuthData |
1951 | - data.AccountId = uint(accountId) |
1952 | - data.ServiceName = C.GoString(serviceName) |
1953 | - data.ServiceType = C.GoString(serviceType) |
1954 | - if error != nil { |
1955 | - data.Error = errors.New(C.GoString((*C.char)(error.message))) |
1956 | - } |
1957 | - if enabled != 0 { |
1958 | - data.Enabled = true |
1959 | - } |
1960 | - if clientId != nil { |
1961 | - data.ClientId = C.GoString(clientId) |
1962 | - } |
1963 | - if clientSecret != nil { |
1964 | - data.ClientSecret = C.GoString(clientSecret) |
1965 | - } |
1966 | - if accessToken != nil { |
1967 | - data.AccessToken = C.GoString(accessToken) |
1968 | - } |
1969 | - if tokenSecret != nil { |
1970 | - data.TokenSecret = C.GoString(tokenSecret) |
1971 | - } |
1972 | - if secret != nil { |
1973 | - data.Secret = C.GoString(secret) |
1974 | - } |
1975 | - if userName != nil { |
1976 | - data.UserName = C.GoString(userName) |
1977 | - } |
1978 | - ch <- data |
1979 | -} |
1980 | |
1981 | === added directory 'click-hook' |
1982 | === added file 'click-hook/account-polld.hook.in' |
1983 | --- click-hook/account-polld.hook.in 1970-01-01 00:00:00 +0000 |
1984 | +++ click-hook/account-polld.hook.in 2016-09-19 11:35:53 +0000 |
1985 | @@ -0,0 +1,4 @@ |
1986 | +Pattern: ${home}/.local/share/account-polld/plugins/${id}.json |
1987 | +User-Level: yes |
1988 | +Hook-Name: account-polld |
1989 | +Exec: $${hook_helper.path}/$${hook_helper.files} |
1990 | |
1991 | === added file 'click-hook/click-hook' |
1992 | --- click-hook/click-hook 1970-01-01 00:00:00 +0000 |
1993 | +++ click-hook/click-hook 2016-09-19 11:35:53 +0000 |
1994 | @@ -0,0 +1,108 @@ |
1995 | +#!/usr/bin/python3 |
1996 | +# -*- python -*- |
1997 | +"""Collect helpers hook data into a single json file""" |
1998 | + |
1999 | +import json |
2000 | +import os |
2001 | +import sys |
2002 | +import time |
2003 | + |
2004 | +import xdg.BaseDirectory |
2005 | + |
2006 | +hook_ext = '.json' |
2007 | + |
2008 | + |
2009 | +class HookProcessor: |
2010 | + def __init__(self): |
2011 | + self.xdg_data_home = xdg.BaseDirectory.xdg_data_home |
2012 | + self.xdg_data_dirs = xdg.BaseDirectory.xdg_data_dirs |
2013 | + self.plugins_data_path = os.path.join(self.xdg_data_home, 'account-polld', |
2014 | + 'plugin_data.json') |
2015 | + self.plugins_data_path_tmp = os.path.join(self.xdg_data_home, 'account-polld', |
2016 | + '.plugin_data_%s.tmp') |
2017 | + os.makedirs(os.path.join(self.xdg_data_home, 'account-polld'), exist_ok=True) |
2018 | + |
2019 | + |
2020 | + def write_plugin_data(self): |
2021 | + plugin_data = {} |
2022 | + for path in self.xdg_data_dirs: |
2023 | + data = self.collect_plugins(path) |
2024 | + plugin_data.update(data) |
2025 | + |
2026 | + # write the collected data to a temp file and rename the original once |
2027 | + # everything is on disk |
2028 | + try: |
2029 | + tmp_filename = self.plugins_data_path_tmp % (time.time(),) |
2030 | + with open(tmp_filename, 'w') as dest: |
2031 | + json.dump(plugin_data, dest) |
2032 | + dest.flush() |
2033 | + os.rename(tmp_filename, self.plugins_data_path) |
2034 | + except Exception as e: |
2035 | + print('Writing file %s failed: %s' % (self.plugins_data_path, e), file=sys.stderr) |
2036 | + return False |
2037 | + return True |
2038 | + |
2039 | + |
2040 | + def collect_plugins(self, base_path): |
2041 | + trusted = False if base_path == self.xdg_data_home else True |
2042 | + hooks_path = os.path.join(base_path, 'account-polld', 'plugins') |
2043 | + plugins_data = {} |
2044 | + if not os.path.isdir(hooks_path): |
2045 | + return plugins_data |
2046 | + for hook_fname in os.listdir(hooks_path): |
2047 | + if not hook_fname.endswith(hook_ext): |
2048 | + continue |
2049 | + try: |
2050 | + with open(os.path.join(hooks_path, hook_fname), 'r') as fd: |
2051 | + data = json.load(fd) |
2052 | + except Exception: |
2053 | + print('Unable to parse JSON from %s' % (hook_fname,), file=sys.stderr) |
2054 | + continue |
2055 | + |
2056 | + helper_id = os.path.splitext(hook_fname)[0] |
2057 | + profile = 'unconfined' if trusted else helper_id |
2058 | + if helper_id.count('_') == 2: |
2059 | + helper_short_id = '_'.join(helper_id.split('_')[0:2]) |
2060 | + else: |
2061 | + helper_short_id = helper_id |
2062 | + |
2063 | + exec_path = data['exec'] |
2064 | + if exec_path != "": |
2065 | + realpath = os.path.realpath(os.path.join(hooks_path, |
2066 | + hook_fname)) |
2067 | + exec_path = os.path.join(os.path.dirname(realpath), exec_path) |
2068 | + app_id = data.get('app_id', None) |
2069 | + if app_id is None: |
2070 | + # no app_id, use the package name from the helper_id |
2071 | + app_id = helper_short_id |
2072 | + elif app_id.count('_') >= 2: |
2073 | + # remove the version from the app_id |
2074 | + app_id = '_'.join(app_id.split('_')[0:2]) |
2075 | + if not trusted: |
2076 | + # check that the plugin comes from the same package as the app |
2077 | + plugin_package = helper_id.split('_')[0] |
2078 | + app_package = app_id.split('_')[0] |
2079 | + if plugin_package != app_package: |
2080 | + print('Skipping %s as it\'s unrelated to package %s' % (hook_fname, app_package), file=sys.stderr) |
2081 | + continue |
2082 | + |
2083 | + parsed = { |
2084 | + 'exec': exec_path, |
2085 | + 'appId': app_id, |
2086 | + 'profile': profile, |
2087 | + } |
2088 | + parsed['needsAuthData'] = data.get('needs_authentication_data', False) |
2089 | + if 'service_ids' in data: |
2090 | + parsed['services'] = data['service_ids'] |
2091 | + if 'interval' in data: |
2092 | + parsed['interval'] = data['interval'] |
2093 | + plugins_data[helper_short_id] = parsed |
2094 | + |
2095 | + return plugins_data |
2096 | + |
2097 | + |
2098 | +if __name__ == "__main__": |
2099 | + processor = HookProcessor() |
2100 | + ok = processor.write_plugin_data() |
2101 | + |
2102 | + sys.exit(0 if ok else 1) |
2103 | |
2104 | === added file 'click-hook/click-hook.pro' |
2105 | --- click-hook/click-hook.pro 1970-01-01 00:00:00 +0000 |
2106 | +++ click-hook/click-hook.pro 2016-09-19 11:35:53 +0000 |
2107 | @@ -0,0 +1,20 @@ |
2108 | +include(../common-project-config.pri) |
2109 | + |
2110 | +TEMPLATE = aux |
2111 | +TARGET = "" |
2112 | + |
2113 | +QMAKE_SUBSTITUTES += \ |
2114 | + account-polld.hook.in |
2115 | + |
2116 | +OTHER_FILES += \ |
2117 | + click-hook |
2118 | + |
2119 | +hook_helper.files = \ |
2120 | + click-hook |
2121 | +hook_helper.path = $${INSTALL_PREFIX}/lib/account-polld |
2122 | +INSTALLS += hook_helper |
2123 | + |
2124 | +hooks.files = \ |
2125 | + account-polld.hook |
2126 | +hooks.path = $${INSTALL_PREFIX}/share/click/hooks |
2127 | +INSTALLS += hooks |
2128 | |
2129 | === removed directory 'cmd' |
2130 | === removed directory 'cmd/account-polld' |
2131 | === removed file 'cmd/account-polld/account_service.go' |
2132 | --- cmd/account-polld/account_service.go 2016-08-02 14:34:52 +0000 |
2133 | +++ cmd/account-polld/account_service.go 1970-01-01 00:00:00 +0000 |
2134 | @@ -1,186 +0,0 @@ |
2135 | -/* |
2136 | - Copyright 2014 Canonical Ltd. |
2137 | - |
2138 | - This program is free software: you can redistribute it and/or modify it |
2139 | - under the terms of the GNU General Public License version 3, as published |
2140 | - by the Free Software Foundation. |
2141 | - |
2142 | - This program is distributed in the hope that it will be useful, but |
2143 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
2144 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2145 | - PURPOSE. See the GNU General Public License for more details. |
2146 | - |
2147 | - You should have received a copy of the GNU General Public License along |
2148 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
2149 | -*/ |
2150 | - |
2151 | -package main |
2152 | - |
2153 | -import ( |
2154 | - "errors" |
2155 | - "log" |
2156 | - "time" |
2157 | - |
2158 | - "launchpad.net/account-polld/accounts" |
2159 | - "launchpad.net/account-polld/plugins" |
2160 | - "launchpad.net/ubuntu-push/click" |
2161 | -) |
2162 | - |
2163 | -type AccountService struct { |
2164 | - watcher *accounts.Watcher |
2165 | - authData accounts.AuthData |
2166 | - plugin plugins.Plugin |
2167 | - interval time.Duration |
2168 | - postWatch chan *PostWatch |
2169 | - authChan chan accounts.AuthData |
2170 | - doneChan chan error |
2171 | - penaltyCount int |
2172 | - authFailureCount int |
2173 | -} |
2174 | - |
2175 | -var ( |
2176 | - pollTimeout = time.Duration(30 * time.Second) |
2177 | - bootstrapPollTimeout = time.Duration(4 * time.Minute) |
2178 | - maxCounter = 4 |
2179 | - authTriesUntilPenalty = 3 |
2180 | - authFailurePenalty = 10 |
2181 | -) |
2182 | - |
2183 | -var ( |
2184 | - authError = errors.New("Skipped account") |
2185 | - clickNotInstalledError = errors.New("Click not installed") |
2186 | -) |
2187 | - |
2188 | -func NewAccountService(watcher *accounts.Watcher, postWatch chan *PostWatch, plugin plugins.Plugin) *AccountService { |
2189 | - return &AccountService{ |
2190 | - watcher: watcher, |
2191 | - plugin: plugin, |
2192 | - postWatch: postWatch, |
2193 | - authChan: make(chan accounts.AuthData, 1), |
2194 | - doneChan: make(chan error, 1), |
2195 | - } |
2196 | -} |
2197 | - |
2198 | -func (a *AccountService) Delete() { |
2199 | - close(a.authChan) |
2200 | - close(a.doneChan) |
2201 | -} |
2202 | - |
2203 | -// Poll() always needs to be called asynchronously as otherwise qtcontacs' GetAvatar() |
2204 | -// will raise an error: "QSocketNotifier: Can only be used with threads started with QThread" |
2205 | -func (a *AccountService) Poll(bootstrap bool) { |
2206 | - gotNewAuthData := false |
2207 | - if a.authData, gotNewAuthData = <-a.authChan; !gotNewAuthData { |
2208 | - log.Println("Account", a.authData.AccountId, "no longer enabled") |
2209 | - return |
2210 | - } |
2211 | - |
2212 | - if a.penaltyCount > 0 { |
2213 | - log.Printf("Leaving poll for account %d as penalty count is %d", a.authData.AccountId, a.penaltyCount) |
2214 | - a.penaltyCount-- |
2215 | - return |
2216 | - } else if !gotNewAuthData && a.authData.Error != nil { |
2217 | - // Retry to poll the account with a previous auth failure as that results in reauthentication in case of token expiry and in ignoring temporary network issues |
2218 | - log.Println("Retrying to poll account with previous auth failure and id", a.authData.AccountId, "(results in reauthentication in case of token expiry and in ignoring temporary network issues)") |
2219 | - a.authData.Error = nil |
2220 | - } |
2221 | - |
2222 | - timeout := pollTimeout |
2223 | - if bootstrap { |
2224 | - timeout = bootstrapPollTimeout |
2225 | - } |
2226 | - |
2227 | - log.Printf("Starting poll for account %d", a.authData.AccountId) |
2228 | - go a.poll() |
2229 | - |
2230 | - select { |
2231 | - case <-time.After(timeout): |
2232 | - log.Println("Poll for account", a.authData.AccountId, "has timed out out after", timeout) |
2233 | - a.penaltyCount++ |
2234 | - case err := <-a.doneChan: |
2235 | - if err == nil { |
2236 | - log.Println("Poll for account", a.authData.AccountId, "was successful") |
2237 | - a.authFailureCount = 0 |
2238 | - a.penaltyCount = 0 |
2239 | - } else { |
2240 | - if err != clickNotInstalledError && err != authError { // Do not log the error twice |
2241 | - log.Println("Poll for account", a.authData.AccountId, "has failed:", err) |
2242 | - } |
2243 | - if err == authError || err == plugins.ErrTokenExpired { |
2244 | - // Increase the authFailureCount counter, except for when we did a poll which |
2245 | - // raised a token expiry error when we did not get any new auth data this time. |
2246 | - if err != plugins.ErrTokenExpired || gotNewAuthData { |
2247 | - log.Println("Increasing the auth failure counter for account", a.authData.AccountId) |
2248 | - a.authFailureCount++ |
2249 | - } else { |
2250 | - log.Println("Not increasing the auth failure counter for account", a.authData.AccountId, "as we do not have new auth data") |
2251 | - } |
2252 | - if a.authFailureCount >= authTriesUntilPenalty { |
2253 | - a.penaltyCount = authFailurePenalty |
2254 | - a.authFailureCount = 0 |
2255 | - log.Println(authTriesUntilPenalty, "auth failures in a row for account", a.authData.AccountId, "-> skipping it for the next", a.penaltyCount, "poll cycles") |
2256 | - } else if err == plugins.ErrTokenExpired && !gotNewAuthData { |
2257 | - // If the error indicates that the authentication token has expired, request reauthentication |
2258 | - // and mark the data as disabled. |
2259 | - // Do not refresh immediately when we just got new (faulty) auth data as immediately trying |
2260 | - // again is probably not going to help. Instead, we wait for the next poll cycle. |
2261 | - a.watcher.Refresh(a.authData.AccountId, a.authData.ServiceName) |
2262 | - a.authData.Enabled = false |
2263 | - a.authData.Error = err |
2264 | - } |
2265 | - } else if a.penaltyCount < maxCounter { |
2266 | - a.authFailureCount = 0 |
2267 | - a.penaltyCount++ |
2268 | - } |
2269 | - } |
2270 | - } |
2271 | - log.Printf("Ending poll for account %d", a.authData.AccountId) |
2272 | -} |
2273 | - |
2274 | -func (a *AccountService) poll() { |
2275 | - log.Println("Polling account", a.authData.AccountId) |
2276 | - if !isClickInstalled(a.plugin.ApplicationId()) { |
2277 | - log.Println( |
2278 | - "Skipping account", a.authData.AccountId, "as target click", |
2279 | - a.plugin.ApplicationId(), "is not installed") |
2280 | - a.doneChan <- clickNotInstalledError |
2281 | - return |
2282 | - } |
2283 | - |
2284 | - if a.authData.Error != nil { |
2285 | - log.Println("Account", a.authData.AccountId, "failed to authenticate:", a.authData.Error) |
2286 | - a.doneChan <- authError |
2287 | - return |
2288 | - } |
2289 | - |
2290 | - if bs, err := a.plugin.Poll(&a.authData); err != nil { |
2291 | - log.Print("Error while polling ", a.authData.AccountId, ": ", err) |
2292 | - a.doneChan <- err |
2293 | - } else { |
2294 | - for _, b := range bs { |
2295 | - log.Println("Account", a.authData.AccountId, "has", len(b.Messages), b.Tag, "updates to report") |
2296 | - } |
2297 | - a.postWatch <- &PostWatch{batches: bs, appId: a.plugin.ApplicationId()} |
2298 | - a.doneChan <- nil |
2299 | - } |
2300 | -} |
2301 | - |
2302 | -func (a *AccountService) updateAuthData(authData accounts.AuthData) { |
2303 | - a.authChan <- authData |
2304 | -} |
2305 | - |
2306 | -func isClickInstalled(appId plugins.ApplicationId) bool { |
2307 | - user, err := click.User() |
2308 | - if err != nil { |
2309 | - log.Println("User instance for click cannot be created to determine if click application", appId, "was installed") |
2310 | - return false |
2311 | - } |
2312 | - |
2313 | - app, err := click.ParseAppId(string(appId)) |
2314 | - if err != nil { |
2315 | - log.Println("Could not parse APP_ID for", appId) |
2316 | - return false |
2317 | - } |
2318 | - |
2319 | - return user.Installed(app, false) |
2320 | -} |
2321 | |
2322 | === removed file 'cmd/account-polld/main.go' |
2323 | --- cmd/account-polld/main.go 2016-08-02 14:34:52 +0000 |
2324 | +++ cmd/account-polld/main.go 1970-01-01 00:00:00 +0000 |
2325 | @@ -1,260 +0,0 @@ |
2326 | -/* |
2327 | - Copyright 2014 Canonical Ltd. |
2328 | - |
2329 | - This program is free software: you can redistribute it and/or modify it |
2330 | - under the terms of the GNU General Public License version 3, as published |
2331 | - by the Free Software Foundation. |
2332 | - |
2333 | - This program is distributed in the hope that it will be useful, but |
2334 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
2335 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2336 | - PURPOSE. See the GNU General Public License for more details. |
2337 | - |
2338 | - You should have received a copy of the GNU General Public License along |
2339 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
2340 | -*/ |
2341 | - |
2342 | -package main |
2343 | - |
2344 | -import ( |
2345 | - "encoding/json" |
2346 | - "fmt" |
2347 | - "strings" |
2348 | - "sync" |
2349 | - |
2350 | - "log" |
2351 | - |
2352 | - "launchpad.net/account-polld/accounts" |
2353 | - "launchpad.net/account-polld/gettext" |
2354 | - "launchpad.net/account-polld/plugins" |
2355 | - "launchpad.net/account-polld/plugins/caldav" |
2356 | - "launchpad.net/account-polld/plugins/dekko" |
2357 | - "launchpad.net/account-polld/plugins/gcalendar" |
2358 | - "launchpad.net/account-polld/plugins/gmail" |
2359 | - "launchpad.net/account-polld/plugins/twitter" |
2360 | - "launchpad.net/account-polld/pollbus" |
2361 | - "launchpad.net/account-polld/qtcontact" |
2362 | - "launchpad.net/go-dbus/v1" |
2363 | -) |
2364 | - |
2365 | -type PostWatch struct { |
2366 | - appId plugins.ApplicationId |
2367 | - batches []*plugins.PushMessageBatch |
2368 | -} |
2369 | - |
2370 | -type AccountKey struct { |
2371 | - serviceId string |
2372 | - accountId uint |
2373 | -} |
2374 | - |
2375 | -/* Use identifiers and API keys provided by the respective webapps which are the official |
2376 | - end points for the notifications */ |
2377 | -const ( |
2378 | - SERVICENAME_DEKKO = "dekko.dekkoproject_dekko" |
2379 | - SERVICENAME_GMAIL = "com.ubuntu.developer.webapps.webapp-gmail_webapp-gmail" |
2380 | - SERVICENAME_TWITTER = "com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter" |
2381 | - SERVICENAME_GCALENDAR = "google-caldav" |
2382 | - SERVICENAME_OCALENDAR = "owncloud-caldav" |
2383 | -) |
2384 | - |
2385 | -const ( |
2386 | - POSTAL_SERVICE = "com.ubuntu.Postal" |
2387 | - POSTAL_INTERFACE = "com.ubuntu.Postal" |
2388 | - POSTAL_OBJECT_PATH_PART = "/com/ubuntu/Postal/" |
2389 | -) |
2390 | - |
2391 | -var mainLoopOnce sync.Once |
2392 | - |
2393 | -func init() { |
2394 | - startMainLoop() |
2395 | -} |
2396 | - |
2397 | -func startMainLoop() { |
2398 | - mainLoopOnce.Do(func() { |
2399 | - go qtcontact.MainLoopStart() |
2400 | - }) |
2401 | -} |
2402 | - |
2403 | -func main() { |
2404 | - // TODO NewAccount called here is just for playing purposes. |
2405 | - postWatch := make(chan *PostWatch) |
2406 | - |
2407 | - // Initialize i18n |
2408 | - gettext.SetLocale(gettext.LC_ALL, "") |
2409 | - gettext.Textdomain("account-polld") |
2410 | - gettext.BindTextdomain("account-polld", "/usr/share/locale") |
2411 | - |
2412 | - bus, err := dbus.Connect(dbus.SessionBus) |
2413 | - if err != nil { |
2414 | - log.Fatal("Cannot connect to bus", err) |
2415 | - } |
2416 | - |
2417 | - pollBus := pollbus.New(bus) |
2418 | - go postOffice(bus, postWatch) |
2419 | - go monitorAccounts(postWatch, pollBus) |
2420 | - |
2421 | - if err := pollBus.Init(); err != nil { |
2422 | - log.Fatal("Issue while setting up the poll bus:", err) |
2423 | - } |
2424 | - |
2425 | - done := make(chan bool) |
2426 | - <-done |
2427 | -} |
2428 | - |
2429 | -func monitorAccounts(postWatch chan *PostWatch, pollBus *pollbus.PollBus) { |
2430 | - watcher := accounts.NewWatcher() |
2431 | - watcher.AddService(SERVICENAME_DEKKO) |
2432 | - watcher.AddService(SERVICENAME_GMAIL) |
2433 | - watcher.AddService(SERVICENAME_GCALENDAR) |
2434 | - watcher.AddService(SERVICENAME_TWITTER) |
2435 | - |
2436 | - mgr := make(map[AccountKey]*AccountService) |
2437 | - |
2438 | - var wg sync.WaitGroup |
2439 | - |
2440 | - pullAccount := func(data accounts.AuthData) bool { |
2441 | - accountKey := AccountKey{data.ServiceName, data.AccountId} |
2442 | - if account, ok := mgr[accountKey]; ok { |
2443 | - if data.Enabled { |
2444 | - log.Println("New account data for existing account with id", data.AccountId) |
2445 | - account.penaltyCount = 0 |
2446 | - account.updateAuthData(data) |
2447 | - wg.Add(1) |
2448 | - go func() { |
2449 | - defer wg.Done() |
2450 | - // Poll() needs to be called asynchronously as otherwise qtcontacs' GetAvatar() will |
2451 | - // raise an error: "QSocketNotifier: Can only be used with threads started with QThread" |
2452 | - account.Poll(false) |
2453 | - }() |
2454 | - // No wg.Wait() here as it would break GetAvatar() again. |
2455 | - // Instead we have a wg.Wait() before the PollChan polling below. |
2456 | - } else { |
2457 | - account.Delete() |
2458 | - delete(mgr, accountKey) |
2459 | - } |
2460 | - } else if data.Enabled { |
2461 | - var plugin plugins.Plugin |
2462 | - log.Println("Creating plugin for service: ", data.ServiceName) |
2463 | - switch data.ServiceName { |
2464 | - case SERVICENAME_DEKKO: |
2465 | - log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) |
2466 | - plugin = dekko.New(data.AccountId) |
2467 | - case SERVICENAME_GMAIL: |
2468 | - log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) |
2469 | - plugin = gmail.New(data.AccountId) |
2470 | - case SERVICENAME_GCALENDAR: |
2471 | - log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) |
2472 | - plugin = gcalendar.New(data.AccountId) |
2473 | - case SERVICENAME_TWITTER: |
2474 | - // This is just stubbed until the plugin exists. |
2475 | - log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) |
2476 | - plugin = twitter.New() |
2477 | - case SERVICENAME_OCALENDAR: |
2478 | - log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) |
2479 | - plugin = caldav.New(data.AccountId) |
2480 | - default: |
2481 | - log.Println("Unhandled account with id", data.AccountId, "for", data.ServiceName) |
2482 | - return false |
2483 | - } |
2484 | - mgr[accountKey] = NewAccountService(watcher, postWatch, plugin) |
2485 | - mgr[accountKey].updateAuthData(data) |
2486 | - wg.Add(1) |
2487 | - go func() { |
2488 | - defer wg.Done() |
2489 | - // Poll() needs to be called asynchronously as otherwise qtcontacs' GetAvatar() will |
2490 | - // raise an error: "QSocketNotifier: Can only be used with threads started with QThread" |
2491 | - mgr[accountKey].Poll(true) |
2492 | - }() |
2493 | - // No wg.Wait() here as it would break GetAvatar() again. |
2494 | - // Instead we have a wg.Wait() before the PollChan polling below. |
2495 | - } |
2496 | - return true |
2497 | - } |
2498 | - |
2499 | -L: |
2500 | - for { |
2501 | - select { |
2502 | - case data := <-watcher.C: |
2503 | - if pullAccount(data) == false { |
2504 | - log.Println("pullAccount returned false, continuing") |
2505 | - continue L |
2506 | - } |
2507 | - case <-pollBus.PollChan: |
2508 | - wg.Wait() // Finish all running Poll() calls before potentially polling the same accounts again |
2509 | - watcher.Run() |
2510 | - wg.Wait() |
2511 | - pollBus.SignalDone() |
2512 | - } |
2513 | - } |
2514 | -} |
2515 | - |
2516 | -func postOffice(bus *dbus.Connection, postWatch chan *PostWatch) { |
2517 | - for post := range postWatch { |
2518 | - obj := bus.Object(POSTAL_SERVICE, pushObjectPath(post.appId)) |
2519 | - |
2520 | - for _, batch := range post.batches { |
2521 | - |
2522 | - notifs := batch.Messages |
2523 | - overflowing := len(notifs) > batch.Limit |
2524 | - |
2525 | - for i, n := range notifs { |
2526 | - // Play sound and vibrate on first notif only. |
2527 | - if i > 0 { |
2528 | - n.Notification.Vibrate = false |
2529 | - n.Notification.Sound = "" |
2530 | - } |
2531 | - |
2532 | - // We're overflowing, so no popups. |
2533 | - // See LP: #1527171 |
2534 | - if overflowing { |
2535 | - n.Notification.Card.Popup = false |
2536 | - } |
2537 | - } |
2538 | - |
2539 | - if overflowing { |
2540 | - n := batch.OverflowHandler(notifs) |
2541 | - n.Notification.Card.Persist = false |
2542 | - n.Notification.Vibrate = false |
2543 | - notifs = append(notifs, n) |
2544 | - } |
2545 | - |
2546 | - for _, n := range notifs { |
2547 | - var pushMessage string |
2548 | - if out, err := json.Marshal(n); err == nil { |
2549 | - pushMessage = string(out) |
2550 | - } else { |
2551 | - log.Printf("Cannot marshall %#v to json: %s", n, err) |
2552 | - continue |
2553 | - } |
2554 | - if _, err := obj.Call(POSTAL_INTERFACE, "Post", post.appId, pushMessage); err != nil { |
2555 | - log.Println("Cannot call the Post Office:", err) |
2556 | - log.Println("Message missed posting:", pushMessage) |
2557 | - } |
2558 | - } |
2559 | - } |
2560 | - } |
2561 | -} |
2562 | - |
2563 | -// pushObjectPath returns the object path of the ApplicationId |
2564 | -// for Push Notifications with the Quoted Package Name in the form of |
2565 | -// /com/ubuntu/PushNotifications/QUOTED_PKGNAME |
2566 | -// |
2567 | -// e.g.; if the APP_ID is com.ubuntu.music", the returned object path |
2568 | -// would be "/com/ubuntu/PushNotifications/com_2eubuntu_2eubuntu_2emusic |
2569 | -func pushObjectPath(id plugins.ApplicationId) dbus.ObjectPath { |
2570 | - idParts := strings.Split(string(id), "_") |
2571 | - if len(idParts) < 2 { |
2572 | - panic(fmt.Sprintf("APP_ID '%s' is not valid", id)) |
2573 | - } |
2574 | - |
2575 | - pkg := POSTAL_OBJECT_PATH_PART |
2576 | - for _, c := range idParts[0] { |
2577 | - switch c { |
2578 | - case '+', '.', '-', ':', '~', '_': |
2579 | - pkg += fmt.Sprintf("_%x", string(c)) |
2580 | - default: |
2581 | - pkg += string(c) |
2582 | - } |
2583 | - } |
2584 | - return dbus.ObjectPath(pkg) |
2585 | -} |
2586 | |
2587 | === removed directory 'cmd/account-watcher-test' |
2588 | === removed file 'cmd/account-watcher-test/main.go' |
2589 | --- cmd/account-watcher-test/main.go 2016-06-17 13:51:55 +0000 |
2590 | +++ cmd/account-watcher-test/main.go 1970-01-01 00:00:00 +0000 |
2591 | @@ -1,17 +0,0 @@ |
2592 | -package main |
2593 | - |
2594 | -import ( |
2595 | - "fmt" |
2596 | - |
2597 | - "launchpad.net/account-polld/accounts" |
2598 | -) |
2599 | - |
2600 | -func main() { |
2601 | - for data := range accounts.NewWatcher().C { |
2602 | - if data.Error != nil { |
2603 | - fmt.Println("Failed to authenticate account", data.AccountId, ":", data.Error) |
2604 | - } else { |
2605 | - fmt.Printf("%#v\n", data) |
2606 | - } |
2607 | - } |
2608 | -} |
2609 | |
2610 | === removed directory 'cmd/qtcontact-test' |
2611 | === removed file 'cmd/qtcontact-test/main.go' |
2612 | --- cmd/qtcontact-test/main.go 2015-03-20 14:34:48 +0000 |
2613 | +++ cmd/qtcontact-test/main.go 1970-01-01 00:00:00 +0000 |
2614 | @@ -1,36 +0,0 @@ |
2615 | -/* |
2616 | - Copyright 2014 Canonical Ltd. |
2617 | - |
2618 | - This program is free software: you can redistribute it and/or modify it |
2619 | - under the terms of the GNU General Public License version 3, as published |
2620 | - by the Free Software Foundation. |
2621 | - |
2622 | - This program is distributed in the hope that it will be useful, but |
2623 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
2624 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2625 | - PURPOSE. See the GNU General Public License for more details. |
2626 | - |
2627 | - You should have received a copy of the GNU General Public License along |
2628 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
2629 | -*/ |
2630 | - |
2631 | -package main |
2632 | - |
2633 | -import ( |
2634 | - "fmt" |
2635 | - "os" |
2636 | - |
2637 | - "launchpad.net/account-polld/qtcontact" |
2638 | -) |
2639 | - |
2640 | -func main() { |
2641 | - qtcontact.MainLoopStart() |
2642 | - |
2643 | - if len(os.Args) != 2 { |
2644 | - fmt.Println("usage:", os.Args[0], "[email address]") |
2645 | - os.Exit(1) |
2646 | - } |
2647 | - |
2648 | - path := qtcontact.GetAvatar(os.Args[1]) |
2649 | - fmt.Println("Avatar found:", path) |
2650 | -} |
2651 | |
2652 | === added file 'common-installs-config.pri' |
2653 | --- common-installs-config.pri 1970-01-01 00:00:00 +0000 |
2654 | +++ common-installs-config.pri 2016-09-19 11:35:53 +0000 |
2655 | @@ -0,0 +1,43 @@ |
2656 | +#----------------------------------------------------------------------------- |
2657 | +# Common installation configuration for all projects. |
2658 | +#----------------------------------------------------------------------------- |
2659 | + |
2660 | + |
2661 | +#----------------------------------------------------------------------------- |
2662 | +# default installation target for applications |
2663 | +#----------------------------------------------------------------------------- |
2664 | +contains(TEMPLATE, app) { |
2665 | + target.path = $${INSTALL_PREFIX}/bin |
2666 | + INSTALLS += target |
2667 | + message("====") |
2668 | + message("==== INSTALLS += target") |
2669 | +} |
2670 | + |
2671 | + |
2672 | +#----------------------------------------------------------------------------- |
2673 | +# default installation target for libraries |
2674 | +#----------------------------------------------------------------------------- |
2675 | +contains(TEMPLATE, lib) { |
2676 | + isEmpty(target.path) { |
2677 | + target.path = $${INSTALL_LIBDIR} |
2678 | + } |
2679 | + INSTALLS += target |
2680 | + message("====") |
2681 | + message("==== INSTALLS += target") |
2682 | +} |
2683 | + |
2684 | +#----------------------------------------------------------------------------- |
2685 | +# target for header files |
2686 | +#----------------------------------------------------------------------------- |
2687 | +!isEmpty(headers.files) { |
2688 | + headers.path = $${INSTALL_PREFIX}/include/$${TARGET} |
2689 | + INSTALLS += headers |
2690 | + message("====") |
2691 | + message("==== INSTALLS += headers") |
2692 | +} else { |
2693 | + message("====") |
2694 | + message("==== NOTE: Remember to add your API headers into `headers.files' for installation!") |
2695 | +} |
2696 | + |
2697 | + |
2698 | +# End of File |
2699 | |
2700 | === added file 'common-pkgconfig.pri' |
2701 | --- common-pkgconfig.pri 1970-01-01 00:00:00 +0000 |
2702 | +++ common-pkgconfig.pri 2016-09-19 11:35:53 +0000 |
2703 | @@ -0,0 +1,12 @@ |
2704 | +# Include this file after defining the pkgconfig.files variable |
2705 | + |
2706 | +!isEmpty(pkgconfig.files) { |
2707 | + QMAKE_SUBSTITUTES += $${pkgconfig.files}.in |
2708 | + pkgconfig.CONFIG = no_check_exist |
2709 | + pkgconfig.path = $${INSTALL_LIBDIR}/pkgconfig |
2710 | + QMAKE_EXTRA_TARGETS += pkgconfig |
2711 | + |
2712 | + INSTALLS += pkgconfig |
2713 | + |
2714 | + QMAKE_CLEAN += $${pkgconfig.files} |
2715 | +} |
2716 | |
2717 | === added file 'common-project-config.pri' |
2718 | --- common-project-config.pri 1970-01-01 00:00:00 +0000 |
2719 | +++ common-project-config.pri 2016-09-19 11:35:53 +0000 |
2720 | @@ -0,0 +1,36 @@ |
2721 | +#----------------------------------------------------------------------------- |
2722 | +# Common configuration for all projects. |
2723 | +#----------------------------------------------------------------------------- |
2724 | + |
2725 | +# we don't like warnings... |
2726 | +QMAKE_CXXFLAGS += -Werror |
2727 | +# Disable RTTI |
2728 | +QMAKE_CXXFLAGS += -fno-exceptions -fno-rtti |
2729 | + |
2730 | +CONFIG += c++11 |
2731 | + |
2732 | +!defined(TOP_SRC_DIR, var) { |
2733 | + TOP_SRC_DIR = $$PWD |
2734 | + TOP_BUILD_DIR = $${TOP_SRC_DIR}/$(BUILD_DIR) |
2735 | +} |
2736 | + |
2737 | +include(coverage.pri) |
2738 | + |
2739 | +#----------------------------------------------------------------------------- |
2740 | +# setup the installation prefix |
2741 | +#----------------------------------------------------------------------------- |
2742 | +INSTALL_PREFIX = /usr # default installation prefix |
2743 | + |
2744 | +# default prefix can be overriden by defining PREFIX when running qmake |
2745 | +isEmpty(PREFIX) { |
2746 | + message("====") |
2747 | + message("==== NOTE: To override the installation path run: `qmake PREFIX=/custom/path'") |
2748 | + message("==== (current installation path is `$${INSTALL_PREFIX}')") |
2749 | +} else { |
2750 | + INSTALL_PREFIX = $${PREFIX} |
2751 | + message("====") |
2752 | + message("==== install prefix set to `$${INSTALL_PREFIX}'") |
2753 | +} |
2754 | + |
2755 | +I18N_DOMAIN="account-polld" |
2756 | +PLUGIN_DATA_FILE="account-polld/plugin_data.json" |
2757 | |
2758 | === added file 'common-vars.pri' |
2759 | --- common-vars.pri 1970-01-01 00:00:00 +0000 |
2760 | +++ common-vars.pri 2016-09-19 11:35:53 +0000 |
2761 | @@ -0,0 +1,18 @@ |
2762 | +#----------------------------------------------------------------------------- |
2763 | +# Common variables for all projects. |
2764 | +#----------------------------------------------------------------------------- |
2765 | + |
2766 | + |
2767 | +#----------------------------------------------------------------------------- |
2768 | +# Project name (used e.g. in include file and doc install path). |
2769 | +# remember to update debian/* files if you changes this |
2770 | +#----------------------------------------------------------------------------- |
2771 | +PROJECT_NAME = account-polld |
2772 | + |
2773 | +#----------------------------------------------------------------------------- |
2774 | +# Project version |
2775 | +# remember to update debian/* files if you changes this |
2776 | +#----------------------------------------------------------------------------- |
2777 | +PROJECT_VERSION = 0.2 |
2778 | + |
2779 | +# End of File |
2780 | |
2781 | === added file 'coverage.pri' |
2782 | --- coverage.pri 1970-01-01 00:00:00 +0000 |
2783 | +++ coverage.pri 2016-09-19 11:35:53 +0000 |
2784 | @@ -0,0 +1,48 @@ |
2785 | +# Coverage |
2786 | +CONFIG(coverage) { |
2787 | + OBJECTS_DIR = |
2788 | + MOC_DIR = |
2789 | + |
2790 | + LIBS += -lgcov |
2791 | + QMAKE_CXXFLAGS += --coverage |
2792 | + QMAKE_LDFLAGS += --coverage |
2793 | + |
2794 | + QMAKE_EXTRA_TARGETS += coverage cov |
2795 | + QMAKE_EXTRA_TARGETS += clean-gcno clean-gcda coverage-html \ |
2796 | + generate-coverage-html clean-coverage-html coverage-gcovr \ |
2797 | + generate-gcovr generate-coverage-gcovr clean-coverage-gcovr |
2798 | + |
2799 | + clean-gcno.commands = \ |
2800 | + "@echo Removing old coverage instrumentation"; \ |
2801 | + "find -name '*.gcno' -print | xargs -r rm" |
2802 | + |
2803 | + clean-gcda.commands = \ |
2804 | + "@echo Removing old coverage results"; \ |
2805 | + "find -name '*.gcda' -print | xargs -r rm" |
2806 | + |
2807 | + coverage-html.depends = clean-gcda check generate-coverage-html |
2808 | + |
2809 | + generate-coverage-html.commands = \ |
2810 | + "@echo Collecting coverage data"; \ |
2811 | + "lcov --directory $${TOP_SRC_DIR} --capture --output-file coverage.info --no-checksum --compat-libtool"; \ |
2812 | + "lcov --extract coverage.info \"*/account-polld/*.cpp\" -o coverage.info"; \ |
2813 | + "lcov --remove coverage.info \"moc_*.cpp\" --remove coverage.info \"tests/*.cpp\" -o coverage.info"; \ |
2814 | + "LANG=C genhtml --prefix $${TOP_SRC_DIR} --output-directory coverage-html --title \"Code Coverage\" --legend --show-details coverage.info" |
2815 | + |
2816 | + clean-coverage-html.depends = clean-gcda |
2817 | + clean-coverage-html.commands = \ |
2818 | + "lcov --directory $${TOP_SRC_DIR} -z"; \ |
2819 | + "rm -rf coverage.info coverage-html" |
2820 | + |
2821 | + coverage-gcovr.depends = clean-gcda check generate-coverage-gcovr |
2822 | + |
2823 | + generate-coverage-gcovr.commands = \ |
2824 | + "@echo Generating coverage GCOVR report"; \ |
2825 | + "gcovr -x -r $${TOP_SRC_DIR} -o $${TOP_SRC_DIR}/coverage.xml -e \".*/moc_.*\" -e \"tests/.*\" -e \".*\\.h\"" |
2826 | + |
2827 | + clean-coverage-gcovr.depends = clean-gcda |
2828 | + clean-coverage-gcovr.commands = \ |
2829 | + "rm -rf $${TOP_SRC_DIR}/coverage.xml" |
2830 | + |
2831 | + QMAKE_CLEAN += *.gcda *.gcno coverage.info coverage.xml |
2832 | +} |
2833 | |
2834 | === modified file 'debian/control' |
2835 | --- debian/control 2015-09-09 08:50:05 +0000 |
2836 | +++ debian/control 2016-09-19 11:35:53 +0000 |
2837 | @@ -3,22 +3,19 @@ |
2838 | Priority: optional |
2839 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
2840 | Build-Depends: debhelper (>= 9), |
2841 | - dh-golang, |
2842 | dh-translations, |
2843 | - golang-go, |
2844 | - golang-go-dbus-dev, |
2845 | - golang-go-xdg-dev, |
2846 | - golang-gocheck-dev, |
2847 | - golang-ubuntu-push-dev, |
2848 | - libaccounts-glib-dev, |
2849 | - libclick-0.4-dev, |
2850 | - libsignon-glib-dev, |
2851 | + libaccounts-qt5-dev, |
2852 | + libqtdbusmock1-dev, |
2853 | + libqtdbustest1-dev, |
2854 | + libsignon-qt5-dev, |
2855 | + pkg-config, |
2856 | qt5-default, |
2857 | qtbase5-dev, |
2858 | - qtpim5-dev, |
2859 | + python3, |
2860 | + python3-xdg, |
2861 | Standards-Version: 3.9.5 |
2862 | Homepage: https://launchpad.net/account-polld |
2863 | -Vcs-Browser: http://bazaar.launchpad.net/~phablet-team/account-polld/trunk/files |
2864 | +Vcs-Browser: http://bazaar.launchpad.net/~online-accounts/account-polld/trunk/files |
2865 | Vcs-Bzr: lp:account-polld |
2866 | |
2867 | Package: account-polld |
2868 | @@ -26,11 +23,9 @@ |
2869 | Depends: accountsservice, |
2870 | ${misc:Depends}, |
2871 | ${shlibs:Depends}, |
2872 | + account-polld-plugins-go, |
2873 | Built-Using: ${misc:Built-Using} |
2874 | -Recommends: accountsservice, |
2875 | Description: Poll daemon for notifications though the Ubuntu Push Client |
2876 | - This component polls twitter and gmail for updates and |
2877 | - communicates with the postal service provided by the ubuntu push client |
2878 | - to expose notifications for the click webapps for the aforementioned |
2879 | - services. |
2880 | -X-Ubuntu-Use-Langpack: yes |
2881 | + This component polls remote services for updates and communicates with the |
2882 | + postal service provided by the ubuntu push client to expose notifications for |
2883 | + the click webapps for the aforementioned services. |
2884 | |
2885 | === modified file 'debian/rules' |
2886 | --- debian/rules 2014-10-01 13:02:24 +0000 |
2887 | +++ debian/rules 2016-09-19 11:35:53 +0000 |
2888 | @@ -2,38 +2,7 @@ |
2889 | # -*- makefile -*- |
2890 | |
2891 | export DH_OPTIONS |
2892 | -export DH_GOPKG := launchpad.net/account-polld |
2893 | -export DH_GOLANG_INSTALL_ALL := 1 |
2894 | + |
2895 | |
2896 | %: |
2897 | - dh $@ \ |
2898 | - --buildsystem=golang \ |
2899 | - --with=golang \ |
2900 | - --with=translations \ |
2901 | - --fail-missing |
2902 | - |
2903 | -override_dh_auto_install: |
2904 | - dh_auto_install -O--buildsystem=golang |
2905 | - rm \ |
2906 | - ${CURDIR}/debian/account-polld/usr/bin/account-watcher-test |
2907 | - # all our libs are private |
2908 | - rm -r \ |
2909 | - ${CURDIR}/debian/account-polld/usr/share/gocode |
2910 | - # setup online accounts service files |
2911 | - mkdir -p \ |
2912 | - ${CURDIR}/debian/account-polld/usr/share/applications |
2913 | - cp ${CURDIR}/data/account-polld.desktop \ |
2914 | - ${CURDIR}/debian/account-polld/usr/share/applications/ |
2915 | - # translations |
2916 | - appname=account-polld; \ |
2917 | - for pofile in po/*.po; do \ |
2918 | - pofilename="$${pofile##*/}"; \ |
2919 | - langcode="$${pofilename%.*}"; \ |
2920 | - localedir="debian/$$appname/usr/share/locale/$$langcode/LC_MESSAGES"; \ |
2921 | - mkdir -p $$localedir; \ |
2922 | - mofile="$$localedir/$$appname.mo"; \ |
2923 | - msgfmt -o $$mofile $$pofile; \ |
2924 | - done |
2925 | - |
2926 | -override_dh_strip: |
2927 | - echo "Skipping strip (LP: #1318027)" |
2928 | + dh $@ |
2929 | |
2930 | === removed directory 'gettext' |
2931 | === removed file 'gettext/LICENSE' |
2932 | --- gettext/LICENSE 2014-07-29 18:02:58 +0000 |
2933 | +++ gettext/LICENSE 1970-01-01 00:00:00 +0000 |
2934 | @@ -1,20 +0,0 @@ |
2935 | -Copyright (c) 2012-2013 José Carlos Nieto, http://xiam.menteslibres.org/ |
2936 | - |
2937 | -Permission is hereby granted, free of charge, to any person obtaining |
2938 | -a copy of this software and associated documentation files (the |
2939 | -"Software"), to deal in the Software without restriction, including |
2940 | -without limitation the rights to use, copy, modify, merge, publish, |
2941 | -distribute, sublicense, and/or sell copies of the Software, and to |
2942 | -permit persons to whom the Software is furnished to do so, subject to |
2943 | -the following conditions: |
2944 | - |
2945 | -The above copyright notice and this permission notice shall be |
2946 | -included in all copies or substantial portions of the Software. |
2947 | - |
2948 | -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
2949 | -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
2950 | -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
2951 | -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
2952 | -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
2953 | -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
2954 | -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
2955 | |
2956 | === removed file 'gettext/README.md' |
2957 | --- gettext/README.md 2014-07-29 18:02:58 +0000 |
2958 | +++ gettext/README.md 1970-01-01 00:00:00 +0000 |
2959 | @@ -1,94 +0,0 @@ |
2960 | -# gosexy/gettext |
2961 | - |
2962 | -Go bindings for [GNU gettext][1], an internationalization and localization |
2963 | -library for writing multilingual systems. |
2964 | - |
2965 | -## Requeriments |
2966 | - |
2967 | -The GNU C library. If you're using GNU/Linux, FreeBSD or OSX you should already |
2968 | -have it. |
2969 | - |
2970 | -## Installation |
2971 | - |
2972 | -Use `go get` to download and install the binding: |
2973 | - |
2974 | -```sh |
2975 | -go get github.com/gosexy/gettext |
2976 | -``` |
2977 | - |
2978 | -## Usage |
2979 | - |
2980 | -```go |
2981 | -package main |
2982 | - |
2983 | -import ( |
2984 | - "github.com/gosexy/gettext" |
2985 | - "fmt" |
2986 | - "os" |
2987 | -) |
2988 | - |
2989 | -func main() { |
2990 | - gettext.BindTextdomain("example", ".") |
2991 | - gettext.Textdomain("example") |
2992 | - |
2993 | - os.Setenv("LANGUAGE", "es_MX.utf8") |
2994 | - |
2995 | - gettext.SetLocale(gettext.LC_ALL, "") |
2996 | - |
2997 | - fmt.Println(gettext.Gettext("Hello, world!")) |
2998 | -} |
2999 | -``` |
3000 | - |
3001 | -You can use `os.Setenv` to set the `LANGUAGE` environment variable or set it |
3002 | -on a terminal: |
3003 | - |
3004 | -```sh |
3005 | -export LANGUAGE="es_MX.utf8" |
3006 | -./gettext-program |
3007 | -``` |
3008 | - |
3009 | -Note that `xgettext` does not officially support Go syntax yet, however, you |
3010 | -can generate a valid `.pot` file by forcing `xgettest` to use the C++ |
3011 | -syntax: |
3012 | - |
3013 | -```sh |
3014 | -xgettext -d example -s gettext_test.go -o example.pot -L c++ -i \ |
3015 | ---keyword=NGettext:1,2 --keyword=Gettext |
3016 | -``` |
3017 | - |
3018 | -This will generate a `example.pot` file. |
3019 | - |
3020 | -After translating the `.pot` file, you must generate `.po` and `.mo` files and |
3021 | -remember to set the UTF-8 charset. |
3022 | - |
3023 | -```sh |
3024 | -msginit -l es_MX -o example.po -i example.pot |
3025 | -msgfmt -c -v -o example.mo example.po |
3026 | -``` |
3027 | - |
3028 | -Finally, move the `.mo` file to an appropriate location. |
3029 | - |
3030 | -```sh |
3031 | -mv example.mo examples/es_MX.utf8/LC_MESSAGES/example.mo |
3032 | -``` |
3033 | - |
3034 | -## Documentation |
3035 | - |
3036 | -You can read `gosexy/gettext` documentation from a terminal |
3037 | - |
3038 | -```sh |
3039 | -go doc github.com/gosexy/gettext |
3040 | -``` |
3041 | - |
3042 | -Or you can [browse it](http://godoc.org/github.com/gosexy/gettext) online. |
3043 | - |
3044 | -The original gettext documentation could be very useful as well: |
3045 | - |
3046 | -```sh |
3047 | -man 3 gettext |
3048 | -``` |
3049 | - |
3050 | -Here's another [good tutorial][2] on using gettext. |
3051 | - |
3052 | -[1]: http://www.gnu.org/software/gettext/ |
3053 | -[2]: http://oriya.sarovar.org/docs/gettext_single.html |
3054 | |
3055 | === removed file 'gettext/gettext.go' |
3056 | --- gettext/gettext.go 2014-07-29 18:02:58 +0000 |
3057 | +++ gettext/gettext.go 1970-01-01 00:00:00 +0000 |
3058 | @@ -1,207 +0,0 @@ |
3059 | -/* |
3060 | - Copyright (c) 2012 José Carlos Nieto, http://xiam.menteslibres.org/ |
3061 | - |
3062 | - Permission is hereby granted, free of charge, to any person obtaining |
3063 | - a copy of this software and associated documentation files (the |
3064 | - "Software"), to deal in the Software without restriction, including |
3065 | - without limitation the rights to use, copy, modify, merge, publish, |
3066 | - distribute, sublicense, and/or sell copies of the Software, and to |
3067 | - permit persons to whom the Software is furnished to do so, subject to |
3068 | - the following conditions: |
3069 | - |
3070 | - The above copyright notice and this permission notice shall be |
3071 | - included in all copies or substantial portions of the Software. |
3072 | - |
3073 | - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
3074 | - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
3075 | - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
3076 | - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
3077 | - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
3078 | - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
3079 | - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
3080 | -*/ |
3081 | - |
3082 | -package gettext |
3083 | - |
3084 | -/* |
3085 | - |
3086 | -#include <libintl.h> |
3087 | -#include <locale.h> |
3088 | -#include <stdlib.h> |
3089 | -*/ |
3090 | -import "C" |
3091 | - |
3092 | -import ( |
3093 | - "fmt" |
3094 | - "strings" |
3095 | - "unsafe" |
3096 | -) |
3097 | - |
3098 | -var ( |
3099 | - // For all of the locale. |
3100 | - LC_ALL = uint(C.LC_ALL) |
3101 | - |
3102 | - // For regular expression matching (it determines the meaning of range |
3103 | - // expressions and equivalence classes) and string collation. |
3104 | - LC_COLATE = uint(C.LC_ALL) |
3105 | - |
3106 | - // For regular expression matching, character classification, conversion, |
3107 | - // case-sensitive comparison, and wide character functions. |
3108 | - LC_CTYPE = uint(C.LC_CTYPE) |
3109 | - |
3110 | - // For localizable natural-language messages. |
3111 | - LC_MESSAGES = uint(C.LC_MESSAGES) |
3112 | - |
3113 | - // For monetary formatting. |
3114 | - LC_MONETARY = uint(C.LC_MONETARY) |
3115 | - |
3116 | - // For number formatting (such as the decimal point and the thousands |
3117 | - // separator). |
3118 | - LC_NUMERIC = uint(C.LC_NUMERIC) |
3119 | - |
3120 | - // For time and date formatting. |
3121 | - LC_TIME = uint(C.LC_TIME) |
3122 | -) |
3123 | - |
3124 | -// Sets or queries the program's current locale. |
3125 | -func SetLocale(category uint, locale string) string { |
3126 | - clocale := C.CString(locale) |
3127 | - |
3128 | - res := C.GoString(C.setlocale(C.int(category), clocale)) |
3129 | - |
3130 | - C.free(unsafe.Pointer(clocale)) |
3131 | - return res |
3132 | -} |
3133 | - |
3134 | -// Sets directory containing message catalogs. |
3135 | -func BindTextdomain(domainname string, dirname string) string { |
3136 | - cdirname := C.CString(dirname) |
3137 | - cdomainname := C.CString(domainname) |
3138 | - |
3139 | - res := C.GoString(C.bindtextdomain(cdomainname, cdirname)) |
3140 | - |
3141 | - C.free(unsafe.Pointer(cdirname)) |
3142 | - C.free(unsafe.Pointer(cdomainname)) |
3143 | - return res |
3144 | -} |
3145 | - |
3146 | -// Sets the output codeset for message catalogs for domain domainname. |
3147 | -func BindTextdomainCodeset(domainname string, codeset string) string { |
3148 | - cdomainname := C.CString(domainname) |
3149 | - ccodeset := C.CString(codeset) |
3150 | - |
3151 | - res := C.GoString(C.bind_textdomain_codeset(cdomainname, ccodeset)) |
3152 | - |
3153 | - C.free(unsafe.Pointer(cdomainname)) |
3154 | - C.free(unsafe.Pointer(ccodeset)) |
3155 | - return res |
3156 | -} |
3157 | - |
3158 | -// Sets or retrieves the current message domain. |
3159 | -func Textdomain(domainname string) string { |
3160 | - cdomainname := C.CString(domainname) |
3161 | - |
3162 | - res := C.GoString(C.textdomain(cdomainname)) |
3163 | - |
3164 | - C.free(unsafe.Pointer(cdomainname)) |
3165 | - return res |
3166 | -} |
3167 | - |
3168 | -// Attempt to translate a text string into the user's native language, by |
3169 | -// looking up the translation in a message catalog. |
3170 | -func Gettext(msgid string) string { |
3171 | - cmsgid := C.CString(msgid) |
3172 | - |
3173 | - res := C.GoString(C.gettext(cmsgid)) |
3174 | - |
3175 | - C.free(unsafe.Pointer(cmsgid)) |
3176 | - return res |
3177 | -} |
3178 | - |
3179 | -// Like Gettext(), but looking up the message in the specified domain. |
3180 | -func DGettext(domain string, msgid string) string { |
3181 | - cdomain := C.CString(domain) |
3182 | - cmsgid := C.CString(msgid) |
3183 | - |
3184 | - res := C.GoString(C.dgettext(cdomain, cmsgid)) |
3185 | - |
3186 | - C.free(unsafe.Pointer(cdomain)) |
3187 | - C.free(unsafe.Pointer(cmsgid)) |
3188 | - return res |
3189 | -} |
3190 | - |
3191 | -// Like Gettext(), but looking up the message in the specified domain and |
3192 | -// category. |
3193 | -func DCGettext(domain string, msgid string, category uint) string { |
3194 | - cdomain := C.CString(domain) |
3195 | - cmsgid := C.CString(msgid) |
3196 | - |
3197 | - res := C.GoString(C.dcgettext(cdomain, cmsgid, C.int(category))) |
3198 | - |
3199 | - C.free(unsafe.Pointer(cdomain)) |
3200 | - C.free(unsafe.Pointer(cmsgid)) |
3201 | - return res |
3202 | -} |
3203 | - |
3204 | -// Attempt to translate a text string into the user's native language, by |
3205 | -// looking up the appropriate plural form of the translation in a message |
3206 | -// catalog. |
3207 | -func NGettext(msgid string, msgid_plural string, n uint64) string { |
3208 | - cmsgid := C.CString(msgid) |
3209 | - cmsgid_plural := C.CString(msgid_plural) |
3210 | - |
3211 | - res := C.GoString(C.ngettext(cmsgid, cmsgid_plural, C.ulong(n))) |
3212 | - |
3213 | - C.free(unsafe.Pointer(cmsgid)) |
3214 | - C.free(unsafe.Pointer(cmsgid_plural)) |
3215 | - |
3216 | - return res |
3217 | -} |
3218 | - |
3219 | -// Like fmt.Sprintf() but without %!(EXTRA) errors. |
3220 | -func Sprintf(format string, a ...interface{}) string { |
3221 | - expects := strings.Count(format, "%") - strings.Count(format, "%%") |
3222 | - |
3223 | - if expects > 0 { |
3224 | - arguments := make([]interface{}, expects) |
3225 | - for i := 0; i < expects; i++ { |
3226 | - if len(a) > i { |
3227 | - arguments[i] = a[i] |
3228 | - } |
3229 | - } |
3230 | - return fmt.Sprintf(format, arguments...) |
3231 | - } |
3232 | - |
3233 | - return format |
3234 | -} |
3235 | - |
3236 | -// Like NGettext(), but looking up the message in the specified domain. |
3237 | -func DNGettext(domainname string, msgid string, msgid_plural string, n uint64) string { |
3238 | - cdomainname := C.CString(domainname) |
3239 | - cmsgid := C.CString(msgid) |
3240 | - cmsgid_plural := C.CString(msgid_plural) |
3241 | - |
3242 | - res := C.GoString(C.dngettext(cdomainname, cmsgid, cmsgid_plural, C.ulong(n))) |
3243 | - |
3244 | - C.free(unsafe.Pointer(cdomainname)) |
3245 | - C.free(unsafe.Pointer(cmsgid)) |
3246 | - C.free(unsafe.Pointer(cmsgid_plural)) |
3247 | - |
3248 | - return res |
3249 | -} |
3250 | - |
3251 | -// Like NGettext(), but looking up the message in the specified domain and |
3252 | -// category. |
3253 | -func DCNGettext(domainname string, msgid string, msgid_plural string, n uint64, category uint) string { |
3254 | - cdomainname := C.CString(domainname) |
3255 | - cmsgid := C.CString(msgid) |
3256 | - cmsgid_plural := C.CString(msgid_plural) |
3257 | - |
3258 | - res := C.GoString(C.dcngettext(cdomainname, cmsgid, cmsgid_plural, C.ulong(n), C.int(category))) |
3259 | - |
3260 | - C.free(unsafe.Pointer(cdomainname)) |
3261 | - C.free(unsafe.Pointer(cmsgid)) |
3262 | - C.free(unsafe.Pointer(cmsgid_plural)) |
3263 | - |
3264 | - return res |
3265 | -} |
3266 | |
3267 | === removed directory 'plugins' |
3268 | === removed directory 'plugins/caldav' |
3269 | === removed file 'plugins/caldav/api.go' |
3270 | --- plugins/caldav/api.go 2016-07-12 19:40:07 +0000 |
3271 | +++ plugins/caldav/api.go 1970-01-01 00:00:00 +0000 |
3272 | @@ -1,54 +0,0 @@ |
3273 | -/* |
3274 | - Copyright 2014 Canonical Ltd. |
3275 | - |
3276 | - This program is free software: you can redistribute it and/or modify it |
3277 | - under the terms of the GNU General Public License version 3, as published |
3278 | - by the Free Software Foundation. |
3279 | - |
3280 | - This program is distributed in the hope that it will be useful, but |
3281 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
3282 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3283 | - PURPOSE. See the GNU General Public License for more details. |
3284 | - |
3285 | - You should have received a copy of the GNU General Public License along |
3286 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
3287 | -*/ |
3288 | - |
3289 | -package caldav |
3290 | - |
3291 | -import ( |
3292 | - "fmt" |
3293 | -) |
3294 | - |
3295 | -// eventList holds a response to call to Calendar.events: list |
3296 | -// defined in https://developers.google.com/google-apps/calendar/v3/reference/events/list#response |
3297 | -type eventList struct { |
3298 | - // Messages holds a list of message. |
3299 | - Events []event `json:"items"` |
3300 | -} |
3301 | - |
3302 | -// event holds the event data response for a Calendar.event. |
3303 | -// The full definition of a message is defined in |
3304 | -// https://developers.google.com/google-apps/calendar/v3/reference/events#resource-representations |
3305 | -type event struct { |
3306 | - // Id is the immutable ID of the message. |
3307 | - Etag string `json:"etag"` |
3308 | - // ThreadId is the ID of the thread the message belongs to. |
3309 | - Summary string `json:"summary"` |
3310 | -} |
3311 | - |
3312 | -func (e event) String() string { |
3313 | - return fmt.Sprintf("Id: %s, snippet: '%s'\n", e.Etag, e.Summary) |
3314 | -} |
3315 | - |
3316 | -type errorResp struct { |
3317 | - Err struct { |
3318 | - Code uint64 `json:"code"` |
3319 | - Message string `json:"message"` |
3320 | - Errors []struct { |
3321 | - Domain string `json:"domain"` |
3322 | - Reason string `json:"reason"` |
3323 | - Message string `json:"message"` |
3324 | - } `json:"errors"` |
3325 | - } `json:"error"` |
3326 | -} |
3327 | |
3328 | === removed file 'plugins/caldav/caldav.go' |
3329 | --- plugins/caldav/caldav.go 2016-08-03 11:59:28 +0000 |
3330 | +++ plugins/caldav/caldav.go 1970-01-01 00:00:00 +0000 |
3331 | @@ -1,201 +0,0 @@ |
3332 | -/* |
3333 | - Copyright 2016 Canonical Ltd. |
3334 | - |
3335 | - This program is free software: you can redistribute it and/or modify it |
3336 | - under the terms of the GNU General Public License version 3, as published |
3337 | - by the Free Software Foundation. |
3338 | - |
3339 | - This program is distributed in the hope that it will be useful, but |
3340 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
3341 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3342 | - PURPOSE. See the GNU General Public License for more details. |
3343 | - |
3344 | - You should have received a copy of the GNU General Public License along |
3345 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
3346 | -*/ |
3347 | - |
3348 | -package caldav |
3349 | - |
3350 | -import ( |
3351 | - "bytes" |
3352 | - "fmt" |
3353 | - "io/ioutil" |
3354 | - "log" |
3355 | - "net/http" |
3356 | - "net/url" |
3357 | - "os" |
3358 | - "strings" |
3359 | - "time" |
3360 | - |
3361 | - "launchpad.net/account-polld/accounts" |
3362 | - "launchpad.net/account-polld/plugins" |
3363 | - "launchpad.net/account-polld/syncmonitor" |
3364 | -) |
3365 | - |
3366 | -const ( |
3367 | - APP_ID = "com.ubuntu.calendar_calendar" |
3368 | - pluginName = "caldav" |
3369 | -) |
3370 | - |
3371 | -type CalDavPlugin struct { |
3372 | - accountId uint |
3373 | -} |
3374 | - |
3375 | -func New(accountId uint) *CalDavPlugin { |
3376 | - return &CalDavPlugin{accountId: accountId} |
3377 | -} |
3378 | - |
3379 | -func (p *CalDavPlugin) ApplicationId() plugins.ApplicationId { |
3380 | - return plugins.ApplicationId(APP_ID) |
3381 | -} |
3382 | - |
3383 | -func (p *CalDavPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) { |
3384 | - // This envvar check is to ease testing. |
3385 | - if token := os.Getenv("ACCOUNT_POLLD_TOKEN_CALDAV"); token != "" { |
3386 | - log.Print("Using token from: ACCOUNT_POLLD_TOKEN_CALDAV env var") |
3387 | - authData.AccessToken = token |
3388 | - } |
3389 | - |
3390 | - log.Print("Check calendar changes for account:", p.accountId) |
3391 | - |
3392 | - syncMonitor := syncmonitor.NewSyncMonitor() |
3393 | - if syncMonitor == nil { |
3394 | - log.Print("Sync monitor not available yet.") |
3395 | - return nil, nil |
3396 | - } |
3397 | - |
3398 | - state, err := syncMonitor.State() |
3399 | - if err != nil { |
3400 | - log.Print("Fail to retrieve sync monitor state ", err) |
3401 | - return nil, nil |
3402 | - } |
3403 | - if state != "idle" { |
3404 | - log.Print("Sync monitor is not on 'idle' state, try later!") |
3405 | - return nil, nil |
3406 | - } |
3407 | - |
3408 | - calendars, err := syncMonitor.ListCalendarsByAccount(p.accountId) |
3409 | - if err != nil { |
3410 | - log.Print("Calendar plugin ", p.accountId, ": cannot load calendars: ", err) |
3411 | - return nil, nil |
3412 | - } |
3413 | - |
3414 | - var calendarsToSync []string |
3415 | - log.Print("Number of calendars for account:", p.accountId, " size:", len(calendars)) |
3416 | - |
3417 | - for id, calendar := range calendars { |
3418 | - lastSyncDate, err := syncMonitor.LastSyncDate(p.accountId, id) |
3419 | - if err != nil { |
3420 | - log.Print("\tcalendar: ", id, ", cannot load previous sync date: ", err, ". Try next time.") |
3421 | - continue |
3422 | - } else { |
3423 | - log.Print("\tcalendar: ", id, " Url: ", calendar, " last sync date: ", lastSyncDate) |
3424 | - } |
3425 | - |
3426 | - var needSync bool |
3427 | - needSync = (len(lastSyncDate) == 0) |
3428 | - |
3429 | - if !needSync { |
3430 | - resp, err := p.requestChanges(authData, calendar, lastSyncDate) |
3431 | - if err != nil { |
3432 | - log.Print("\tERROR: Fail to query for changes: ", err) |
3433 | - continue |
3434 | - } |
3435 | - |
3436 | - needSync, err = p.containEvents(resp) |
3437 | - if err != nil { |
3438 | - log.Print("\tERROR: Fail to parse changes: ", err) |
3439 | - if err == plugins.ErrTokenExpired { |
3440 | - log.Print("\t\tAbort poll") |
3441 | - return nil, err |
3442 | - } else { |
3443 | - continue |
3444 | - } |
3445 | - } |
3446 | - } |
3447 | - |
3448 | - if needSync { |
3449 | - log.Print("\tCalendar needs sync: ", id) |
3450 | - calendarsToSync = append(calendarsToSync, id) |
3451 | - } else { |
3452 | - log.Print("\tFound no calendar updates for account: ", p.accountId, " calendar: ", id) |
3453 | - } |
3454 | - } |
3455 | - |
3456 | - if len(calendarsToSync) > 0 { |
3457 | - log.Print("Request account sync") |
3458 | - err = syncMonitor.SyncAccount(p.accountId, calendarsToSync) |
3459 | - if err != nil { |
3460 | - log.Print("ERROR: Fail to start account sync ", p.accountId, " message: ", err) |
3461 | - } |
3462 | - } |
3463 | - |
3464 | - return nil, nil |
3465 | -} |
3466 | - |
3467 | -func (p *CalDavPlugin) containEvents(resp *http.Response) (bool, error) { |
3468 | - defer resp.Body.Close() |
3469 | - log.Print("RESPONSE CODE ----:", resp.StatusCode) |
3470 | - |
3471 | - if resp.StatusCode != 207 { |
3472 | - var errResp errorResp |
3473 | - log.Print("Invalid response:", errResp.Err.Code) |
3474 | - return false, nil |
3475 | - } else { |
3476 | - data, err := ioutil.ReadAll(resp.Body) |
3477 | - if err != nil { |
3478 | - return false, err |
3479 | - } |
3480 | - fmt.Printf("DATA: %s", data) |
3481 | - return strings.Contains(string(data), "BEGIN:VEVENT"), nil |
3482 | - } |
3483 | - |
3484 | - return false, nil |
3485 | -} |
3486 | - |
3487 | -func (p *CalDavPlugin) requestChanges(authData *accounts.AuthData, calendar string, lastSyncDate string) (*http.Response, error) { |
3488 | - u, err := url.Parse(calendar) |
3489 | - if err != nil { |
3490 | - return nil, err |
3491 | - } |
3492 | - startDate, err := time.Parse(time.RFC3339, lastSyncDate) |
3493 | - if err != nil { |
3494 | - log.Print("Fail to parse date: ", lastSyncDate) |
3495 | - return nil, err |
3496 | - } |
3497 | - |
3498 | - // Start date will be one minute before last sync |
3499 | - startDate = startDate.Add(time.Duration(-1) * time.Minute) |
3500 | - |
3501 | - // End Date will be one year in the future from now |
3502 | - endDate := time.Now().AddDate(1, 0, 0).UTC() |
3503 | - |
3504 | - log.Print("Calendar Url:", calendar) |
3505 | - |
3506 | - query := "<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">\n" |
3507 | - query += "<d:prop>\n" |
3508 | - query += "<d:getetag />\n" |
3509 | - query += "<c:calendar-data />\n" |
3510 | - query += "</d:prop>\n" |
3511 | - query += "<c:filter>\n" |
3512 | - query += "<c:comp-filter name=\"VCALENDAR\">\n" |
3513 | - query += "<c:comp-filter name=\"VEVENT\">\n" |
3514 | - query += "<c:prop-filter name=\"LAST-MODIFIED\">\n" |
3515 | - query += "<c:time-range start=\"" + startDate.Format("20060102T150405Z") + "\" end=\"" + endDate.Format("20060102T150405Z") + "\"/>\n" |
3516 | - query += "</c:prop-filter>\n" |
3517 | - query += "</c:comp-filter>\n" |
3518 | - query += "</c:comp-filter>\n" |
3519 | - query += "</c:filter>\n" |
3520 | - query += "</c:calendar-query>\n" |
3521 | - log.Print("Query: ", query) |
3522 | - req, err := http.NewRequest("REPORT", u.String(), bytes.NewBufferString(query)) |
3523 | - if err != nil { |
3524 | - return nil, err |
3525 | - } |
3526 | - req.Header.Set("Depth", "1") |
3527 | - req.Header.Set("Prefer", "return-minimal") |
3528 | - req.Header.Set("Content-Type", "application/xml; charset=utf-8") |
3529 | - req.SetBasicAuth(authData.UserName, authData.Secret) |
3530 | - |
3531 | - return http.DefaultClient.Do(req) |
3532 | -} |
3533 | |
3534 | === removed directory 'plugins/dekko' |
3535 | === removed file 'plugins/dekko/api.go' |
3536 | --- plugins/dekko/api.go 2016-06-15 14:41:33 +0000 |
3537 | +++ plugins/dekko/api.go 1970-01-01 00:00:00 +0000 |
3538 | @@ -1,127 +0,0 @@ |
3539 | -/* |
3540 | - Copyright 2014 Canonical Ltd. |
3541 | - |
3542 | - This program is free software: you can redistribute it and/or modify it |
3543 | - under the terms of the GNU General Public License version 3, as published |
3544 | - by the Free Software Foundation. |
3545 | - |
3546 | - This program is distributed in the hope that it will be useful, but |
3547 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
3548 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3549 | - PURPOSE. See the GNU General Public License for more details. |
3550 | - |
3551 | - You should have received a copy of the GNU General Public License along |
3552 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
3553 | -*/ |
3554 | - |
3555 | -package dekko |
3556 | - |
3557 | -import ( |
3558 | - "fmt" |
3559 | - "time" |
3560 | - |
3561 | - "launchpad.net/account-polld/plugins" |
3562 | -) |
3563 | - |
3564 | -const gmailTime = "Mon, 2 Jan 2006 15:04:05 -0700" |
3565 | - |
3566 | -type pushes map[string]*plugins.PushMessage |
3567 | -type headers map[string]string |
3568 | - |
3569 | -// messageList holds a response to call to Users.messages: list |
3570 | -// defined in https://developers.google.com/gmail/api/v1/reference/users/messages/list |
3571 | -type messageList struct { |
3572 | - // Messages holds a list of message. |
3573 | - Messages []message `json:"messages"` |
3574 | - // NextPageToken is used to retrieve the next page of results in the list. |
3575 | - NextPageToken string `json:"nextPageToken"` |
3576 | - // ResultSizeEstimage is the estimated total number of results. |
3577 | - ResultSizeEstimage uint64 `json:"resultSizeEstimate"` |
3578 | -} |
3579 | - |
3580 | -// message holds a partial response for a Users.messages. |
3581 | -// The full definition of a message is defined in |
3582 | -// https://developers.google.com/gmail/api/v1/reference/users/messages#resource |
3583 | -type message struct { |
3584 | - // Id is the immutable ID of the message. |
3585 | - Id string `json:"id"` |
3586 | - // ThreadId is the ID of the thread the message belongs to. |
3587 | - ThreadId string `json:"threadId"` |
3588 | - // HistoryId is the ID of the last history record that modified |
3589 | - // this message. |
3590 | - HistoryId string `json:"historyId"` |
3591 | - // Snippet is a short part of the message text. This text is |
3592 | - // used for the push message summary. |
3593 | - Snippet string `json:"snippet"` |
3594 | - // Payload represents the message payload. |
3595 | - Payload payload `json:"payload"` |
3596 | -} |
3597 | - |
3598 | -func (m message) String() string { |
3599 | - return fmt.Sprintf("Id: %d, snippet: '%s'\n", m.Id, m.Snippet[:10]) |
3600 | -} |
3601 | - |
3602 | -// ById implements sort.Interface for []message based on |
3603 | -// the Id field. |
3604 | -type byId []message |
3605 | - |
3606 | -func (m byId) Len() int { return len(m) } |
3607 | -func (m byId) Swap(i, j int) { m[i], m[j] = m[j], m[i] } |
3608 | -func (m byId) Less(i, j int) bool { return m[i].Id < m[j].Id } |
3609 | - |
3610 | -// payload represents the message payload. |
3611 | -type payload struct { |
3612 | - Headers []messageHeader `json:"headers"` |
3613 | -} |
3614 | - |
3615 | -func (p *payload) mapHeaders() headers { |
3616 | - headers := make(map[string]string) |
3617 | - for _, hdr := range p.Headers { |
3618 | - headers[hdr.Name] = hdr.Value |
3619 | - } |
3620 | - return headers |
3621 | -} |
3622 | - |
3623 | -func (hdr headers) getTimestamp() time.Time { |
3624 | - timestamp, ok := hdr[hdrDATE] |
3625 | - if !ok { |
3626 | - return time.Now() |
3627 | - } |
3628 | - |
3629 | - if t, err := time.Parse(gmailTime, timestamp); err == nil { |
3630 | - return t |
3631 | - } |
3632 | - return time.Now() |
3633 | -} |
3634 | - |
3635 | -func (hdr headers) getEpoch() int64 { |
3636 | - return hdr.getTimestamp().Unix() |
3637 | -} |
3638 | - |
3639 | -// messageHeader represents the message headers. |
3640 | -type messageHeader struct { |
3641 | - Name string `json:"name"` |
3642 | - Value string `json:"value"` |
3643 | -} |
3644 | - |
3645 | -type errorResp struct { |
3646 | - Err struct { |
3647 | - Code uint64 `json:"code"` |
3648 | - Message string `json:"message"` |
3649 | - Errors []struct { |
3650 | - Domain string `json:"domain"` |
3651 | - Reason string `json:"reason"` |
3652 | - Message string `json:"message"` |
3653 | - } `json:"errors"` |
3654 | - } `json:"error"` |
3655 | -} |
3656 | - |
3657 | -func (err *errorResp) Error() string { |
3658 | - return fmt.Sprint("backend response:", err.Err.Message) |
3659 | -} |
3660 | - |
3661 | -const ( |
3662 | - hdrDATE = "Date" |
3663 | - hdrSUBJECT = "Subject" |
3664 | - hdrFROM = "From" |
3665 | -) |
3666 | |
3667 | === removed file 'plugins/dekko/dekko.go' |
3668 | --- plugins/dekko/dekko.go 2016-07-22 09:46:36 +0000 |
3669 | +++ plugins/dekko/dekko.go 1970-01-01 00:00:00 +0000 |
3670 | @@ -1,346 +0,0 @@ |
3671 | -/* |
3672 | - Copyright 2014 Canonical Ltd. |
3673 | - |
3674 | - This program is free software: you can redistribute it and/or modify it |
3675 | - under the terms of the GNU General Public License version 3, as published |
3676 | - by the Free Software Foundation. |
3677 | - |
3678 | - This program is distributed in the hope that it will be useful, but |
3679 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
3680 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
3681 | - PURPOSE. See the GNU General Public License for more details. |
3682 | - |
3683 | - You should have received a copy of the GNU General Public License along |
3684 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
3685 | -*/ |
3686 | - |
3687 | -package dekko |
3688 | - |
3689 | -import ( |
3690 | - "encoding/json" |
3691 | - "fmt" |
3692 | - "net/http" |
3693 | - "net/mail" |
3694 | - "net/url" |
3695 | - "os" |
3696 | - "regexp" |
3697 | - "sort" |
3698 | - "strings" |
3699 | - "time" |
3700 | - |
3701 | - "log" |
3702 | - |
3703 | - "launchpad.net/account-polld/accounts" |
3704 | - "launchpad.net/account-polld/gettext" |
3705 | - "launchpad.net/account-polld/plugins" |
3706 | - "launchpad.net/account-polld/qtcontact" |
3707 | -) |
3708 | - |
3709 | -const ( |
3710 | - APP_ID = "dekko.dekkoproject_dekko" |
3711 | - dekkoDispatchUrl = "dekko://notify/%d/%s/%s" |
3712 | - // If there's more than 10 emails in one batch, we don't show 10 notification |
3713 | - // bubbles, but instead show one summary. We always show all notifications in the |
3714 | - // indicator. |
3715 | - individualNotificationsLimit = 10 |
3716 | - pluginName = "dekko" |
3717 | -) |
3718 | - |
3719 | -type reportedIdMap map[string]time.Time |
3720 | - |
3721 | -var baseUrl, _ = url.Parse("https://www.googleapis.com/gmail/v1/users/me/") |
3722 | - |
3723 | -// timeDelta defines how old messages can be to be reported. |
3724 | -var timeDelta = time.Duration(time.Hour * 24) |
3725 | - |
3726 | -// trackDelta defines how old messages can be before removed from tracking |
3727 | -var trackDelta = time.Duration(time.Hour * 24 * 7) |
3728 | - |
3729 | -// relativeTimeDelta is the same as timeDelta |
3730 | -var relativeTimeDelta string = "1d" |
3731 | - |
3732 | -// regexp for identifying non-ascii characters |
3733 | -var nonAsciiChars, _ = regexp.Compile("[^\x00-\x7F]") |
3734 | - |
3735 | -type GmailPlugin struct { |
3736 | - // reportedIds holds the messages that have already been notified. This |
3737 | - // approach is taken against timestamps as it avoids needing to call |
3738 | - // get on the message. |
3739 | - reportedIds reportedIdMap |
3740 | - accountId uint |
3741 | -} |
3742 | - |
3743 | -func idsFromPersist(accountId uint) (ids reportedIdMap, err error) { |
3744 | - err = plugins.FromPersist(pluginName, accountId, &ids) |
3745 | - if err != nil { |
3746 | - return nil, err |
3747 | - } |
3748 | - // discard old ids |
3749 | - timestamp := time.Now() |
3750 | - for k, v := range ids { |
3751 | - delta := timestamp.Sub(v) |
3752 | - if delta > trackDelta { |
3753 | - log.Print("gmail plugin ", accountId, ": deleting ", k, " as ", delta, " is greater than ", trackDelta) |
3754 | - delete(ids, k) |
3755 | - } |
3756 | - } |
3757 | - return ids, nil |
3758 | -} |
3759 | - |
3760 | -func (ids reportedIdMap) persist(accountId uint) (err error) { |
3761 | - err = plugins.Persist(pluginName, accountId, ids) |
3762 | - if err != nil { |
3763 | - log.Print("gmail plugin ", accountId, ": failed to save state: ", err) |
3764 | - } |
3765 | - return nil |
3766 | -} |
3767 | - |
3768 | -func New(accountId uint) *GmailPlugin { |
3769 | - reportedIds, err := idsFromPersist(accountId) |
3770 | - if err != nil { |
3771 | - log.Print("gmail plugin ", accountId, ": cannot load previous state from storage: ", err) |
3772 | - } else { |
3773 | - log.Print("gmail plugin ", accountId, ": last state loaded from storage") |
3774 | - } |
3775 | - return &GmailPlugin{reportedIds: reportedIds, accountId: accountId} |
3776 | -} |
3777 | - |
3778 | -func (p *GmailPlugin) ApplicationId() plugins.ApplicationId { |
3779 | - return plugins.ApplicationId(APP_ID) |
3780 | -} |
3781 | - |
3782 | -func (p *GmailPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) { |
3783 | - // This envvar check is to ease testing. |
3784 | - if token := os.Getenv("ACCOUNT_POLLD_TOKEN_GMAIL"); token != "" { |
3785 | - authData.AccessToken = token |
3786 | - } |
3787 | - |
3788 | - resp, err := p.requestMessageList(authData.AccessToken) |
3789 | - if err != nil { |
3790 | - return nil, err |
3791 | - } |
3792 | - messages, err := p.parseMessageListResponse(resp) |
3793 | - if err != nil { |
3794 | - return nil, err |
3795 | - } |
3796 | - |
3797 | - // TODO use the batching API defined in https://developers.google.com/gmail/api/guides/batch |
3798 | - for i := range messages { |
3799 | - resp, err := p.requestMessage(messages[i].Id, authData.AccessToken) |
3800 | - if err != nil { |
3801 | - return nil, err |
3802 | - } |
3803 | - messages[i], err = p.parseMessageResponse(resp) |
3804 | - if err != nil { |
3805 | - return nil, err |
3806 | - } |
3807 | - } |
3808 | - notif, err := p.createNotifications(messages) |
3809 | - if err != nil { |
3810 | - return nil, err |
3811 | - } |
3812 | - return []*plugins.PushMessageBatch{ |
3813 | - &plugins.PushMessageBatch{ |
3814 | - Messages: notif, |
3815 | - Limit: individualNotificationsLimit, |
3816 | - OverflowHandler: p.handleOverflow, |
3817 | - Tag: "dekko", |
3818 | - }}, nil |
3819 | - |
3820 | -} |
3821 | - |
3822 | -func (p *GmailPlugin) reported(id string) bool { |
3823 | - _, ok := p.reportedIds[id] |
3824 | - return ok |
3825 | -} |
3826 | - |
3827 | -func (p *GmailPlugin) createNotifications(messages []message) ([]*plugins.PushMessage, error) { |
3828 | - timestamp := time.Now() |
3829 | - pushMsgMap := make(pushes) |
3830 | - |
3831 | - for _, msg := range messages { |
3832 | - hdr := msg.Payload.mapHeaders() |
3833 | - |
3834 | - from := hdr[hdrFROM] |
3835 | - var avatarPath string |
3836 | - |
3837 | - emailAddress, err := mail.ParseAddress(from) |
3838 | - if err != nil { |
3839 | - // If the email address contains non-ascii characters, we get an |
3840 | - // error so we're going to try again, this time mangling the name |
3841 | - // by removing all non-ascii characters. We only care about the email |
3842 | - // address here anyway. |
3843 | - // XXX: We can't check the error message due to [1]: the error |
3844 | - // message is different in go < 1.3 and > 1.5. |
3845 | - // [1] https://github.com/golang/go/issues/12492 |
3846 | - mangledAddr := nonAsciiChars.ReplaceAllString(from, "") |
3847 | - mangledEmail, mangledParseError := mail.ParseAddress(mangledAddr) |
3848 | - if mangledParseError == nil { |
3849 | - emailAddress = mangledEmail |
3850 | - } |
3851 | - } else if emailAddress.Name != "" { |
3852 | - // We only want the Name if the first ParseAddress |
3853 | - // call was successful. I.e. we do not want the name |
3854 | - // from a mangled email address. |
3855 | - from = emailAddress.Name |
3856 | - } |
3857 | - |
3858 | - if emailAddress != nil { |
3859 | - avatarPath = qtcontact.GetAvatar(emailAddress.Address) |
3860 | - // If icon path starts with a path separator, assume local file path, |
3861 | - // encode it and prepend file scheme defined in RFC 1738. |
3862 | - if strings.HasPrefix(avatarPath, string(os.PathSeparator)) { |
3863 | - avatarPath = url.QueryEscape(avatarPath) |
3864 | - avatarPath = "file://" + avatarPath |
3865 | - } |
3866 | - } |
3867 | - |
3868 | - msgStamp := hdr.getTimestamp() |
3869 | - |
3870 | - if _, ok := pushMsgMap[msg.ThreadId]; ok { |
3871 | - // TRANSLATORS: the %s is an appended "from" corresponding to an specific email thread |
3872 | - pushMsgMap[msg.ThreadId].Notification.Card.Summary += fmt.Sprintf(gettext.Gettext(", %s"), from) |
3873 | - } else if timestamp.Sub(msgStamp) < timeDelta { |
3874 | - // TRANSLATORS: the %s is the "from" header corresponding to a specific email |
3875 | - summary := fmt.Sprintf(gettext.Gettext("%s"), from) |
3876 | - // TRANSLATORS: the first %s refers to the email "subject", the second %s refers "from" |
3877 | - body := fmt.Sprintf(gettext.Gettext("%s\n%s"), hdr[hdrSUBJECT], msg.Snippet) |
3878 | - // fmt with label personal and threadId |
3879 | - action := fmt.Sprintf(dekkoDispatchUrl, p.accountId, "INBOX", msg.Id) |
3880 | - epoch := hdr.getEpoch() |
3881 | - pushMsgMap[msg.ThreadId] = plugins.NewStandardPushMessage(summary, body, action, avatarPath, epoch) |
3882 | - } else { |
3883 | - log.Print("gmail plugin ", p.accountId, ": skipping message id ", msg.Id, " with date ", msgStamp, " older than ", timeDelta) |
3884 | - } |
3885 | - } |
3886 | - pushMsg := make([]*plugins.PushMessage, 0, len(pushMsgMap)) |
3887 | - for _, v := range pushMsgMap { |
3888 | - pushMsg = append(pushMsg, v) |
3889 | - } |
3890 | - return pushMsg, nil |
3891 | - |
3892 | -} |
3893 | -func (p *GmailPlugin) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage { |
3894 | - // TODO it would probably be better to grab the estimate that google returns in the message list. |
3895 | - approxUnreadMessages := len(pushMsg) |
3896 | - |
3897 | - // TRANSLATORS: the %d refers to the number of new email messages. |
3898 | - summary := fmt.Sprintf(gettext.Gettext("You have %d new messages"), approxUnreadMessages) |
3899 | - |
3900 | - body := "" |
3901 | - |
3902 | - // fmt with label personal and no threadId |
3903 | - action := fmt.Sprintf(dekkoDispatchUrl, p.accountId, "INBOX") |
3904 | - epoch := time.Now().Unix() |
3905 | - |
3906 | - return plugins.NewStandardPushMessage(summary, body, action, "", epoch) |
3907 | -} |
3908 | - |
3909 | -func (p *GmailPlugin) parseMessageListResponse(resp *http.Response) ([]message, error) { |
3910 | - defer resp.Body.Close() |
3911 | - decoder := json.NewDecoder(resp.Body) |
3912 | - |
3913 | - if resp.StatusCode != http.StatusOK { |
3914 | - var errResp errorResp |
3915 | - if err := decoder.Decode(&errResp); err != nil { |
3916 | - return nil, err |
3917 | - } |
3918 | - if errResp.Err.Code == 401 { |
3919 | - return nil, plugins.ErrTokenExpired |
3920 | - } |
3921 | - return nil, &errResp |
3922 | - } |
3923 | - |
3924 | - var messages messageList |
3925 | - if err := decoder.Decode(&messages); err != nil { |
3926 | - return nil, err |
3927 | - } |
3928 | - |
3929 | - filteredMsg := p.messageListFilter(messages.Messages) |
3930 | - |
3931 | - return filteredMsg, nil |
3932 | -} |
3933 | - |
3934 | -// messageListFilter returns a subset of unread messages where the subset |
3935 | -// depends on not being in reportedIds. Before returning, reportedIds is |
3936 | -// updated with the new list of unread messages. |
3937 | -func (p *GmailPlugin) messageListFilter(messages []message) []message { |
3938 | - sort.Sort(byId(messages)) |
3939 | - var reportMsg []message |
3940 | - var ids = make(reportedIdMap) |
3941 | - |
3942 | - for _, msg := range messages { |
3943 | - if !p.reported(msg.Id) { |
3944 | - reportMsg = append(reportMsg, msg) |
3945 | - } |
3946 | - ids[msg.Id] = time.Now() |
3947 | - } |
3948 | - p.reportedIds = ids |
3949 | - p.reportedIds.persist(p.accountId) |
3950 | - return reportMsg |
3951 | -} |
3952 | - |
3953 | -func (p *GmailPlugin) parseMessageResponse(resp *http.Response) (message, error) { |
3954 | - defer resp.Body.Close() |
3955 | - decoder := json.NewDecoder(resp.Body) |
3956 | - |
3957 | - if resp.StatusCode != http.StatusOK { |
3958 | - var errResp errorResp |
3959 | - if err := decoder.Decode(&errResp); err != nil { |
3960 | - return message{}, err |
3961 | - } |
3962 | - return message{}, &errResp |
3963 | - } |
3964 | - |
3965 | - var msg message |
3966 | - if err := decoder.Decode(&msg); err != nil { |
3967 | - return message{}, err |
3968 | - } |
3969 | - |
3970 | - return msg, nil |
3971 | -} |
3972 | - |
3973 | -func (p *GmailPlugin) requestMessage(id, accessToken string) (*http.Response, error) { |
3974 | - u, err := baseUrl.Parse("messages/" + id) |
3975 | - if err != nil { |
3976 | - return nil, err |
3977 | - } |
3978 | - |
3979 | - query := u.Query() |
3980 | - // only request specific fields |
3981 | - query.Add("fields", "snippet,threadId,id,payload/headers") |
3982 | - // get the full message to get From and Subject from headers |
3983 | - query.Add("format", "full") |
3984 | - u.RawQuery = query.Encode() |
3985 | - |
3986 | - req, err := http.NewRequest("GET", u.String(), nil) |
3987 | - if err != nil { |
3988 | - return nil, err |
3989 | - } |
3990 | - req.Header.Set("Authorization", "Bearer "+accessToken) |
3991 | - |
3992 | - return http.DefaultClient.Do(req) |
3993 | -} |
3994 | - |
3995 | -func (p *GmailPlugin) requestMessageList(accessToken string) (*http.Response, error) { |
3996 | - u, err := baseUrl.Parse("messages") |
3997 | - if err != nil { |
3998 | - return nil, err |
3999 | - } |
4000 | - |
4001 | - query := u.Query() |
4002 | - |
4003 | - // get all unread inbox emails received after |
4004 | - // the last time we checked. If this is the first |
4005 | - // time we check, get unread emails after timeDelta |
4006 | - query.Add("q", fmt.Sprintf("is:unread in:inbox newer_than:%s", relativeTimeDelta)) |
4007 | - u.RawQuery = query.Encode() |
4008 | - |
4009 | - req, err := http.NewRequest("GET", u.String(), nil) |
4010 | - if err != nil { |
4011 | - return nil, err |
4012 | - } |
4013 | - req.Header.Set("Authorization", "Bearer "+accessToken) |
4014 | - |
4015 | - return http.DefaultClient.Do(req) |
4016 | -} |
4017 | |
4018 | === removed directory 'plugins/gcalendar' |
4019 | === removed file 'plugins/gcalendar/api.go' |
4020 | --- plugins/gcalendar/api.go 2016-04-14 14:58:10 +0000 |
4021 | +++ plugins/gcalendar/api.go 1970-01-01 00:00:00 +0000 |
4022 | @@ -1,54 +0,0 @@ |
4023 | -/* |
4024 | - Copyright 2014 Canonical Ltd. |
4025 | - |
4026 | - This program is free software: you can redistribute it and/or modify it |
4027 | - under the terms of the GNU General Public License version 3, as published |
4028 | - by the Free Software Foundation. |
4029 | - |
4030 | - This program is distributed in the hope that it will be useful, but |
4031 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
4032 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4033 | - PURPOSE. See the GNU General Public License for more details. |
4034 | - |
4035 | - You should have received a copy of the GNU General Public License along |
4036 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
4037 | -*/ |
4038 | - |
4039 | -package gcalendar |
4040 | - |
4041 | -import ( |
4042 | - "fmt" |
4043 | -) |
4044 | - |
4045 | -// eventList holds a response to call to Calendar.events: list |
4046 | -// defined in https://developers.google.com/google-apps/calendar/v3/reference/events/list#response |
4047 | -type eventList struct { |
4048 | - // Messages holds a list of message. |
4049 | - Events []event `json:"items"` |
4050 | -} |
4051 | - |
4052 | -// event holds the event data response for a Calendar.event. |
4053 | -// The full definition of a message is defined in |
4054 | -// https://developers.google.com/google-apps/calendar/v3/reference/events#resource-representations |
4055 | -type event struct { |
4056 | - // Id is the immutable ID of the message. |
4057 | - Etag string `json:"etag"` |
4058 | - // ThreadId is the ID of the thread the message belongs to. |
4059 | - Summary string `json:"summary"` |
4060 | -} |
4061 | - |
4062 | -func (e event) String() string { |
4063 | - return fmt.Sprintf("Id: %s, snippet: '%s'\n", e.Etag, e.Summary) |
4064 | -} |
4065 | - |
4066 | -type errorResp struct { |
4067 | - Err struct { |
4068 | - Code uint64 `json:"code"` |
4069 | - Message string `json:"message"` |
4070 | - Errors []struct { |
4071 | - Domain string `json:"domain"` |
4072 | - Reason string `json:"reason"` |
4073 | - Message string `json:"message"` |
4074 | - } `json:"errors"` |
4075 | - } `json:"error"` |
4076 | -} |
4077 | |
4078 | === removed file 'plugins/gcalendar/gcalendar.go' |
4079 | --- plugins/gcalendar/gcalendar.go 2016-08-02 14:16:26 +0000 |
4080 | +++ plugins/gcalendar/gcalendar.go 1970-01-01 00:00:00 +0000 |
4081 | @@ -1,189 +0,0 @@ |
4082 | -/* |
4083 | - Copyright 2016 Canonical Ltd. |
4084 | - |
4085 | - This program is free software: you can redistribute it and/or modify it |
4086 | - under the terms of the GNU General Public License version 3, as published |
4087 | - by the Free Software Foundation. |
4088 | - |
4089 | - This program is distributed in the hope that it will be useful, but |
4090 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
4091 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4092 | - PURPOSE. See the GNU General Public License for more details. |
4093 | - |
4094 | - You should have received a copy of the GNU General Public License along |
4095 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
4096 | -*/ |
4097 | - |
4098 | -package gcalendar |
4099 | - |
4100 | -import ( |
4101 | - "encoding/json" |
4102 | - "log" |
4103 | - "net/http" |
4104 | - "net/url" |
4105 | - "os" |
4106 | - |
4107 | - "launchpad.net/account-polld/accounts" |
4108 | - "launchpad.net/account-polld/plugins" |
4109 | - "launchpad.net/account-polld/syncmonitor" |
4110 | -) |
4111 | - |
4112 | -const ( |
4113 | - APP_ID = "com.ubuntu.calendar_calendar" |
4114 | - pluginName = "gcalendar" |
4115 | -) |
4116 | - |
4117 | -var baseUrl, _ = url.Parse("https://www.googleapis.com/calendar/v3/calendars/") |
4118 | - |
4119 | -type GCalendarPlugin struct { |
4120 | - accountId uint |
4121 | -} |
4122 | - |
4123 | -func New(accountId uint) *GCalendarPlugin { |
4124 | - return &GCalendarPlugin{accountId: accountId} |
4125 | -} |
4126 | - |
4127 | -func (p *GCalendarPlugin) ApplicationId() plugins.ApplicationId { |
4128 | - return plugins.ApplicationId(APP_ID) |
4129 | -} |
4130 | - |
4131 | -func (p *GCalendarPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) { |
4132 | - // This envvar check is to ease testing. |
4133 | - if token := os.Getenv("ACCOUNT_POLLD_TOKEN_GCALENDAR"); token != "" { |
4134 | - log.Print("calendar: Using token from: ACCOUNT_POLLD_TOKEN_GCALENDAR env var") |
4135 | - authData.AccessToken = token |
4136 | - } |
4137 | - |
4138 | - log.Print("calendar: Check calendar changes for account:", p.accountId) |
4139 | - |
4140 | - syncMonitor := syncmonitor.NewSyncMonitor() |
4141 | - if syncMonitor == nil { |
4142 | - log.Print("calendar: Sync monitor not available yet.") |
4143 | - return nil, nil |
4144 | - } |
4145 | - |
4146 | - state, err := syncMonitor.State() |
4147 | - if err != nil { |
4148 | - log.Print("calendar: Fail to retrieve sync monitor state ", err) |
4149 | - return nil, nil |
4150 | - } |
4151 | - if state != "idle" { |
4152 | - log.Print("calendar: Sync monitor is not on 'idle' state, try later!") |
4153 | - return nil, nil |
4154 | - } |
4155 | - |
4156 | - calendars, err := syncMonitor.ListCalendarsByAccount(p.accountId) |
4157 | - if err != nil { |
4158 | - log.Print("calendar: Calendar plugin ", p.accountId, ": cannot load calendars: ", err) |
4159 | - return nil, nil |
4160 | - } |
4161 | - |
4162 | - var calendarsToSync []string |
4163 | - log.Print("calendar: Number of calendars for account:", p.accountId, " size:", len(calendars)) |
4164 | - |
4165 | - for id, calendar := range calendars { |
4166 | - lastSyncDate, err := syncMonitor.LastSyncDate(p.accountId, id) |
4167 | - if err != nil { |
4168 | - log.Print("\tcalendar: ", calendar, ", cannot load previous sync date: ", err, ". Try next time.") |
4169 | - continue |
4170 | - } else { |
4171 | - log.Print("\tcalendar: ", calendar, " Id: ", id, ": last sync date: ", lastSyncDate) |
4172 | - } |
4173 | - |
4174 | - var needSync bool |
4175 | - needSync = (len(lastSyncDate) == 0) |
4176 | - |
4177 | - if !needSync { |
4178 | - resp, err := p.requestChanges(authData.AccessToken, id, lastSyncDate) |
4179 | - if err != nil { |
4180 | - log.Print("\tcalendar: ERROR: Fail to query for changes: ", err) |
4181 | - continue |
4182 | - } |
4183 | - |
4184 | - messages, err := p.parseChangesResponse(resp) |
4185 | - if err != nil { |
4186 | - log.Print("\tcalendar: ERROR: Fail to parse changes: ", err) |
4187 | - if err == plugins.ErrTokenExpired { |
4188 | - log.Print("\t\tcalendar: Abort poll") |
4189 | - return nil, err |
4190 | - } else { |
4191 | - continue |
4192 | - } |
4193 | - } |
4194 | - needSync = (len(messages) > 0) |
4195 | - } |
4196 | - |
4197 | - if needSync { |
4198 | - log.Print("\tcalendar: Calendar needs sync: ", calendar) |
4199 | - calendarsToSync = append(calendarsToSync, id) |
4200 | - } else { |
4201 | - log.Print("\tcalendar: Found no calendar updates for account: ", p.accountId, " calendar: ", calendar) |
4202 | - } |
4203 | - } |
4204 | - |
4205 | - if len(calendarsToSync) > 0 { |
4206 | - log.Print("calendar: Request account sync") |
4207 | - err = syncMonitor.SyncAccount(p.accountId, calendarsToSync) |
4208 | - if err != nil { |
4209 | - log.Print("calendar: ERROR: Fail to start account sync ", p.accountId, " message: ", err) |
4210 | - } |
4211 | - } |
4212 | - |
4213 | - return nil, nil |
4214 | -} |
4215 | - |
4216 | -func (p *GCalendarPlugin) parseChangesResponse(resp *http.Response) ([]event, error) { |
4217 | - defer resp.Body.Close() |
4218 | - decoder := json.NewDecoder(resp.Body) |
4219 | - |
4220 | - if resp.StatusCode != http.StatusOK { |
4221 | - var errResp errorResp |
4222 | - log.Print("calendar: Invalid response:", errResp.Err.Code) |
4223 | - if err := decoder.Decode(&errResp); err != nil { |
4224 | - return nil, err |
4225 | - } |
4226 | - if errResp.Err.Code == 401 { |
4227 | - return nil, plugins.ErrTokenExpired |
4228 | - } |
4229 | - return nil, nil |
4230 | - } |
4231 | - |
4232 | - var events eventList |
4233 | - if err := decoder.Decode(&events); err != nil { |
4234 | - log.Print("calendar: Fail to decode") |
4235 | - return nil, err |
4236 | - } |
4237 | - |
4238 | - for _, ev := range events.Events { |
4239 | - log.Print("calendar: Found event: ", ev.Etag, ev.Summary) |
4240 | - } |
4241 | - |
4242 | - return events.Events, nil |
4243 | -} |
4244 | - |
4245 | -func (p *GCalendarPlugin) requestChanges(accessToken string, calendar string, lastSyncDate string) (*http.Response, error) { |
4246 | - u, err := baseUrl.Parse("") |
4247 | - if err != nil { |
4248 | - return nil, err |
4249 | - } |
4250 | - u.Path += calendar + "/events" |
4251 | - |
4252 | - //GET https://www.googleapis.com/calendar/v3/calendars/<calendar>/events?showDeleted=true&singleEvents=true&updatedMin=2016-04-06T10%3A00%3A00.00Z&fields=description%2Citems(description%2Cetag%2Csummary)&key={YOUR_API_KEY} |
4253 | - query := u.Query() |
4254 | - query.Add("showDeleted", "true") |
4255 | - query.Add("singleEvents", "true") |
4256 | - query.Add("fields", "description,items(summary,etag)") |
4257 | - query.Add("maxResults", "1") |
4258 | - if len(lastSyncDate) > 0 { |
4259 | - query.Add("updatedMin", lastSyncDate) |
4260 | - } |
4261 | - u.RawQuery = query.Encode() |
4262 | - |
4263 | - req, err := http.NewRequest("GET", u.String(), nil) |
4264 | - if err != nil { |
4265 | - return nil, err |
4266 | - } |
4267 | - req.Header.Set("Authorization", "Bearer "+accessToken) |
4268 | - |
4269 | - return http.DefaultClient.Do(req) |
4270 | -} |
4271 | |
4272 | === removed directory 'plugins/gmail' |
4273 | === removed file 'plugins/gmail/api.go' |
4274 | --- plugins/gmail/api.go 2015-03-20 14:34:48 +0000 |
4275 | +++ plugins/gmail/api.go 1970-01-01 00:00:00 +0000 |
4276 | @@ -1,127 +0,0 @@ |
4277 | -/* |
4278 | - Copyright 2014 Canonical Ltd. |
4279 | - |
4280 | - This program is free software: you can redistribute it and/or modify it |
4281 | - under the terms of the GNU General Public License version 3, as published |
4282 | - by the Free Software Foundation. |
4283 | - |
4284 | - This program is distributed in the hope that it will be useful, but |
4285 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
4286 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4287 | - PURPOSE. See the GNU General Public License for more details. |
4288 | - |
4289 | - You should have received a copy of the GNU General Public License along |
4290 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
4291 | -*/ |
4292 | - |
4293 | -package gmail |
4294 | - |
4295 | -import ( |
4296 | - "fmt" |
4297 | - "time" |
4298 | - |
4299 | - "launchpad.net/account-polld/plugins" |
4300 | -) |
4301 | - |
4302 | -const gmailTime = "Mon, 2 Jan 2006 15:04:05 -0700" |
4303 | - |
4304 | -type pushes map[string]*plugins.PushMessage |
4305 | -type headers map[string]string |
4306 | - |
4307 | -// messageList holds a response to call to Users.messages: list |
4308 | -// defined in https://developers.google.com/gmail/api/v1/reference/users/messages/list |
4309 | -type messageList struct { |
4310 | - // Messages holds a list of message. |
4311 | - Messages []message `json:"messages"` |
4312 | - // NextPageToken is used to retrieve the next page of results in the list. |
4313 | - NextPageToken string `json:"nextPageToken"` |
4314 | - // ResultSizeEstimage is the estimated total number of results. |
4315 | - ResultSizeEstimage uint64 `json:"resultSizeEstimate"` |
4316 | -} |
4317 | - |
4318 | -// message holds a partial response for a Users.messages. |
4319 | -// The full definition of a message is defined in |
4320 | -// https://developers.google.com/gmail/api/v1/reference/users/messages#resource |
4321 | -type message struct { |
4322 | - // Id is the immutable ID of the message. |
4323 | - Id string `json:"id"` |
4324 | - // ThreadId is the ID of the thread the message belongs to. |
4325 | - ThreadId string `json:"threadId"` |
4326 | - // HistoryId is the ID of the last history record that modified |
4327 | - // this message. |
4328 | - HistoryId string `json:"historyId"` |
4329 | - // Snippet is a short part of the message text. This text is |
4330 | - // used for the push message summary. |
4331 | - Snippet string `json:"snippet"` |
4332 | - // Payload represents the message payload. |
4333 | - Payload payload `json:"payload"` |
4334 | -} |
4335 | - |
4336 | -func (m message) String() string { |
4337 | - return fmt.Sprintf("Id: %d, snippet: '%s'\n", m.Id, m.Snippet[:10]) |
4338 | -} |
4339 | - |
4340 | -// ById implements sort.Interface for []message based on |
4341 | -// the Id field. |
4342 | -type byId []message |
4343 | - |
4344 | -func (m byId) Len() int { return len(m) } |
4345 | -func (m byId) Swap(i, j int) { m[i], m[j] = m[j], m[i] } |
4346 | -func (m byId) Less(i, j int) bool { return m[i].Id < m[j].Id } |
4347 | - |
4348 | -// payload represents the message payload. |
4349 | -type payload struct { |
4350 | - Headers []messageHeader `json:"headers"` |
4351 | -} |
4352 | - |
4353 | -func (p *payload) mapHeaders() headers { |
4354 | - headers := make(map[string]string) |
4355 | - for _, hdr := range p.Headers { |
4356 | - headers[hdr.Name] = hdr.Value |
4357 | - } |
4358 | - return headers |
4359 | -} |
4360 | - |
4361 | -func (hdr headers) getTimestamp() time.Time { |
4362 | - timestamp, ok := hdr[hdrDATE] |
4363 | - if !ok { |
4364 | - return time.Now() |
4365 | - } |
4366 | - |
4367 | - if t, err := time.Parse(gmailTime, timestamp); err == nil { |
4368 | - return t |
4369 | - } |
4370 | - return time.Now() |
4371 | -} |
4372 | - |
4373 | -func (hdr headers) getEpoch() int64 { |
4374 | - return hdr.getTimestamp().Unix() |
4375 | -} |
4376 | - |
4377 | -// messageHeader represents the message headers. |
4378 | -type messageHeader struct { |
4379 | - Name string `json:"name"` |
4380 | - Value string `json:"value"` |
4381 | -} |
4382 | - |
4383 | -type errorResp struct { |
4384 | - Err struct { |
4385 | - Code uint64 `json:"code"` |
4386 | - Message string `json:"message"` |
4387 | - Errors []struct { |
4388 | - Domain string `json:"domain"` |
4389 | - Reason string `json:"reason"` |
4390 | - Message string `json:"message"` |
4391 | - } `json:"errors"` |
4392 | - } `json:"error"` |
4393 | -} |
4394 | - |
4395 | -func (err *errorResp) Error() string { |
4396 | - return fmt.Sprint("backend response:", err.Err.Message) |
4397 | -} |
4398 | - |
4399 | -const ( |
4400 | - hdrDATE = "Date" |
4401 | - hdrSUBJECT = "Subject" |
4402 | - hdrFROM = "From" |
4403 | -) |
4404 | |
4405 | === removed file 'plugins/gmail/gmail.go' |
4406 | --- plugins/gmail/gmail.go 2016-04-20 11:10:17 +0000 |
4407 | +++ plugins/gmail/gmail.go 1970-01-01 00:00:00 +0000 |
4408 | @@ -1,346 +0,0 @@ |
4409 | -/* |
4410 | - Copyright 2014 Canonical Ltd. |
4411 | - |
4412 | - This program is free software: you can redistribute it and/or modify it |
4413 | - under the terms of the GNU General Public License version 3, as published |
4414 | - by the Free Software Foundation. |
4415 | - |
4416 | - This program is distributed in the hope that it will be useful, but |
4417 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
4418 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4419 | - PURPOSE. See the GNU General Public License for more details. |
4420 | - |
4421 | - You should have received a copy of the GNU General Public License along |
4422 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
4423 | -*/ |
4424 | - |
4425 | -package gmail |
4426 | - |
4427 | -import ( |
4428 | - "encoding/json" |
4429 | - "fmt" |
4430 | - "net/http" |
4431 | - "net/mail" |
4432 | - "net/url" |
4433 | - "os" |
4434 | - "regexp" |
4435 | - "sort" |
4436 | - "strings" |
4437 | - "time" |
4438 | - |
4439 | - "log" |
4440 | - |
4441 | - "launchpad.net/account-polld/accounts" |
4442 | - "launchpad.net/account-polld/gettext" |
4443 | - "launchpad.net/account-polld/plugins" |
4444 | - "launchpad.net/account-polld/qtcontact" |
4445 | -) |
4446 | - |
4447 | -const ( |
4448 | - APP_ID = "com.ubuntu.developer.webapps.webapp-gmail_webapp-gmail" |
4449 | - gmailDispatchUrl = "https://mail.google.com/mail/mu/mp/#cv/priority/^smartlabel_%s/%s" |
4450 | - // If there's more than 10 emails in one batch, we don't show 10 notification |
4451 | - // bubbles, but instead show one summary. We always show all notifications in the |
4452 | - // indicator. |
4453 | - individualNotificationsLimit = 10 |
4454 | - pluginName = "gmail" |
4455 | -) |
4456 | - |
4457 | -type reportedIdMap map[string]time.Time |
4458 | - |
4459 | -var baseUrl, _ = url.Parse("https://www.googleapis.com/gmail/v1/users/me/") |
4460 | - |
4461 | -// timeDelta defines how old messages can be to be reported. |
4462 | -var timeDelta = time.Duration(time.Hour * 24) |
4463 | - |
4464 | -// trackDelta defines how old messages can be before removed from tracking |
4465 | -var trackDelta = time.Duration(time.Hour * 24 * 7) |
4466 | - |
4467 | -// relativeTimeDelta is the same as timeDelta |
4468 | -var relativeTimeDelta string = "1d" |
4469 | - |
4470 | -// regexp for identifying non-ascii characters |
4471 | -var nonAsciiChars, _ = regexp.Compile("[^\x00-\x7F]") |
4472 | - |
4473 | -type GmailPlugin struct { |
4474 | - // reportedIds holds the messages that have already been notified. This |
4475 | - // approach is taken against timestamps as it avoids needing to call |
4476 | - // get on the message. |
4477 | - reportedIds reportedIdMap |
4478 | - accountId uint |
4479 | -} |
4480 | - |
4481 | -func idsFromPersist(accountId uint) (ids reportedIdMap, err error) { |
4482 | - err = plugins.FromPersist(pluginName, accountId, &ids) |
4483 | - if err != nil { |
4484 | - return nil, err |
4485 | - } |
4486 | - // discard old ids |
4487 | - timestamp := time.Now() |
4488 | - for k, v := range ids { |
4489 | - delta := timestamp.Sub(v) |
4490 | - if delta > trackDelta { |
4491 | - log.Print("gmail plugin ", accountId, ": deleting ", k, " as ", delta, " is greater than ", trackDelta) |
4492 | - delete(ids, k) |
4493 | - } |
4494 | - } |
4495 | - return ids, nil |
4496 | -} |
4497 | - |
4498 | -func (ids reportedIdMap) persist(accountId uint) (err error) { |
4499 | - err = plugins.Persist(pluginName, accountId, ids) |
4500 | - if err != nil { |
4501 | - log.Print("gmail plugin ", accountId, ": failed to save state: ", err) |
4502 | - } |
4503 | - return nil |
4504 | -} |
4505 | - |
4506 | -func New(accountId uint) *GmailPlugin { |
4507 | - reportedIds, err := idsFromPersist(accountId) |
4508 | - if err != nil { |
4509 | - log.Print("gmail plugin ", accountId, ": cannot load previous state from storage: ", err) |
4510 | - } else { |
4511 | - log.Print("gmail plugin ", accountId, ": last state loaded from storage") |
4512 | - } |
4513 | - return &GmailPlugin{reportedIds: reportedIds, accountId: accountId} |
4514 | -} |
4515 | - |
4516 | -func (p *GmailPlugin) ApplicationId() plugins.ApplicationId { |
4517 | - return plugins.ApplicationId(APP_ID) |
4518 | -} |
4519 | - |
4520 | -func (p *GmailPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) { |
4521 | - // This envvar check is to ease testing. |
4522 | - if token := os.Getenv("ACCOUNT_POLLD_TOKEN_GMAIL"); token != "" { |
4523 | - authData.AccessToken = token |
4524 | - } |
4525 | - |
4526 | - resp, err := p.requestMessageList(authData.AccessToken) |
4527 | - if err != nil { |
4528 | - return nil, err |
4529 | - } |
4530 | - messages, err := p.parseMessageListResponse(resp) |
4531 | - if err != nil { |
4532 | - return nil, err |
4533 | - } |
4534 | - |
4535 | - // TODO use the batching API defined in https://developers.google.com/gmail/api/guides/batch |
4536 | - for i := range messages { |
4537 | - resp, err := p.requestMessage(messages[i].Id, authData.AccessToken) |
4538 | - if err != nil { |
4539 | - return nil, err |
4540 | - } |
4541 | - messages[i], err = p.parseMessageResponse(resp) |
4542 | - if err != nil { |
4543 | - return nil, err |
4544 | - } |
4545 | - } |
4546 | - notif, err := p.createNotifications(messages) |
4547 | - if err != nil { |
4548 | - return nil, err |
4549 | - } |
4550 | - return []*plugins.PushMessageBatch{ |
4551 | - &plugins.PushMessageBatch{ |
4552 | - Messages: notif, |
4553 | - Limit: individualNotificationsLimit, |
4554 | - OverflowHandler: p.handleOverflow, |
4555 | - Tag: "gmail", |
4556 | - }}, nil |
4557 | - |
4558 | -} |
4559 | - |
4560 | -func (p *GmailPlugin) reported(id string) bool { |
4561 | - _, ok := p.reportedIds[id] |
4562 | - return ok |
4563 | -} |
4564 | - |
4565 | -func (p *GmailPlugin) createNotifications(messages []message) ([]*plugins.PushMessage, error) { |
4566 | - timestamp := time.Now() |
4567 | - pushMsgMap := make(pushes) |
4568 | - |
4569 | - for _, msg := range messages { |
4570 | - hdr := msg.Payload.mapHeaders() |
4571 | - |
4572 | - from := hdr[hdrFROM] |
4573 | - var avatarPath string |
4574 | - |
4575 | - emailAddress, err := mail.ParseAddress(from) |
4576 | - if err != nil { |
4577 | - // If the email address contains non-ascii characters, we get an |
4578 | - // error so we're going to try again, this time mangling the name |
4579 | - // by removing all non-ascii characters. We only care about the email |
4580 | - // address here anyway. |
4581 | - // XXX: We can't check the error message due to [1]: the error |
4582 | - // message is different in go < 1.3 and > 1.5. |
4583 | - // [1] https://github.com/golang/go/issues/12492 |
4584 | - mangledAddr := nonAsciiChars.ReplaceAllString(from, "") |
4585 | - mangledEmail, mangledParseError := mail.ParseAddress(mangledAddr) |
4586 | - if mangledParseError == nil { |
4587 | - emailAddress = mangledEmail |
4588 | - } |
4589 | - } else if emailAddress.Name != "" { |
4590 | - // We only want the Name if the first ParseAddress |
4591 | - // call was successful. I.e. we do not want the name |
4592 | - // from a mangled email address. |
4593 | - from = emailAddress.Name |
4594 | - } |
4595 | - |
4596 | - if emailAddress != nil { |
4597 | - avatarPath = qtcontact.GetAvatar(emailAddress.Address) |
4598 | - // If icon path starts with a path separator, assume local file path, |
4599 | - // encode it and prepend file scheme defined in RFC 1738. |
4600 | - if strings.HasPrefix(avatarPath, string(os.PathSeparator)) { |
4601 | - avatarPath = url.QueryEscape(avatarPath) |
4602 | - avatarPath = "file://" + avatarPath |
4603 | - } |
4604 | - } |
4605 | - |
4606 | - msgStamp := hdr.getTimestamp() |
4607 | - |
4608 | - if _, ok := pushMsgMap[msg.ThreadId]; ok { |
4609 | - // TRANSLATORS: the %s is an appended "from" corresponding to an specific email thread |
4610 | - pushMsgMap[msg.ThreadId].Notification.Card.Summary += fmt.Sprintf(gettext.Gettext(", %s"), from) |
4611 | - } else if timestamp.Sub(msgStamp) < timeDelta { |
4612 | - // TRANSLATORS: the %s is the "from" header corresponding to a specific email |
4613 | - summary := fmt.Sprintf(gettext.Gettext("%s"), from) |
4614 | - // TRANSLATORS: the first %s refers to the email "subject", the second %s refers "from" |
4615 | - body := fmt.Sprintf(gettext.Gettext("%s\n%s"), hdr[hdrSUBJECT], msg.Snippet) |
4616 | - // fmt with label personal and threadId |
4617 | - action := fmt.Sprintf(gmailDispatchUrl, "personal", msg.ThreadId) |
4618 | - epoch := hdr.getEpoch() |
4619 | - pushMsgMap[msg.ThreadId] = plugins.NewStandardPushMessage(summary, body, action, avatarPath, epoch) |
4620 | - } else { |
4621 | - log.Print("gmail plugin ", p.accountId, ": skipping message id ", msg.Id, " with date ", msgStamp, " older than ", timeDelta) |
4622 | - } |
4623 | - } |
4624 | - pushMsg := make([]*plugins.PushMessage, 0, len(pushMsgMap)) |
4625 | - for _, v := range pushMsgMap { |
4626 | - pushMsg = append(pushMsg, v) |
4627 | - } |
4628 | - return pushMsg, nil |
4629 | - |
4630 | -} |
4631 | -func (p *GmailPlugin) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage { |
4632 | - // TODO it would probably be better to grab the estimate that google returns in the message list. |
4633 | - approxUnreadMessages := len(pushMsg) |
4634 | - |
4635 | - // TRANSLATORS: the %d refers to the number of new email messages. |
4636 | - summary := fmt.Sprintf(gettext.Gettext("You have %d new messages"), approxUnreadMessages) |
4637 | - |
4638 | - body := "" |
4639 | - |
4640 | - // fmt with label personal and no threadId |
4641 | - action := fmt.Sprintf(gmailDispatchUrl, "personal") |
4642 | - epoch := time.Now().Unix() |
4643 | - |
4644 | - return plugins.NewStandardPushMessage(summary, body, action, "", epoch) |
4645 | -} |
4646 | - |
4647 | -func (p *GmailPlugin) parseMessageListResponse(resp *http.Response) ([]message, error) { |
4648 | - defer resp.Body.Close() |
4649 | - decoder := json.NewDecoder(resp.Body) |
4650 | - |
4651 | - if resp.StatusCode != http.StatusOK { |
4652 | - var errResp errorResp |
4653 | - if err := decoder.Decode(&errResp); err != nil { |
4654 | - return nil, err |
4655 | - } |
4656 | - if errResp.Err.Code == 401 { |
4657 | - return nil, plugins.ErrTokenExpired |
4658 | - } |
4659 | - return nil, &errResp |
4660 | - } |
4661 | - |
4662 | - var messages messageList |
4663 | - if err := decoder.Decode(&messages); err != nil { |
4664 | - return nil, err |
4665 | - } |
4666 | - |
4667 | - filteredMsg := p.messageListFilter(messages.Messages) |
4668 | - |
4669 | - return filteredMsg, nil |
4670 | -} |
4671 | - |
4672 | -// messageListFilter returns a subset of unread messages where the subset |
4673 | -// depends on not being in reportedIds. Before returning, reportedIds is |
4674 | -// updated with the new list of unread messages. |
4675 | -func (p *GmailPlugin) messageListFilter(messages []message) []message { |
4676 | - sort.Sort(byId(messages)) |
4677 | - var reportMsg []message |
4678 | - var ids = make(reportedIdMap) |
4679 | - |
4680 | - for _, msg := range messages { |
4681 | - if !p.reported(msg.Id) { |
4682 | - reportMsg = append(reportMsg, msg) |
4683 | - } |
4684 | - ids[msg.Id] = time.Now() |
4685 | - } |
4686 | - p.reportedIds = ids |
4687 | - p.reportedIds.persist(p.accountId) |
4688 | - return reportMsg |
4689 | -} |
4690 | - |
4691 | -func (p *GmailPlugin) parseMessageResponse(resp *http.Response) (message, error) { |
4692 | - defer resp.Body.Close() |
4693 | - decoder := json.NewDecoder(resp.Body) |
4694 | - |
4695 | - if resp.StatusCode != http.StatusOK { |
4696 | - var errResp errorResp |
4697 | - if err := decoder.Decode(&errResp); err != nil { |
4698 | - return message{}, err |
4699 | - } |
4700 | - return message{}, &errResp |
4701 | - } |
4702 | - |
4703 | - var msg message |
4704 | - if err := decoder.Decode(&msg); err != nil { |
4705 | - return message{}, err |
4706 | - } |
4707 | - |
4708 | - return msg, nil |
4709 | -} |
4710 | - |
4711 | -func (p *GmailPlugin) requestMessage(id, accessToken string) (*http.Response, error) { |
4712 | - u, err := baseUrl.Parse("messages/" + id) |
4713 | - if err != nil { |
4714 | - return nil, err |
4715 | - } |
4716 | - |
4717 | - query := u.Query() |
4718 | - // only request specific fields |
4719 | - query.Add("fields", "snippet,threadId,id,payload/headers") |
4720 | - // get the full message to get From and Subject from headers |
4721 | - query.Add("format", "full") |
4722 | - u.RawQuery = query.Encode() |
4723 | - |
4724 | - req, err := http.NewRequest("GET", u.String(), nil) |
4725 | - if err != nil { |
4726 | - return nil, err |
4727 | - } |
4728 | - req.Header.Set("Authorization", "Bearer "+accessToken) |
4729 | - |
4730 | - return http.DefaultClient.Do(req) |
4731 | -} |
4732 | - |
4733 | -func (p *GmailPlugin) requestMessageList(accessToken string) (*http.Response, error) { |
4734 | - u, err := baseUrl.Parse("messages") |
4735 | - if err != nil { |
4736 | - return nil, err |
4737 | - } |
4738 | - |
4739 | - query := u.Query() |
4740 | - |
4741 | - // get all unread inbox emails received after |
4742 | - // the last time we checked. If this is the first |
4743 | - // time we check, get unread emails after timeDelta |
4744 | - query.Add("q", fmt.Sprintf("is:unread in:inbox newer_than:%s", relativeTimeDelta)) |
4745 | - u.RawQuery = query.Encode() |
4746 | - |
4747 | - req, err := http.NewRequest("GET", u.String(), nil) |
4748 | - if err != nil { |
4749 | - return nil, err |
4750 | - } |
4751 | - req.Header.Set("Authorization", "Bearer "+accessToken) |
4752 | - |
4753 | - return http.DefaultClient.Do(req) |
4754 | -} |
4755 | |
4756 | === removed file 'plugins/plugins.go' |
4757 | --- plugins/plugins.go 2016-02-08 19:21:31 +0000 |
4758 | +++ plugins/plugins.go 1970-01-01 00:00:00 +0000 |
4759 | @@ -1,239 +0,0 @@ |
4760 | -/* |
4761 | - Copyright 2014 Canonical Ltd. |
4762 | - |
4763 | - This program is free software: you can redistribute it and/or modify it |
4764 | - under the terms of the GNU General Public License version 3, as published |
4765 | - by the Free Software Foundation. |
4766 | - |
4767 | - This program is distributed in the hope that it will be useful, but |
4768 | - WITHOUT ANY WARRANTY; without even the implied warranties of |
4769 | - MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
4770 | - PURPOSE. See the GNU General Public License for more details. |
4771 | - |
4772 | - You should have received a copy of the GNU General Public License along |
4773 | - with this program. If not, see <http://www.gnu.org/licenses/>. |
4774 | -*/ |
4775 | - |
4776 | -package plugins |
4777 | - |
4778 | -import ( |
4779 | - "bufio" |
4780 | - "encoding/json" |
4781 | - "errors" |
4782 | - "fmt" |
4783 | - "os" |
4784 | - "path/filepath" |
4785 | - "reflect" |
4786 | - |
4787 | - "launchpad.net/account-polld/accounts" |
4788 | - "launchpad.net/go-xdg/v0" |
4789 | -) |
4790 | - |
4791 | -func init() { |
4792 | - cmdName = filepath.Base(os.Args[0]) |
4793 | -} |
4794 | - |
4795 | -// Plugin is an interface which the plugins will adhere to for the poll |
4796 | -// daemon to interact with. |
4797 | -// |
4798 | -// Poll interacts with the backend service with the means the plugin defines |
4799 | -// and returns a list of Notifications to send to the Push service. If an |
4800 | -// error occurs and is returned the daemon can decide to throttle the service. |
4801 | -// |
4802 | -// ApplicationId returns the APP_ID of the delivery target for Post Office. |
4803 | -type Plugin interface { |
4804 | - ApplicationId() ApplicationId |
4805 | - Poll(*accounts.AuthData) ([]*PushMessageBatch, error) |
4806 | -} |
4807 | - |
4808 | -// AuthTokens is a map with tokens the plugins are to use to make requests. |
4809 | -type AuthTokens map[string]interface{} |
4810 | - |
4811 | -// ApplicationId represents the application id to direct posts to. |
4812 | -// e.g.: com.ubuntu.diaspora_diaspora or com.ubuntu.diaspora_diaspora_1.0 |
4813 | -type ApplicationId string |
4814 | - |
4815 | -// NewStandardPushMessage creates a base Notification with common |
4816 | -// components (members) setup. |
4817 | -func NewStandardPushMessage(summary, body, action, icon string, epoch int64) *PushMessage { |
4818 | - pm := &PushMessage{ |
4819 | - Notification: Notification{ |
4820 | - Card: &Card{ |
4821 | - Summary: summary, |
4822 | - Body: body, |
4823 | - Actions: []string{action}, |
4824 | - Icon: icon, |
4825 | - Timestamp: epoch, |
4826 | - Popup: true, |
4827 | - Persist: true, |
4828 | - }, |
4829 | - Sound: DefaultSound(), |
4830 | - Vibrate: true, |
4831 | - Tag: cmdName, |
4832 | - }, |
4833 | - } |
4834 | - return pm |
4835 | -} |
4836 | - |
4837 | -// PushMessageBatch represents a logical grouping of PushMessages that |
4838 | -// have a limit on the number of their notifications that want to be |
4839 | -// presented to the user at the same time, and a way to handle the |
4840 | -// overflow. All Notifications that are part of a Batch share the same |
4841 | -// tag (Tag). ${Tag}-overflow is the overflow notification tag. |
4842 | -// |
4843 | -// TODO: support notifications sharing just the prefix (so the app can |
4844 | -// tell them apart by tag). |
4845 | -type PushMessageBatch struct { |
4846 | - Messages []*PushMessage |
4847 | - Limit int |
4848 | - OverflowHandler func([]*PushMessage) *PushMessage |
4849 | - Tag string |
4850 | -} |
4851 | - |
4852 | -// PushMessage represents a data structure to be sent over to the |
4853 | -// Post Office. It consists of a Notification and a Message. |
4854 | -type PushMessage struct { |
4855 | - // Message represents a JSON object that is passed as-is to the |
4856 | - // application |
4857 | - Message string `json:"message,omitempty"` |
4858 | - // Notification (optional) describes the user-facing notifications |
4859 | - // triggered by this push message. |
4860 | - Notification Notification `json:"notification,omitempty"` |
4861 | -} |
4862 | - |
4863 | -// Notification (optional) describes the user-facing notifications |
4864 | -// triggered by this push message. |
4865 | -type Notification struct { |
4866 | - // Sound (optional) is the path to a sound file which can or |
4867 | - // cannot be played depending on user preferences. |
4868 | - Sound string `json:"sound,omitempty"` |
4869 | - // Card represents a specific bubble to give to the user |
4870 | - Card *Card `json:"card,omitempty"` |
4871 | - // Vibrate is the haptic feedback part of a notification. |
4872 | - Vibrate bool `json:"vibrate,omitempty"` |
4873 | - // EmblemCounter represents and application counter hint |
4874 | - // related to the notification. |
4875 | - EmblemCounter *EmblemCounter `json:"emblem-counter,omitempty"` |
4876 | - // Tag represents a tag to identify persistent notifications |
4877 | - Tag string `json:"tag,omitempty"` |
4878 | -} |
4879 | - |
4880 | -// Card is part of a notification and represents the user visible hints for |
4881 | -// a specific notification. |
4882 | -type Card struct { |
4883 | - // Summary is a required title. The card will not be presented if this is missing. |
4884 | - Summary string `json:"summary"` |
4885 | - // Body is the longer text. |
4886 | - Body string `json:"body,omitempty"` |
4887 | - // Whether to show a bubble. Users can disable this, and can easily miss |
4888 | - // them, so don’t rely on it exclusively. |
4889 | - Popup bool `json:"popup,omitempty"` |
4890 | - // Actions provides actions for the bubble's snap decissions. |
4891 | - Actions []string `json:"actions,omitempty"` |
4892 | - // Icon is a path to an icon to display with the notification bubble. |
4893 | - Icon string `json:"icon,omitempty"` |
4894 | - // Whether to show in notification centre. |
4895 | - Persist bool `json:"persist,omitempty"` |
4896 | - // Seconds since the unix epoch, useful for persistent cards. |
4897 | - Timestamp int64 `json:"Timestamp,omitempty"` |
4898 | -} |
4899 | - |
4900 | -// EmblemCounter is part of a notification and represents the application visual |
4901 | -// hints related to a notification. |
4902 | -type EmblemCounter struct { |
4903 | - // Count is a number to be displayed over the application’s icon in the |
4904 | - // launcher. |
4905 | - Count uint `json:"count"` |
4906 | - // Visible determines if the counter is visible or not. |
4907 | - Visible bool `json:"visible"` |
4908 | -} |
4909 | - |
4910 | -// The constanst defined here determine the polling aggressivenes with the following criteria |
4911 | -// MAXIMUM: calls, health warning |
4912 | -// HIGH: SMS, chat message, new email |
4913 | -// DEFAULT: social media updates |
4914 | -// LOW: software updates, junk email |
4915 | -const ( |
4916 | - PRIORITY_MAXIMUM = 0 |
4917 | - PRIORITY_HIGH |
4918 | - PRIORITY_DEFAULT |
4919 | - PRIORITY_LOW |
4920 | -) |
4921 | - |
4922 | -const ( |
4923 | - PLUGIN_EMAIL = 0 |
4924 | - PLUGIN_SOCIAL |
4925 | -) |
4926 | - |
4927 | -// ErrTokenExpired is the error returned by a plugin to indicate that |
4928 | -// the web service reported that the authentication token has expired. |
4929 | -var ErrTokenExpired = errors.New("Token expired") |
4930 | - |
4931 | -var cmdName string |
4932 | - |
4933 | -var XdgDataFind = xdg.Data.Find |
4934 | -var XdgDataEnsure = xdg.Data.Ensure |
4935 | - |
4936 | -// Persist stores the plugins data in a common location to a json file |
4937 | -// from which it can recover later |
4938 | -func Persist(pluginName string, accountId uint, data interface{}) (err error) { |
4939 | - var p string |
4940 | - defer func() { |
4941 | - if err != nil && p != "" { |
4942 | - os.Remove(p) |
4943 | - } |
4944 | - }() |
4945 | - p, err = XdgDataEnsure(filepath.Join(cmdName, fmt.Sprintf("%s-%d.json", pluginName, accountId))) |
4946 | - if err != nil { |
4947 | - return err |
4948 | - } |
4949 | - file, err := os.Create(p) |
4950 | - if err != nil { |
4951 | - return err |
4952 | - } |
4953 | - defer file.Close() |
4954 | - w := bufio.NewWriter(file) |
4955 | - defer w.Flush() |
4956 | - jsonWriter := json.NewEncoder(w) |
4957 | - if err := jsonWriter.Encode(data); err != nil { |
4958 | - return err |
4959 | - } |
4960 | - return nil |
4961 | -} |
4962 | - |
4963 | -// FromPersist restores the plugins data from a common location which |
4964 | -// was stored in a json file |
4965 | -func FromPersist(pluginName string, accountId uint, data interface{}) (err error) { |
4966 | - if reflect.ValueOf(data).Kind() != reflect.Ptr { |
4967 | - return errors.New("decode target is not a pointer") |
4968 | - } |
4969 | - var p string |
4970 | - defer func() { |
4971 | - if err != nil { |
4972 | - if p != "" { |
4973 | - os.Remove(p) |
4974 | - } |
4975 | - } |
4976 | - }() |
4977 | - p, err = XdgDataFind(filepath.Join(cmdName, fmt.Sprintf("%s-%d.json", pluginName, accountId))) |
4978 | - if err != nil { |
4979 | - return err |
4980 | - } |
4981 | - file, err := os.Open(p) |
4982 | - if err != nil { |
4983 | - return err |
4984 | - } |
4985 | - defer file.Close() |
4986 | - jsonReader := json.NewDecoder(file) |
4987 | - if err := jsonReader.Decode(&data); err != nil { |
4988 | - return err |
4989 | - } |
4990 | - |
4991 | - return nil |
4992 | -} |
4993 | - |
4994 | -// DefaultSound returns the path to the default sound for a Notification |
4995 | -func DefaultSound() string { |
4996 | - // path is searched within XDG_DATA_DIRS |
4997 | - return "sounds/ubuntu/notifications/Blip.ogg" |
4998 | -} |
4999 | |
5000 | === removed directory 'plugins/twitter' |