Merge lp:~renatofilho/account-polld/account-polld-caldav into lp:~ubuntu-push-hackers/account-polld/trunk

Proposed by Renato Araujo Oliveira Filho
Status: Superseded
Proposed branch: lp:~renatofilho/account-polld/account-polld-caldav
Merge into: lp:~ubuntu-push-hackers/account-polld/trunk
Prerequisite: lp:~renatofilho/account-polld/query-calendars
Diff against target: 1606 lines (+978/-244)
14 files modified
accounts/account-watcher.c (+79/-90)
accounts/account-watcher.h (+9/-3)
accounts/accounts.c (+3/-2)
accounts/accounts.go (+25/-9)
cmd/account-polld/account_service.go (+11/-13)
cmd/account-polld/main.go (+24/-28)
cmd/account-watcher-test/main.go (+1/-3)
plugins/caldav/api.go (+54/-0)
plugins/caldav/caldav.go (+203/-0)
plugins/dekko/api.go (+127/-0)
plugins/dekko/dekko.go (+346/-0)
plugins/gcalendar/gcalendar.go (+3/-2)
plugins/gcalendar/syncmonitor.go (+0/-94)
syncmonitor/syncmonitor.go (+93/-0)
To merge this branch: bzr merge lp:~renatofilho/account-polld/account-polld-caldav
Reviewer Review Type Date Requested Status
system-apps-ci-bot continuous-integration Approve
PS Jenkins bot continuous-integration Pending
Ubuntu Push Hackers Pending
Review via email: mp+299868@code.launchpad.net

This proposal supersedes a proposal from 2016-07-12.

This proposal has been superseded by a proposal from 2016-08-02.

Commit message

Implement the caldav plugin to query for changes on caldav sources.

To post a comment you must log in.
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :
Download full text (3.3 KiB)

FAILED: Continuous integration, rev:179
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~renatofilho/account-polld/account-polld-caldav/+merge/299868/+edit-commit-message

https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/11/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/953
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/169
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/953
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/858
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/858
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/858
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/855/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/855
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg...

Read more...

review: Needs Fixing (continuous-integration)
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:180
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/16/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1052
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/209
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1052
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/949
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/949
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/949
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/940/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/940
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/940/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/16/rebuild

review: Approve (continuous-integration)
180. By Renato Araujo Oliveira Filho

Do not use hard coded urls.

181. By Renato Araujo Oliveira Filho

print debug message.

182. By Renato Araujo Oliveira Filho

Trunk merged.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:182
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/17/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1076
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/220
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1076
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/973
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/973
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/973
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/962/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/962
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/962/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/17/rebuild

review: Approve (continuous-integration)
183. By Renato Araujo Oliveira Filho

Use UTC date on end date.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

FAILED: Continuous integration, rev:183
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/18/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/system-apps/job/build/1079/console
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1079
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/976
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/976
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/976
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/965/console
    FAILURE: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/965/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/18/rebuild

review: Needs Fixing (continuous-integration)
184. By Renato Araujo Oliveira Filho

Fix utc conversion.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:184
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/19/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1084
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/221
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1084
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/981
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/981
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/981
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/970/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/970
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/970/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/19/rebuild

review: Approve (continuous-integration)
185. By Renato Araujo Oliveira Filho

Retrieve changed events one miture before last sync.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:185
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/20/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1088
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/223
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1088
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/985
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/985
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/985
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/974/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/974
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/974/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/20/rebuild

review: Approve (continuous-integration)
186. By Renato Araujo Oliveira Filho

Fix startDate.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:186
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/21/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1089
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/224
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1089
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/986
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/986
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/986
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/975/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/975
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/975/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/21/rebuild

review: Approve (continuous-integration)
187. By Renato Araujo Oliveira Filho

Query for changes in localtime format if UTC returns empty values.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:187
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/22/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1090
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/225
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1090
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/987
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/987
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/987
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/976/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/976
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/976/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/22/rebuild

review: Approve (continuous-integration)
188. By Renato Araujo Oliveira Filho

Query events using UTC time.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:188
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/23/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1101
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/227
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1101
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/998
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/998
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/998
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/987/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/987
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/987/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/23/rebuild

review: Approve (continuous-integration)
189. By Renato Araujo Oliveira Filho

Rever changes on account_manager.go

190. By Renato Araujo Oliveira Filho

Format code.

191. By Renato Araujo Oliveira Filho

Merged: lp:~mardy/account-polld/dekko-gmail

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :

PASSED: Continuous integration, rev:190
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/24/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build/1119
    SUCCESS: https://jenkins.canonical.com/system-apps/job/test-0-autopkgtest/label=phone-armhf,release=vivid+overlay,testname=default/228
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-0-fetch/1119
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=vivid+overlay/1014
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=xenial+overlay/1014
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-1-sourcepkg/release=yakkety/1014
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=vivid+overlay/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=xenial+overlay/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=amd64,release=yakkety/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=vivid+overlay/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=xenial+overlay/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=armhf,release=yakkety/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=vivid+overlay/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=xenial+overlay/1003/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1003
        deb: https://jenkins.canonical.com/system-apps/job/build-2-binpkg/arch=i386,release=yakkety/1003/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/system-apps/job/lp-account-polld-ci/24/rebuild

review: Approve (continuous-integration)
192. By Renato Araujo Oliveira Filho

