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