Remove debug messages.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'accounts/account-watcher.c'
2--- accounts/account-watcher.c 2016-04-19 19:58:25 +0000
3+++ accounts/account-watcher.c 2016-08-02 14:37:44 +0000
4@@ -31,12 +31,12 @@
5 struct _AccountWatcher {
6 AgManager *manager;
7 /* A hash table of the enabled accounts we know of.
8- * Keys are account ID integers, and AccountInfo structs as values.
9+ * Keys are "<accountId>/<serviceName>", and AccountInfo structs as values.
10 */
11 GHashTable *services;
12
13- gulong enabled_event_signal_id;
14- gulong account_deleted_signal_id;
15+ /* List of supported services' IDs */
16+ GSList *supported_services;
17
18 AccountEnabledCallback callback;
19 void *user_data;
20@@ -51,9 +51,7 @@
21 GVariant *auth_params;
22 GVariant *session_data;
23
24- gulong enabled_signal_id;
25 AgAccountId account_id;
26- gboolean enabled; /* the last known state of the account */
27 };
28
29 static void account_info_clear_login(AccountInfo *info) {
30@@ -74,11 +72,6 @@
31
32 static void account_info_free(AccountInfo *info) {
33 account_info_clear_login(info);
34- if (info->enabled_signal_id != 0) {
35- g_signal_handler_disconnect(
36- info->account_service, info->enabled_signal_id);
37- }
38- info->enabled_signal_id = 0;
39 if (info->account_service) {
40 g_object_unref(info->account_service);
41 info->account_service = NULL;
42@@ -94,6 +87,8 @@
43 char *client_secret = NULL;
44 char *access_token = NULL;
45 char *token_secret = NULL;
46+ char *secret = NULL;
47+ char *user_name = NULL;
48
49 if (info->auth_params != NULL) {
50 /* Look up OAuth 2 parameters, falling back to OAuth 1 names */
51@@ -109,6 +104,8 @@
52 if (info->session_data != NULL) {
53 g_variant_lookup(info->session_data, "AccessToken", "&s", &access_token);
54 g_variant_lookup(info->session_data, "TokenSecret", "&s", &token_secret);
55+ g_variant_lookup(info->session_data, "Secret", "&s", &secret);
56+ g_variant_lookup(info->session_data, "UserName", "&s", &user_name);
57 }
58
59 info->watcher->callback(info->watcher,
60@@ -116,11 +113,13 @@
61 service_type,
62 service_name,
63 error,
64- info->enabled,
65+ TRUE,
66 client_id,
67 client_secret,
68 access_token,
69 token_secret,
70+ user_name,
71+ secret,
72 info->watcher->user_data);
73 }
74
75@@ -178,24 +177,6 @@
76 ag_auth_data_unref(auth_data);
77 }
78
79-static void account_info_enabled_cb(
80- AgAccountService *account_service, gboolean enabled, AccountInfo *info) {
81- trace("account_info_enabled_cb for %u, enabled=%d\n", info->account_id, enabled);
82- if (info->enabled == enabled) {
83- /* no change */
84- return;
85- }
86- info->enabled = enabled;
87-
88- if (enabled) {
89- account_info_login(info);
90- } else {
91- account_info_clear_login(info);
92- // Send notification that account has been disabled */
93- account_info_notify(info, NULL);
94- }
95-}
96-
97 static AccountInfo *account_info_new(AccountWatcher *watcher, AgAccountService *account_service) {
98 AccountInfo *info = g_new0(AccountInfo, 1);
99 info->watcher = watcher;
100@@ -204,99 +185,105 @@
101 AgAccount *account = ag_account_service_get_account(account_service);
102 g_object_get(account, "id", &info->account_id, NULL);
103
104- info->enabled_signal_id = g_signal_connect(
105- account_service, "enabled",
106- G_CALLBACK(account_info_enabled_cb), info);
107- // Set initial state
108- account_info_enabled_cb(account_service, ag_account_service_get_enabled(account_service), info);
109-
110 return info;
111 }
112
113-static void account_watcher_enabled_event_cb(
114- AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {
115- trace("enabled-event for %u\n", account_id);
116- if (g_hash_table_contains(watcher->services, GUINT_TO_POINTER(account_id))) {
117- /* We are already tracking this account */
118- return;
119- }
120- AgAccount *account = ag_manager_get_account(manager, account_id);
121- if (account == NULL) {
122- /* There was a problem looking up the account */
123- return;
124- }
125- /* Since our AgManager is restricted to a particular service type,
126- * pick the first service for the account. */
127- GList *services = ag_account_list_services(account);
128- if (services != NULL) {
129- AgService *service = services->data;
130- AgAccountService *account_service = ag_account_service_new(
131- account, service);
132- AccountInfo *info = account_info_new(watcher, account_service);
133- g_object_unref(account_service);
134- g_hash_table_insert(watcher->services, GUINT_TO_POINTER(account_id), info);
135- }
136- ag_service_list_free(services);
137- g_object_unref(account);
138-}
139-
140-static void account_watcher_account_deleted_cb(
141- AgManager *manager, AgAccountId account_id, AccountWatcher *watcher) {
142- trace("account-deleted for %u\n", account_id);
143- /* A disabled event should have been sent prior to this, so no
144- * need to send any notification. */
145- g_hash_table_remove(watcher->services, GUINT_TO_POINTER(account_id));
146+static gboolean service_is_supported(AccountWatcher *watcher,
147+ const char *service_id)
148+{
149+ GSList *node = g_slist_find_custom(watcher->supported_services,
150+ service_id,
151+ (GCompareFunc)g_strcmp0);
152+ return node != NULL;
153 }
154
155 static gboolean account_watcher_setup(void *user_data) {
156 AccountWatcher *watcher = (AccountWatcher *)user_data;
157
158- /* Track changes to accounts */
159- watcher->enabled_event_signal_id = g_signal_connect(
160- watcher->manager, "enabled-event",
161- G_CALLBACK(account_watcher_enabled_event_cb), watcher);
162- watcher->account_deleted_signal_id = g_signal_connect(
163- watcher->manager, "account-deleted",
164- G_CALLBACK(account_watcher_account_deleted_cb), watcher);
165-
166 /* Now check initial state */
167- GList *enabled_accounts = ag_manager_list(watcher->manager);
168+ GList *enabled_accounts =
169+ ag_manager_get_enabled_account_services(watcher->manager);
170+ GList *old_services = g_hash_table_get_keys(watcher->services);
171+
172+ /* Update the services table */
173 GList *l;
174 for (l = enabled_accounts; l != NULL; l = l->next) {
175- AgAccountId account_id = GPOINTER_TO_UINT(l->data);
176- account_watcher_enabled_event_cb(watcher->manager, account_id, watcher);
177- }
178- ag_manager_list_free(enabled_accounts);
179+ AgAccountService *account_service = l->data;
180+ AgAccountId id = ag_account_service_get_account(account_service)->id;
181+ AgService *service = ag_account_service_get_service(account_service);
182+ const char *service_id = ag_service_get_name(service);
183+
184+ if (!service_is_supported(watcher, service_id)) continue;
185+
186+ char *key = g_strdup_printf("%d/%s", id, service_id);
187+
188+ AccountInfo *info = g_hash_table_lookup(watcher->services, key);
189+ if (info) {
190+ GList *node = g_list_find_custom(old_services, key,
191+ (GCompareFunc)g_strcmp0);
192+ old_services = g_list_remove_link(old_services, node);
193+ g_free(key);
194+ } else {
195+ trace("adding account %s\n", key);
196+ info = account_info_new(watcher, account_service);
197+ g_hash_table_insert(watcher->services, key, info);
198+ }
199+ account_info_login(info);
200+ }
201+ g_list_free_full(enabled_accounts, g_object_unref);
202+
203+ /* Remove from the table the accounts which are no longer enabled */
204+ for (l = old_services; l != NULL; l = l->next) {
205+ char *key = l->data;
206+ trace("removing account %s\n", key);
207+ g_hash_table_remove(watcher->services, key);
208+ }
209+ g_list_free(old_services);
210
211 return G_SOURCE_REMOVE;
212 }
213
214-AccountWatcher *account_watcher_new(const char *service_type,
215- AccountEnabledCallback callback,
216+AccountWatcher *account_watcher_new(AccountEnabledCallback callback,
217 void *user_data) {
218 AccountWatcher *watcher = g_new0(AccountWatcher, 1);
219
220- watcher->manager = ag_manager_new_for_service_type(service_type);
221+ watcher->manager = ag_manager_new();
222 watcher->services = g_hash_table_new_full(
223- g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)account_info_free);
224+ g_str_hash, g_str_equal, g_free, (GDestroyNotify)account_info_free);
225+ watcher->supported_services = NULL;
226 watcher->callback = callback;
227 watcher->user_data = user_data;
228
229+ return watcher;
230+}
231+
232+void account_watcher_add_service(AccountWatcher *watcher,
233+ char *serviceId) {
234+ watcher->supported_services =
235+ g_slist_prepend(watcher->supported_services, serviceId);
236+}
237+
238+void account_watcher_run(AccountWatcher *watcher) {
239 /* Make sure main setup occurs within the mainloop thread */
240 g_idle_add(account_watcher_setup, watcher);
241- return watcher;
242 }
243
244 struct refresh_info {
245 AccountWatcher *watcher;
246 AgAccountId account_id;
247+ char *service_name;
248 };
249
250+static void refresh_info_free(struct refresh_info *data) {
251+ g_free(data->service_name);
252+ g_free(data);
253+}
254+
255 static gboolean account_watcher_refresh_cb(void *user_data) {
256 struct refresh_info *data = (struct refresh_info *)user_data;
257
258- AccountInfo *info = g_hash_table_lookup(
259- data->watcher->services, GUINT_TO_POINTER(data->account_id));
260+ char *key = g_strdup_printf("%d/%s", data->account_id, data->service_name);
261+ AccountInfo *info = g_hash_table_lookup(data->watcher->services, key);
262 if (info != NULL) {
263 account_info_login(info);
264 }
265@@ -304,10 +291,12 @@
266 return G_SOURCE_REMOVE;
267 }
268
269-void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id) {
270+void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id,
271+ const char *service_name) {
272 struct refresh_info *data = g_new(struct refresh_info, 1);
273 data->watcher = watcher;
274 data->account_id = account_id;
275+ data->service_name = g_strdup(service_name);
276 g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, account_watcher_refresh_cb,
277- data, g_free);
278+ data, (GDestroyNotify)refresh_info_free);
279 }
280
281=== modified file 'accounts/account-watcher.h'
282--- accounts/account-watcher.h 2016-04-19 19:58:25 +0000
283+++ accounts/account-watcher.h 2016-08-02 14:37:44 +0000
284@@ -30,12 +30,18 @@
285 const char *client_secret,
286 const char *access_token,
287 const char *token_secret,
288+ const char *user_name,
289+ const char *secret,
290 void *user_data);
291
292-AccountWatcher *account_watcher_new(const char *service_type,
293- AccountEnabledCallback callback,
294+AccountWatcher *account_watcher_new(AccountEnabledCallback callback,
295 void *user_data);
296+void account_watcher_add_service(AccountWatcher *watcher,
297+ char *serviceId);
298+void account_watcher_run(AccountWatcher *watcher);
299
300-void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id);
301+void account_watcher_refresh(AccountWatcher *watcher,
302+ unsigned int account_id,
303+ const char *service_name);
304
305 #endif
306
307=== modified file 'accounts/accounts.c'
308--- accounts/accounts.c 2016-04-19 19:58:25 +0000
309+++ accounts/accounts.c 2016-08-02 14:37:44 +0000
310@@ -1,6 +1,6 @@
311 #include "_cgo_export.h"
312
313-AccountWatcher *watch_for_service_type(const char *service_type) {
314+AccountWatcher *watch() {
315 /* Transfer service names to hash table */
316 if (FALSE) {
317 /* The Go callback doesn't quite match the
318@@ -16,10 +16,11 @@
319 GError *error, int enabled,
320 char *client_id, char *client_secret,
321 char *access_token, char *token_secret,
322+ char *user_name, char *secret,
323 void *user_data) = authCallback;
324 }
325
326 AccountWatcher *watcher = account_watcher_new(
327- service_type, (AccountEnabledCallback)authCallback, NULL);
328+ (AccountEnabledCallback)authCallback, NULL);
329 return watcher;
330 }
331
332=== modified file 'accounts/accounts.go'
333--- accounts/accounts.go 2016-04-19 19:58:25 +0000
334+++ accounts/accounts.go 2016-08-02 14:37:44 +0000
335@@ -22,7 +22,7 @@
336 #include <glib.h>
337 #include "account-watcher.h"
338
339-AccountWatcher *watch_for_service_type(const char *service_type);
340+AccountWatcher *watch();
341 */
342 import "C"
343 import (
344@@ -47,6 +47,8 @@
345 ClientSecret string
346 AccessToken string
347 TokenSecret string
348+ Secret string
349+ UserName string
350 }
351
352 var (
353@@ -54,12 +56,10 @@
354 authChannelsLock sync.Mutex
355 )
356
357-// NewWatcher creates a new account watcher for the given service names
358-func NewWatcher(serviceType string) *Watcher {
359+// NewWatcher creates a new account watcher
360+func NewWatcher() *Watcher {
361 w := new(Watcher)
362- cServiceType := C.CString(serviceType)
363- defer C.free(unsafe.Pointer(cServiceType))
364- w.watcher = C.watch_for_service_type(cServiceType)
365+ w.watcher = C.watch()
366
367 ch := make(chan AuthData)
368 w.C = ch
369@@ -70,14 +70,24 @@
370 return w
371 }
372
373+func (w *Watcher) AddService(serviceId string) {
374+ C.account_watcher_add_service(w.watcher, C.CString(serviceId))
375+}
376+
377+// Walk through the enabled accounts, and get auth tokens for each of them.
378+// The new access token will be delivered over the watcher's channel.
379+func (w *Watcher) Run() {
380+ C.account_watcher_run(w.watcher)
381+}
382+
383 // Refresh requests that the token for the given account be refreshed.
384 // The new access token will be delivered over the watcher's channel.
385-func (w *Watcher) Refresh(accountId uint) {
386- C.account_watcher_refresh(w.watcher, C.uint(accountId))
387+func (w *Watcher) Refresh(accountId uint, serviceName string) {
388+ C.account_watcher_refresh(w.watcher, C.uint(accountId), C.CString(serviceName))
389 }
390
391 //export authCallback
392-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, userData unsafe.Pointer) {
393+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) {
394 // Ideally the first argument would be of type
395 // *C.AccountWatcher, but that fails with Go 1.2.
396 authChannelsLock.Lock()
397@@ -110,5 +120,11 @@
398 if tokenSecret != nil {
399 data.TokenSecret = C.GoString(tokenSecret)
400 }
401+ if secret != nil {
402+ data.Secret = C.GoString(secret)
403+ }
404+ if userName != nil {
405+ data.UserName = C.GoString(userName)
406+ }
407 ch <- data
408 }
409
410=== renamed file 'cmd/account-polld/account_manager.go' => 'cmd/account-polld/account_service.go'
411--- cmd/account-polld/account_manager.go 2016-06-30 19:21:46 +0000
412+++ cmd/account-polld/account_service.go 2016-08-02 14:37:44 +0000
413@@ -26,7 +26,7 @@
414 "launchpad.net/ubuntu-push/click"
415 )
416
417-type AccountManager struct {
418+type AccountService struct {
419 watcher *accounts.Watcher
420 authData accounts.AuthData
421 plugin plugins.Plugin
422@@ -51,8 +51,8 @@
423 clickNotInstalledError = errors.New("Click not installed")
424 )
425
426-func NewAccountManager(watcher *accounts.Watcher, postWatch chan *PostWatch, plugin plugins.Plugin) *AccountManager {
427- return &AccountManager{
428+func NewAccountService(watcher *accounts.Watcher, postWatch chan *PostWatch, plugin plugins.Plugin) *AccountService {
429+ return &AccountService{
430 watcher: watcher,
431 plugin: plugin,
432 postWatch: postWatch,
433@@ -61,20 +61,18 @@
434 }
435 }
436
437-func (a *AccountManager) Delete() {
438+func (a *AccountService) Delete() {
439 close(a.authChan)
440 close(a.doneChan)
441 }
442
443 // Poll() always needs to be called asynchronously as otherwise qtcontacs' GetAvatar()
444 // will raise an error: "QSocketNotifier: Can only be used with threads started with QThread"
445-func (a *AccountManager) Poll(bootstrap bool) {
446+func (a *AccountService) Poll(bootstrap bool) {
447 gotNewAuthData := false
448- if !a.authData.Enabled {
449- if a.authData, gotNewAuthData = <-a.authChan; !gotNewAuthData {
450- log.Println("Account", a.authData.AccountId, "no longer enabled")
451- return
452- }
453+ if a.authData, gotNewAuthData = <-a.authChan; !gotNewAuthData {
454+ log.Println("Account", a.authData.AccountId, "no longer enabled")
455+ return
456 }
457
458 if a.penaltyCount > 0 {
459@@ -126,7 +124,7 @@
460 // and mark the data as disabled.
461 // Do not refresh immediately when we just got new (faulty) auth data as immediately trying
462 // again is probably not going to help. Instead, we wait for the next poll cycle.
463- a.watcher.Refresh(a.authData.AccountId)
464+ a.watcher.Refresh(a.authData.AccountId, a.authData.ServiceName)
465 a.authData.Enabled = false
466 a.authData.Error = err
467 }
468@@ -139,7 +137,7 @@
469 log.Printf("Ending poll for account %d", a.authData.AccountId)
470 }
471
472-func (a *AccountManager) poll() {
473+func (a *AccountService) poll() {
474 log.Println("Polling account", a.authData.AccountId)
475 if !isClickInstalled(a.plugin.ApplicationId()) {
476 log.Println(
477@@ -167,7 +165,7 @@
478 }
479 }
480
481-func (a *AccountManager) updateAuthData(authData accounts.AuthData) {
482+func (a *AccountService) updateAuthData(authData accounts.AuthData) {
483 a.authChan <- authData
484 }
485
486
487=== modified file 'cmd/account-polld/main.go'
488--- cmd/account-polld/main.go 2016-04-21 23:33:48 +0000
489+++ cmd/account-polld/main.go 2016-08-02 14:37:44 +0000
490@@ -27,6 +27,8 @@
491 "launchpad.net/account-polld/accounts"
492 "launchpad.net/account-polld/gettext"
493 "launchpad.net/account-polld/plugins"
494+ "launchpad.net/account-polld/plugins/caldav"
495+ "launchpad.net/account-polld/plugins/dekko"
496 "launchpad.net/account-polld/plugins/gcalendar"
497 "launchpad.net/account-polld/plugins/gmail"
498 "launchpad.net/account-polld/plugins/twitter"
499@@ -41,19 +43,18 @@
500 }
501
502 type AccountKey struct {
503- serviceType string
504+ serviceId string
505 accountId uint
506 }
507
508 /* Use identifiers and API keys provided by the respective webapps which are the official
509 end points for the notifications */
510 const (
511- SERVICETYPE_WEBAPPS = "webapps"
512- SERVICETYPE_CALENDAR = "calendar"
513-
514+ SERVICENAME_DEKKO = "dekko.dekkoproject_dekko"
515 SERVICENAME_GMAIL = "com.ubuntu.developer.webapps.webapp-gmail_webapp-gmail"
516 SERVICENAME_TWITTER = "com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter"
517 SERVICENAME_GCALENDAR = "google-caldav"
518+ SERVICENAME_OCALENDAR = "owncloud-caldav"
519 )
520
521 const (
522@@ -101,16 +102,18 @@
523 }
524
525 func monitorAccounts(postWatch chan *PostWatch, pollBus *pollbus.PollBus) {
526- watchers := make(map[string]*accounts.Watcher)
527- watchers[SERVICETYPE_WEBAPPS] = accounts.NewWatcher(SERVICETYPE_WEBAPPS)
528- watchers[SERVICETYPE_CALENDAR] = accounts.NewWatcher(SERVICETYPE_CALENDAR)
529+ watcher := accounts.NewWatcher()
530+ watcher.AddService(SERVICENAME_DEKKO)
531+ watcher.AddService(SERVICENAME_GMAIL)
532+ watcher.AddService(SERVICENAME_GCALENDAR)
533+ watcher.AddService(SERVICENAME_TWITTER)
534
535- mgr := make(map[AccountKey]*AccountManager)
536+ mgr := make(map[AccountKey]*AccountService)
537
538 var wg sync.WaitGroup
539
540 pullAccount := func(data accounts.AuthData) bool {
541- accountKey := AccountKey{data.ServiceType, data.AccountId}
542+ accountKey := AccountKey{data.ServiceName, data.AccountId}
543 if account, ok := mgr[accountKey]; ok {
544 if data.Enabled {
545 log.Println("New account data for existing account with id", data.AccountId)
546@@ -131,8 +134,11 @@
547 }
548 } else if data.Enabled {
549 var plugin plugins.Plugin
550- log.Println("Creat plugin for service: ", data.ServiceName)
551+ log.Println("Creating plugin for service: ", data.ServiceName)
552 switch data.ServiceName {
553+ case SERVICENAME_DEKKO:
554+ log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)
555+ plugin = dekko.New(data.AccountId)
556 case SERVICENAME_GMAIL:
557 log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)
558 plugin = gmail.New(data.AccountId)
559@@ -143,11 +149,14 @@
560 // This is just stubbed until the plugin exists.
561 log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)
562 plugin = twitter.New()
563+ case SERVICENAME_OCALENDAR:
564+ log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)
565+ plugin = caldav.New(data.AccountId)
566 default:
567 log.Println("Unhandled account with id", data.AccountId, "for", data.ServiceName)
568 return false
569 }
570- mgr[accountKey] = NewAccountManager(watchers[data.ServiceType], postWatch, plugin)
571+ mgr[accountKey] = NewAccountService(watcher, postWatch, plugin)
572 mgr[accountKey].updateAuthData(data)
573 wg.Add(1)
574 go func() {
575@@ -165,27 +174,14 @@
576 L:
577 for {
578 select {
579- case data := <-watchers[SERVICETYPE_CALENDAR].C:
580- if pullAccount(data) == false {
581- continue L
582- }
583- case data := <-watchers[SERVICETYPE_WEBAPPS].C:
584- if pullAccount(data) == false {
585+ case data := <-watcher.C:
586+ if pullAccount(data) == false {
587+ log.Println("pullAccount returned false, continuing")
588 continue L
589 }
590 case <-pollBus.PollChan:
591 wg.Wait() // Finish all running Poll() calls before potentially polling the same accounts again
592- for _, v := range mgr {
593- if v.authData.Error != plugins.ErrTokenExpired { // Do not poll if the new token hasn't been loaded yet
594- wg.Add(1)
595- go func(accountManager *AccountManager) {
596- defer wg.Done()
597- accountManager.Poll(false)
598- }(v)
599- } else {
600- log.Println("Skipping account with id", v.authData.AccountId, "as it is refreshing its token")
601- }
602- }
603+ watcher.Run()
604 wg.Wait()
605 pollBus.SignalDone()
606 }
607
608=== modified file 'cmd/account-watcher-test/main.go'
609--- cmd/account-watcher-test/main.go 2014-07-24 05:38:51 +0000
610+++ cmd/account-watcher-test/main.go 2016-08-02 14:37:44 +0000
611@@ -2,14 +2,12 @@
612
613 import (
614 "fmt"
615- "os"
616
617 "launchpad.net/account-polld/accounts"
618 )
619
620 func main() {
621- // Expects a list of service names as command line arguments
622- for data := range accounts.NewWatcher(os.Args[1]).C {
623+ for data := range accounts.NewWatcher().C {
624 if data.Error != nil {
625 fmt.Println("Failed to authenticate account", data.AccountId, ":", data.Error)
626 } else {
627
628=== added directory 'plugins/caldav'
629=== added file 'plugins/caldav/api.go'
630--- plugins/caldav/api.go 1970-01-01 00:00:00 +0000
631+++ plugins/caldav/api.go 2016-08-02 14:37:44 +0000
632@@ -0,0 +1,54 @@
633+/*
634+ Copyright 2014 Canonical Ltd.
635+
636+ This program is free software: you can redistribute it and/or modify it
637+ under the terms of the GNU General Public License version 3, as published
638+ by the Free Software Foundation.
639+
640+ This program is distributed in the hope that it will be useful, but
641+ WITHOUT ANY WARRANTY; without even the implied warranties of
642+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
643+ PURPOSE. See the GNU General Public License for more details.
644+
645+ You should have received a copy of the GNU General Public License along
646+ with this program. If not, see <http://www.gnu.org/licenses/>.
647+*/
648+
649+package caldav
650+
651+import (
652+ "fmt"
653+)
654+
655+// eventList holds a response to call to Calendar.events: list
656+// defined in https://developers.google.com/google-apps/calendar/v3/reference/events/list#response
657+type eventList struct {
658+ // Messages holds a list of message.
659+ Events []event `json:"items"`
660+}
661+
662+// event holds the event data response for a Calendar.event.
663+// The full definition of a message is defined in
664+// https://developers.google.com/google-apps/calendar/v3/reference/events#resource-representations
665+type event struct {
666+ // Id is the immutable ID of the message.
667+ Etag string `json:"etag"`
668+ // ThreadId is the ID of the thread the message belongs to.
669+ Summary string `json:"summary"`
670+}
671+
672+func (e event) String() string {
673+ return fmt.Sprintf("Id: %s, snippet: '%s'\n", e.Etag, e.Summary)
674+}
675+
676+type errorResp struct {
677+ Err struct {
678+ Code uint64 `json:"code"`
679+ Message string `json:"message"`
680+ Errors []struct {
681+ Domain string `json:"domain"`
682+ Reason string `json:"reason"`
683+ Message string `json:"message"`
684+ } `json:"errors"`
685+ } `json:"error"`
686+}
687
688=== added file 'plugins/caldav/caldav.go'
689--- plugins/caldav/caldav.go 1970-01-01 00:00:00 +0000
690+++ plugins/caldav/caldav.go 2016-08-02 14:37:44 +0000
691@@ -0,0 +1,203 @@
692+/*
693+ Copyright 2016 Canonical Ltd.
694+
695+ This program is free software: you can redistribute it and/or modify it
696+ under the terms of the GNU General Public License version 3, as published
697+ by the Free Software Foundation.
698+
699+ This program is distributed in the hope that it will be useful, but
700+ WITHOUT ANY WARRANTY; without even the implied warranties of
701+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
702+ PURPOSE. See the GNU General Public License for more details.
703+
704+ You should have received a copy of the GNU General Public License along
705+ with this program. If not, see <http://www.gnu.org/licenses/>.
706+*/
707+
708+package caldav
709+
710+import (
711+ "bytes"
712+ "fmt"
713+ "io/ioutil"
714+ "log"
715+ "net/http"
716+ "net/url"
717+ "os"
718+ "strings"
719+ "time"
720+
721+ "launchpad.net/account-polld/accounts"
722+ "launchpad.net/account-polld/plugins"
723+ "launchpad.net/account-polld/syncmonitor"
724+)
725+
726+const (
727+ APP_ID = "com.ubuntu.calendar_calendar"
728+ pluginName = "caldav"
729+)
730+
731+type CalDavPlugin struct {
732+ accountId uint
733+}
734+
735+func New(accountId uint) *CalDavPlugin {
736+ return &CalDavPlugin{accountId: accountId}
737+}
738+
739+func (p *CalDavPlugin) ApplicationId() plugins.ApplicationId {
740+ return plugins.ApplicationId(APP_ID)
741+}
742+
743+func (p *CalDavPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) {
744+ // This envvar check is to ease testing.
745+ if token := os.Getenv("ACCOUNT_POLLD_TOKEN_CALDAV"); token != "" {
746+ log.Print("Using token from: ACCOUNT_POLLD_TOKEN_CALDAV env var")
747+ authData.AccessToken = token
748+ }
749+
750+ log.Print("Check calendar changes for account:", p.accountId)
751+
752+ syncMonitor := syncmonitor.NewSyncMonitor()
753+ if syncMonitor == nil {
754+ log.Print("Sync monitor not available yet.")
755+ return nil, nil
756+ }
757+
758+ state, err := syncMonitor.State()
759+ if err != nil {
760+ log.Print("Fail to retrieve sync monitor state ", err)
761+ return nil, nil
762+ }
763+ if state != "idle" {
764+ log.Print("Sync monitor is not on 'idle' state, try later!")
765+ return nil, nil
766+ }
767+
768+ calendars, err := syncMonitor.ListCalendarsByAccount(p.accountId)
769+ if err != nil {
770+ log.Print("Calendar plugin ", p.accountId, ": cannot load calendars: ", err)
771+ return nil, nil
772+ }
773+
774+ var calendarsToSync []string
775+ log.Print("Number of calendars for account:", p.accountId, " size:", len(calendars))
776+
777+ for id, calendar := range calendars {
778+ lastSyncDate, err := syncMonitor.LastSyncDate(p.accountId, id)
779+ if err != nil {
780+ log.Print("\tcalendar: ", id, ", cannot load previous sync date: ", err, ". Try next time.")
781+ continue
782+ } else {
783+ log.Print("\tcalendar: ", id, " Url: ", calendar, " last sync date: ", lastSyncDate)
784+ }
785+
786+ var needSync bool
787+ needSync = (len(lastSyncDate) == 0)
788+
789+ if !needSync {
790+ resp, err := p.requestChanges(authData, calendar, lastSyncDate)
791+ if err != nil {
792+ log.Print("\tERROR: Fail to query for changes: ", err)
793+ continue
794+ }
795+
796+ needSync, err = p.containEvents(resp)
797+ if err != nil {
798+ log.Print("\tERROR: Fail to parse changes: ", err)
799+ if err == plugins.ErrTokenExpired {
800+ log.Print("\t\tAbort poll")
801+ return nil, err
802+ } else {
803+ continue
804+ }
805+ }
806+ }
807+
808+ if needSync {
809+ log.Print("\tCalendar needs sync: ", id)
810+ calendarsToSync = append(calendarsToSync, id)
811+ } else {
812+ log.Print("\tFound no calendar updates for account: ", p.accountId, " calendar: ", id)
813+ }
814+ }
815+
816+ if len(calendarsToSync) > 0 {
817+ log.Print("Request account sync")
818+ err = syncMonitor.SyncAccount(p.accountId, calendarsToSync)
819+ if err != nil {
820+ log.Print("ERROR: Fail to start account sync ", p.accountId, " message: ", err)
821+ }
822+ }
823+
824+ return nil, nil
825+}
826+
827+func (p *CalDavPlugin) containEvents(resp *http.Response) (bool, error) {
828+ defer resp.Body.Close()
829+ log.Print("RESPONSE CODE ----:", resp.StatusCode)
830+
831+ if resp.StatusCode != 207 {
832+ var errResp errorResp
833+ log.Print("Invalid response:", errResp.Err.Code)
834+ return false, nil
835+ } else {
836+ data, err := ioutil.ReadAll(resp.Body)
837+ if err != nil {
838+ return false, err
839+ }
840+ fmt.Printf("DATA: %s", data)
841+ return strings.Contains(string(data), "BEGIN:VEVENT"), nil
842+ }
843+
844+ return false, nil
845+}
846+
847+func (p *CalDavPlugin) requestChanges(authData *accounts.AuthData, calendar string, lastSyncDate string) (*http.Response, error) {
848+ u, err := url.Parse(calendar)
849+ if err != nil {
850+ return nil, err
851+ }
852+ startDate, err := time.Parse(time.RFC3339, lastSyncDate)
853+ if err != nil {
854+ log.Print("Fail to parse date: ", lastSyncDate)
855+ return nil, err
856+ }
857+
858+ // Start date will be one minute before last sync
859+ startDate = startDate.Add(time.Duration(-1) * time.Minute)
860+
861+ // End Date will be one year in the future from now
862+ endDate := time.Now().AddDate(1, 0, 0).UTC()
863+
864+ log.Print("Calendar Url:", calendar)
865+ //u.Path += "/remote.php/caldav/calendars/renatox@gmail.com/" + calendar
866+
867+ //GET https://my.owndrive.com:443/remote.php/caldav/calendars/renatox%40gmail.com/teste/
868+ query := "<c:calendar-query xmlns:d=\"DAV:\" xmlns:c=\"urn:ietf:params:xml:ns:caldav\">\n"
869+ query += "<d:prop>\n"
870+ query += "<d:getetag />\n"
871+ query += "<c:calendar-data />\n"
872+ query += "</d:prop>\n"
873+ query += "<c:filter>\n"
874+ query += "<c:comp-filter name=\"VCALENDAR\">\n"
875+ query += "<c:comp-filter name=\"VEVENT\">\n"
876+ query += "<c:prop-filter name=\"LAST-MODIFIED\">\n"
877+ query += "<c:time-range start=\"" + startDate.Format("20060102T150405Z") + "\" end=\"" + endDate.Format("20060102T150405Z") + "\"/>\n"
878+ query += "</c:prop-filter>\n"
879+ query += "</c:comp-filter>\n"
880+ query += "</c:comp-filter>\n"
881+ query += "</c:filter>\n"
882+ query += "</c:calendar-query>\n"
883+ log.Print("Query: ", query)
884+ req, err := http.NewRequest("REPORT", u.String(), bytes.NewBufferString(query))
885+ if err != nil {
886+ return nil, err
887+ }
888+ req.Header.Set("Depth", "1")
889+ req.Header.Set("Prefer", "return-minimal")
890+ req.Header.Set("Content-Type", "application/xml; charset=utf-8")
891+ req.SetBasicAuth(authData.UserName, authData.Secret)
892+
893+ return http.DefaultClient.Do(req)
894+}
895
896=== added directory 'plugins/dekko'
897=== added file 'plugins/dekko/api.go'
898--- plugins/dekko/api.go 1970-01-01 00:00:00 +0000
899+++ plugins/dekko/api.go 2016-08-02 14:37:44 +0000
900@@ -0,0 +1,127 @@
901+/*
902+ Copyright 2014 Canonical Ltd.
903+
904+ This program is free software: you can redistribute it and/or modify it
905+ under the terms of the GNU General Public License version 3, as published
906+ by the Free Software Foundation.
907+
908+ This program is distributed in the hope that it will be useful, but
909+ WITHOUT ANY WARRANTY; without even the implied warranties of
910+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
911+ PURPOSE. See the GNU General Public License for more details.
912+
913+ You should have received a copy of the GNU General Public License along
914+ with this program. If not, see <http://www.gnu.org/licenses/>.
915+*/
916+
917+package dekko
918+
919+import (
920+ "fmt"
921+ "time"
922+
923+ "launchpad.net/account-polld/plugins"
924+)
925+
926+const gmailTime = "Mon, 2 Jan 2006 15:04:05 -0700"
927+
928+type pushes map[string]*plugins.PushMessage
929+type headers map[string]string
930+
931+// messageList holds a response to call to Users.messages: list
932+// defined in https://developers.google.com/gmail/api/v1/reference/users/messages/list
933+type messageList struct {
934+ // Messages holds a list of message.
935+ Messages []message `json:"messages"`
936+ // NextPageToken is used to retrieve the next page of results in the list.
937+ NextPageToken string `json:"nextPageToken"`
938+ // ResultSizeEstimage is the estimated total number of results.
939+ ResultSizeEstimage uint64 `json:"resultSizeEstimate"`
940+}
941+
942+// message holds a partial response for a Users.messages.
943+// The full definition of a message is defined in
944+// https://developers.google.com/gmail/api/v1/reference/users/messages#resource
945+type message struct {
946+ // Id is the immutable ID of the message.
947+ Id string `json:"id"`
948+ // ThreadId is the ID of the thread the message belongs to.
949+ ThreadId string `json:"threadId"`
950+ // HistoryId is the ID of the last history record that modified
951+ // this message.
952+ HistoryId string `json:"historyId"`
953+ // Snippet is a short part of the message text. This text is
954+ // used for the push message summary.
955+ Snippet string `json:"snippet"`
956+ // Payload represents the message payload.
957+ Payload payload `json:"payload"`
958+}
959+
960+func (m message) String() string {
961+ return fmt.Sprintf("Id: %d, snippet: '%s'\n", m.Id, m.Snippet[:10])
962+}
963+
964+// ById implements sort.Interface for []message based on
965+// the Id field.
966+type byId []message
967+
968+func (m byId) Len() int { return len(m) }
969+func (m byId) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
970+func (m byId) Less(i, j int) bool { return m[i].Id < m[j].Id }
971+
972+// payload represents the message payload.
973+type payload struct {
974+ Headers []messageHeader `json:"headers"`
975+}
976+
977+func (p *payload) mapHeaders() headers {
978+ headers := make(map[string]string)
979+ for _, hdr := range p.Headers {
980+ headers[hdr.Name] = hdr.Value
981+ }
982+ return headers
983+}
984+
985+func (hdr headers) getTimestamp() time.Time {
986+ timestamp, ok := hdr[hdrDATE]
987+ if !ok {
988+ return time.Now()
989+ }
990+
991+ if t, err := time.Parse(gmailTime, timestamp); err == nil {
992+ return t
993+ }
994+ return time.Now()
995+}
996+
997+func (hdr headers) getEpoch() int64 {
998+ return hdr.getTimestamp().Unix()
999+}
1000+
1001+// messageHeader represents the message headers.
1002+type messageHeader struct {
1003+ Name string `json:"name"`
1004+ Value string `json:"value"`
1005+}
1006+
1007+type errorResp struct {
1008+ Err struct {
1009+ Code uint64 `json:"code"`
1010+ Message string `json:"message"`
1011+ Errors []struct {
1012+ Domain string `json:"domain"`
1013+ Reason string `json:"reason"`
1014+ Message string `json:"message"`
1015+ } `json:"errors"`
1016+ } `json:"error"`
1017+}
1018+
1019+func (err *errorResp) Error() string {
1020+ return fmt.Sprint("backend response:", err.Err.Message)
1021+}
1022+
1023+const (
1024+ hdrDATE = "Date"
1025+ hdrSUBJECT = "Subject"
1026+ hdrFROM = "From"
1027+)
1028
1029=== added file 'plugins/dekko/dekko.go'
1030--- plugins/dekko/dekko.go 1970-01-01 00:00:00 +0000
1031+++ plugins/dekko/dekko.go 2016-08-02 14:37:44 +0000
1032@@ -0,0 +1,346 @@
1033+/*
1034+ Copyright 2014 Canonical Ltd.
1035+
1036+ This program is free software: you can redistribute it and/or modify it
1037+ under the terms of the GNU General Public License version 3, as published
1038+ by the Free Software Foundation.
1039+
1040+ This program is distributed in the hope that it will be useful, but
1041+ WITHOUT ANY WARRANTY; without even the implied warranties of
1042+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1043+ PURPOSE. See the GNU General Public License for more details.
1044+
1045+ You should have received a copy of the GNU General Public License along
1046+ with this program. If not, see <http://www.gnu.org/licenses/>.
1047+*/
1048+
1049+package dekko
1050+
1051+import (
1052+ "encoding/json"
1053+ "fmt"
1054+ "net/http"
1055+ "net/mail"
1056+ "net/url"
1057+ "os"
1058+ "regexp"
1059+ "sort"
1060+ "strings"
1061+ "time"
1062+
1063+ "log"
1064+
1065+ "launchpad.net/account-polld/accounts"
1066+ "launchpad.net/account-polld/gettext"
1067+ "launchpad.net/account-polld/plugins"
1068+ "launchpad.net/account-polld/qtcontact"
1069+)
1070+
1071+const (
1072+ APP_ID = "dekko.dekkoproject_dekko"
1073+ dekkoDispatchUrl = "dekko://notify/%d/%s/%s"
1074+ // If there's more than 10 emails in one batch, we don't show 10 notification
1075+ // bubbles, but instead show one summary. We always show all notifications in the
1076+ // indicator.
1077+ individualNotificationsLimit = 10
1078+ pluginName = "dekko"
1079+)
1080+
1081+type reportedIdMap map[string]time.Time
1082+
1083+var baseUrl, _ = url.Parse("https://www.googleapis.com/gmail/v1/users/me/")
1084+
1085+// timeDelta defines how old messages can be to be reported.
1086+var timeDelta = time.Duration(time.Hour * 24)
1087+
1088+// trackDelta defines how old messages can be before removed from tracking
1089+var trackDelta = time.Duration(time.Hour * 24 * 7)
1090+
1091+// relativeTimeDelta is the same as timeDelta
1092+var relativeTimeDelta string = "1d"
1093+
1094+// regexp for identifying non-ascii characters
1095+var nonAsciiChars, _ = regexp.Compile("[^\x00-\x7F]")
1096+
1097+type GmailPlugin struct {
1098+ // reportedIds holds the messages that have already been notified. This
1099+ // approach is taken against timestamps as it avoids needing to call
1100+ // get on the message.
1101+ reportedIds reportedIdMap
1102+ accountId uint
1103+}
1104+
1105+func idsFromPersist(accountId uint) (ids reportedIdMap, err error) {
1106+ err = plugins.FromPersist(pluginName, accountId, &ids)
1107+ if err != nil {
1108+ return nil, err
1109+ }
1110+ // discard old ids
1111+ timestamp := time.Now()
1112+ for k, v := range ids {
1113+ delta := timestamp.Sub(v)
1114+ if delta > trackDelta {
1115+ log.Print("gmail plugin ", accountId, ": deleting ", k, " as ", delta, " is greater than ", trackDelta)
1116+ delete(ids, k)
1117+ }
1118+ }
1119+ return ids, nil
1120+}
1121+
1122+func (ids reportedIdMap) persist(accountId uint) (err error) {
1123+ err = plugins.Persist(pluginName, accountId, ids)
1124+ if err != nil {
1125+ log.Print("gmail plugin ", accountId, ": failed to save state: ", err)
1126+ }
1127+ return nil
1128+}
1129+
1130+func New(accountId uint) *GmailPlugin {
1131+ reportedIds, err := idsFromPersist(accountId)
1132+ if err != nil {
1133+ log.Print("gmail plugin ", accountId, ": cannot load previous state from storage: ", err)
1134+ } else {
1135+ log.Print("gmail plugin ", accountId, ": last state loaded from storage")
1136+ }
1137+ return &GmailPlugin{reportedIds: reportedIds, accountId: accountId}
1138+}
1139+
1140+func (p *GmailPlugin) ApplicationId() plugins.ApplicationId {
1141+ return plugins.ApplicationId(APP_ID)
1142+}
1143+
1144+func (p *GmailPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) {
1145+ // This envvar check is to ease testing.
1146+ if token := os.Getenv("ACCOUNT_POLLD_TOKEN_GMAIL"); token != "" {
1147+ authData.AccessToken = token
1148+ }
1149+
1150+ resp, err := p.requestMessageList(authData.AccessToken)
1151+ if err != nil {
1152+ return nil, err
1153+ }
1154+ messages, err := p.parseMessageListResponse(resp)
1155+ if err != nil {
1156+ return nil, err
1157+ }
1158+
1159+ // TODO use the batching API defined in https://developers.google.com/gmail/api/guides/batch
1160+ for i := range messages {
1161+ resp, err := p.requestMessage(messages[i].Id, authData.AccessToken)
1162+ if err != nil {
1163+ return nil, err
1164+ }
1165+ messages[i], err = p.parseMessageResponse(resp)
1166+ if err != nil {
1167+ return nil, err
1168+ }
1169+ }
1170+ notif, err := p.createNotifications(messages)
1171+ if err != nil {
1172+ return nil, err
1173+ }
1174+ return []*plugins.PushMessageBatch{
1175+ &plugins.PushMessageBatch{
1176+ Messages: notif,
1177+ Limit: individualNotificationsLimit,
1178+ OverflowHandler: p.handleOverflow,
1179+ Tag: "dekko",
1180+ }}, nil
1181+
1182+}
1183+
1184+func (p *GmailPlugin) reported(id string) bool {
1185+ _, ok := p.reportedIds[id]
1186+ return ok
1187+}
1188+
1189+func (p *GmailPlugin) createNotifications(messages []message) ([]*plugins.PushMessage, error) {
1190+ timestamp := time.Now()
1191+ pushMsgMap := make(pushes)
1192+
1193+ for _, msg := range messages {
1194+ hdr := msg.Payload.mapHeaders()
1195+
1196+ from := hdr[hdrFROM]
1197+ var avatarPath string
1198+
1199+ emailAddress, err := mail.ParseAddress(from)
1200+ if err != nil {
1201+ // If the email address contains non-ascii characters, we get an
1202+ // error so we're going to try again, this time mangling the name
1203+ // by removing all non-ascii characters. We only care about the email
1204+ // address here anyway.
1205+ // XXX: We can't check the error message due to [1]: the error
1206+ // message is different in go < 1.3 and > 1.5.
1207+ // [1] https://github.com/golang/go/issues/12492
1208+ mangledAddr := nonAsciiChars.ReplaceAllString(from, "")
1209+ mangledEmail, mangledParseError := mail.ParseAddress(mangledAddr)
1210+ if mangledParseError == nil {
1211+ emailAddress = mangledEmail
1212+ }
1213+ } else if emailAddress.Name != "" {
1214+ // We only want the Name if the first ParseAddress
1215+ // call was successful. I.e. we do not want the name
1216+ // from a mangled email address.
1217+ from = emailAddress.Name
1218+ }
1219+
1220+ if emailAddress != nil {
1221+ avatarPath = qtcontact.GetAvatar(emailAddress.Address)
1222+ // If icon path starts with a path separator, assume local file path,
1223+ // encode it and prepend file scheme defined in RFC 1738.
1224+ if strings.HasPrefix(avatarPath, string(os.PathSeparator)) {
1225+ avatarPath = url.QueryEscape(avatarPath)
1226+ avatarPath = "file://" + avatarPath
1227+ }
1228+ }
1229+
1230+ msgStamp := hdr.getTimestamp()
1231+
1232+ if _, ok := pushMsgMap[msg.ThreadId]; ok {
1233+ // TRANSLATORS: the %s is an appended "from" corresponding to an specific email thread
1234+ pushMsgMap[msg.ThreadId].Notification.Card.Summary += fmt.Sprintf(gettext.Gettext(", %s"), from)
1235+ } else if timestamp.Sub(msgStamp) < timeDelta {
1236+ // TRANSLATORS: the %s is the "from" header corresponding to a specific email
1237+ summary := fmt.Sprintf(gettext.Gettext("%s"), from)
1238+ // TRANSLATORS: the first %s refers to the email "subject", the second %s refers "from"
1239+ body := fmt.Sprintf(gettext.Gettext("%s\n%s"), hdr[hdrSUBJECT], msg.Snippet)
1240+ // fmt with label personal and threadId
1241+ action := fmt.Sprintf(dekkoDispatchUrl, p.accountId, "INBOX", msg.Id)
1242+ epoch := hdr.getEpoch()
1243+ pushMsgMap[msg.ThreadId] = plugins.NewStandardPushMessage(summary, body, action, avatarPath, epoch)
1244+ } else {
1245+ log.Print("gmail plugin ", p.accountId, ": skipping message id ", msg.Id, " with date ", msgStamp, " older than ", timeDelta)
1246+ }
1247+ }
1248+ pushMsg := make([]*plugins.PushMessage, 0, len(pushMsgMap))
1249+ for _, v := range pushMsgMap {
1250+ pushMsg = append(pushMsg, v)
1251+ }
1252+ return pushMsg, nil
1253+
1254+}
1255+func (p *GmailPlugin) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
1256+ // TODO it would probably be better to grab the estimate that google returns in the message list.
1257+ approxUnreadMessages := len(pushMsg)
1258+
1259+ // TRANSLATORS: the %d refers to the number of new email messages.
1260+ summary := fmt.Sprintf(gettext.Gettext("You have %d new messages"), approxUnreadMessages)
1261+
1262+ body := ""
1263+
1264+ // fmt with label personal and no threadId
1265+ action := fmt.Sprintf(dekkoDispatchUrl, p.accountId, "INBOX")
1266+ epoch := time.Now().Unix()
1267+
1268+ return plugins.NewStandardPushMessage(summary, body, action, "", epoch)
1269+}
1270+
1271+func (p *GmailPlugin) parseMessageListResponse(resp *http.Response) ([]message, error) {
1272+ defer resp.Body.Close()
1273+ decoder := json.NewDecoder(resp.Body)
1274+
1275+ if resp.StatusCode != http.StatusOK {
1276+ var errResp errorResp
1277+ if err := decoder.Decode(&errResp); err != nil {
1278+ return nil, err
1279+ }
1280+ if errResp.Err.Code == 401 {
1281+ return nil, plugins.ErrTokenExpired
1282+ }
1283+ return nil, &errResp
1284+ }
1285+
1286+ var messages messageList
1287+ if err := decoder.Decode(&messages); err != nil {
1288+ return nil, err
1289+ }
1290+
1291+ filteredMsg := p.messageListFilter(messages.Messages)
1292+
1293+ return filteredMsg, nil
1294+}
1295+
1296+// messageListFilter returns a subset of unread messages where the subset
1297+// depends on not being in reportedIds. Before returning, reportedIds is
1298+// updated with the new list of unread messages.
1299+func (p *GmailPlugin) messageListFilter(messages []message) []message {
1300+ sort.Sort(byId(messages))
1301+ var reportMsg []message
1302+ var ids = make(reportedIdMap)
1303+
1304+ for _, msg := range messages {
1305+ if !p.reported(msg.Id) {
1306+ reportMsg = append(reportMsg, msg)
1307+ }
1308+ ids[msg.Id] = time.Now()
1309+ }
1310+ p.reportedIds = ids
1311+ p.reportedIds.persist(p.accountId)
1312+ return reportMsg
1313+}
1314+
1315+func (p *GmailPlugin) parseMessageResponse(resp *http.Response) (message, error) {
1316+ defer resp.Body.Close()
1317+ decoder := json.NewDecoder(resp.Body)
1318+
1319+ if resp.StatusCode != http.StatusOK {
1320+ var errResp errorResp
1321+ if err := decoder.Decode(&errResp); err != nil {
1322+ return message{}, err
1323+ }
1324+ return message{}, &errResp
1325+ }
1326+
1327+ var msg message
1328+ if err := decoder.Decode(&msg); err != nil {
1329+ return message{}, err
1330+ }
1331+
1332+ return msg, nil
1333+}
1334+
1335+func (p *GmailPlugin) requestMessage(id, accessToken string) (*http.Response, error) {
1336+ u, err := baseUrl.Parse("messages/" + id)
1337+ if err != nil {
1338+ return nil, err
1339+ }
1340+
1341+ query := u.Query()
1342+ // only request specific fields
1343+ query.Add("fields", "snippet,threadId,id,payload/headers")
1344+ // get the full message to get From and Subject from headers
1345+ query.Add("format", "full")
1346+ u.RawQuery = query.Encode()
1347+
1348+ req, err := http.NewRequest("GET", u.String(), nil)
1349+ if err != nil {
1350+ return nil, err
1351+ }
1352+ req.Header.Set("Authorization", "Bearer "+accessToken)
1353+
1354+ return http.DefaultClient.Do(req)
1355+}
1356+
1357+func (p *GmailPlugin) requestMessageList(accessToken string) (*http.Response, error) {
1358+ u, err := baseUrl.Parse("messages")
1359+ if err != nil {
1360+ return nil, err
1361+ }
1362+
1363+ query := u.Query()
1364+
1365+ // get all unread inbox emails received after
1366+ // the last time we checked. If this is the first
1367+ // time we check, get unread emails after timeDelta
1368+ query.Add("q", fmt.Sprintf("is:unread in:inbox newer_than:%s", relativeTimeDelta))
1369+ u.RawQuery = query.Encode()
1370+
1371+ req, err := http.NewRequest("GET", u.String(), nil)
1372+ if err != nil {
1373+ return nil, err
1374+ }
1375+ req.Header.Set("Authorization", "Bearer "+accessToken)
1376+
1377+ return http.DefaultClient.Do(req)
1378+}
1379
1380=== modified file 'plugins/gcalendar/gcalendar.go'
1381--- plugins/gcalendar/gcalendar.go 2016-07-12 14:36:52 +0000
1382+++ plugins/gcalendar/gcalendar.go 2016-08-02 14:37:44 +0000
1383@@ -25,6 +25,7 @@
1384
1385 "launchpad.net/account-polld/accounts"
1386 "launchpad.net/account-polld/plugins"
1387+ "launchpad.net/account-polld/syncmonitor"
1388 )
1389
1390 const (
1391@@ -32,7 +33,7 @@
1392 pluginName = "gcalendar"
1393 )
1394
1395-var baseUrl,_ = url.Parse("https://www.googleapis.com/calendar/v3/calendars/")
1396+var baseUrl, _ = url.Parse("https://www.googleapis.com/calendar/v3/calendars/")
1397
1398 type GCalendarPlugin struct {
1399 accountId uint
1400@@ -55,7 +56,7 @@
1401
1402 log.Print("calendar: Check calendar changes for account:", p.accountId)
1403
1404- syncMonitor := NewSyncMonitor()
1405+ syncMonitor := syncmonitor.NewSyncMonitor()
1406 if syncMonitor == nil {
1407 log.Print("calendar: Sync monitor not available yet.")
1408 return nil, nil
1409
1410=== removed file 'plugins/gcalendar/syncmonitor.go'
1411--- plugins/gcalendar/syncmonitor.go 2016-06-23 18:06:35 +0000
1412+++ plugins/gcalendar/syncmonitor.go 1970-01-01 00:00:00 +0000
1413@@ -1,94 +0,0 @@
1414-/*
1415- Copyright 2016 Canonical Ltd.
1416-
1417- This program is free software: you can redistribute it and/or modify it
1418- under the terms of the GNU General Public License version 3, as published
1419- by the Free Software Foundation.
1420-
1421- This program is distributed in the hope that it will be useful, but
1422- WITHOUT ANY WARRANTY; without even the implied warranties of
1423- MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1424- PURPOSE. See the GNU General Public License for more details.
1425-
1426- You should have received a copy of the GNU General Public License along
1427- with this program. If not, see <http://www.gnu.org/licenses/>.
1428-*/
1429-
1430-package gcalendar
1431-
1432-import (
1433- "log"
1434- "runtime"
1435-
1436- "launchpad.net/go-dbus/v1"
1437-)
1438-
1439-const (
1440- busInterface = "com.canonical.SyncMonitor"
1441- busPath = "/com/canonical/SyncMonitor"
1442- busName = "com.canonical.SyncMonitor"
1443-)
1444-
1445-type SyncMonitor struct {
1446- conn *dbus.Connection
1447- obj *dbus.ObjectProxy
1448-}
1449-
1450-func NewSyncMonitor() *SyncMonitor {
1451- conn, err := dbus.Connect(dbus.SessionBus)
1452- if err != nil {
1453- log.Print("Fail to connect with session bus: ", err)
1454- return nil
1455- }
1456-
1457- p := &SyncMonitor{
1458- conn: conn,
1459- obj: conn.Object(busName, busPath),
1460- }
1461- runtime.SetFinalizer(p, clean)
1462- return p
1463-}
1464-
1465-func clean(p *SyncMonitor) {
1466- if p.conn != nil {
1467- p.conn.Close()
1468- }
1469-}
1470-
1471-func (p *SyncMonitor) ListCalendarsByAccount(accountId uint) (calendars map[string]string, err error) {
1472- message, err := p.obj.Call(busInterface, "listCalendarsByAccount", uint32(accountId))
1473- if err != nil {
1474- var calendars map[string]string
1475- return calendars, err
1476- } else {
1477- err = message.Args(&calendars)
1478- return calendars, err
1479- }
1480-}
1481-
1482-func (p *SyncMonitor) LastSyncDate(accountId uint, sourceId string) (lastSyncDate string, err error) {
1483- message, err := p.obj.Call(busInterface, "lastSuccessfulSyncDate", uint32(accountId), sourceId)
1484- if err != nil {
1485- return "", err
1486- } else {
1487- var lastSyncDate string
1488- err = message.Args(&lastSyncDate)
1489- return lastSyncDate, err
1490- }
1491-}
1492-
1493-func (p *SyncMonitor) SyncAccount(accountId uint, sources []string) (err error) {
1494- _, err = p.obj.Call(busInterface, "syncAccount", uint32(accountId), sources)
1495- return err
1496-}
1497-
1498-func (p *SyncMonitor) State() (state string, err error) {
1499- message, err := p.obj.Call(busInterface, "state")
1500- if err != nil {
1501- return "", err
1502- } else {
1503- err = message.Args(&state)
1504- return state, err
1505- }
1506-}
1507-
1508
1509=== added directory 'syncmonitor'
1510=== added file 'syncmonitor/syncmonitor.go'
1511--- syncmonitor/syncmonitor.go 1970-01-01 00:00:00 +0000
1512+++ syncmonitor/syncmonitor.go 2016-08-02 14:37:44 +0000
1513@@ -0,0 +1,93 @@
1514+/*
1515+ Copyright 2016 Canonical Ltd.
1516+
1517+ This program is free software: you can redistribute it and/or modify it
1518+ under the terms of the GNU General Public License version 3, as published
1519+ by the Free Software Foundation.
1520+
1521+ This program is distributed in the hope that it will be useful, but
1522+ WITHOUT ANY WARRANTY; without even the implied warranties of
1523+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1524+ PURPOSE. See the GNU General Public License for more details.
1525+
1526+ You should have received a copy of the GNU General Public License along
1527+ with this program. If not, see <http://www.gnu.org/licenses/>.
1528+*/
1529+
1530+package syncmonitor
1531+
1532+import (
1533+ "log"
1534+ "runtime"
1535+
1536+ "launchpad.net/go-dbus/v1"
1537+)
1538+
1539+const (
1540+ busInterface = "com.canonical.SyncMonitor"
1541+ busPath = "/com/canonical/SyncMonitor"
1542+ busName = "com.canonical.SyncMonitor"
1543+)
1544+
1545+type SyncMonitor struct {
1546+ conn *dbus.Connection
1547+ obj *dbus.ObjectProxy
1548+}
1549+
1550+func NewSyncMonitor() *SyncMonitor {
1551+ conn, err := dbus.Connect(dbus.SessionBus)
1552+ if err != nil {
1553+ log.Print("Fail to connect with session bus: ", err)
1554+ return nil
1555+ }
1556+
1557+ p := &SyncMonitor{
1558+ conn: conn,
1559+ obj: conn.Object(busName, busPath),
1560+ }
1561+ runtime.SetFinalizer(p, clean)
1562+ return p
1563+}
1564+
1565+func clean(p *SyncMonitor) {
1566+ if p.conn != nil {
1567+ p.conn.Close()
1568+ }
1569+}
1570+
1571+func (p *SyncMonitor) ListCalendarsByAccount(accountId uint) (calendars map[string]string, err error) {
1572+ message, err := p.obj.Call(busInterface, "listCalendarsByAccount", uint32(accountId))
1573+ if err != nil {
1574+ var calendars map[string]string
1575+ return calendars, err
1576+ } else {
1577+ err = message.Args(&calendars)
1578+ return calendars, err
1579+ }
1580+}
1581+
1582+func (p *SyncMonitor) LastSyncDate(accountId uint, sourceId string) (lastSyncDate string, err error) {
1583+ message, err := p.obj.Call(busInterface, "lastSuccessfulSyncDate", uint32(accountId), sourceId)
1584+ if err != nil {
1585+ return "", err
1586+ } else {
1587+ var lastSyncDate string
1588+ err = message.Args(&lastSyncDate)
1589+ return lastSyncDate, err
1590+ }
1591+}
1592+
1593+func (p *SyncMonitor) SyncAccount(accountId uint, sources []string) (err error) {
1594+ _, err = p.obj.Call(busInterface, "syncAccount", uint32(accountId), sources)
1595+ return err
1596+}
1597+
1598+func (p *SyncMonitor) State() (state string, err error) {
1599+ message, err := p.obj.Call(busInterface, "state")
1600+ if err != nil {
1601+ return "", err
1602+ } else {
1603+ err = message.Args(&state)
1604+ return state, err
1605+ }
1606+}

Subscribers

People subscribed via source and target branches