Merge lp:~ted/ubuntu-app-launch/snappy-backend into lp:ubuntu-app-launch/16.10

Proposed by Ted Gould
Status: Merged
Approved by: Ted Gould
Approved revision: 383
Merged at revision: 253
Proposed branch: lp:~ted/ubuntu-app-launch/snappy-backend
Merge into: lp:ubuntu-app-launch/16.10
Prerequisite: lp:~ted/ubuntu-app-launch/snappy-backend-no-snap
Diff against target: 2609 lines (+2102/-13)
28 files modified
.bzrignore (+1/-0)
CMakeLists.txt (+3/-0)
application-failed.c (+2/-1)
debian/control (+1/-0)
docs/index.rst (+30/-0)
libubuntu-app-launch/CMakeLists.txt (+12/-0)
libubuntu-app-launch/appid.h (+4/-1)
libubuntu-app-launch/application-impl-legacy.cpp (+13/-0)
libubuntu-app-launch/application-impl-snap.cpp (+459/-0)
libubuntu-app-launch/application-impl-snap.h (+98/-0)
libubuntu-app-launch/application.cpp (+14/-0)
libubuntu-app-launch/registry-impl.h (+9/-0)
libubuntu-app-launch/registry.cpp (+8/-0)
libubuntu-app-launch/snapd-info.cpp (+471/-0)
libubuntu-app-launch/snapd-info.h (+76/-0)
libubuntu-app-launch/ubuntu-app-launch.cpp (+3/-0)
tests/CMakeLists.txt (+18/-0)
tests/exec-util-test.cc (+1/-0)
tests/failure-test.cc (+17/-9)
tests/libual-cpp-test.cc (+199/-1)
tests/libual-test.cc (+2/-0)
tests/list-apps.cpp (+79/-0)
tests/snapd-info-test.cpp (+150/-0)
tests/snapd-mock.h (+384/-0)
upstart-jobs/CMakeLists.txt (+8/-0)
upstart-jobs/application-failed.conf.in (+1/-1)
upstart-jobs/application-logrotate.conf (+1/-0)
upstart-jobs/application-snap.conf.in (+38/-0)
To merge this branch: bzr merge lp:~ted/ubuntu-app-launch/snappy-backend
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
unity-api-1-bot continuous-integration Approve
Review via email: mp+302027@code.launchpad.net

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

Commit message

Support launching applications installed as snaps

Description of the change

Adds a new Application Implementation for Snaps. This also includes the snap-info object which is used to connect to snapd. There are also several adjustments to shared classes to make sure they take the new backend into account.

To post a comment you must log in.
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:346
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/31/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/302/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/308
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/237
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/237
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/237
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/166/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/166/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/31/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:347
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/52/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/380/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/386
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/303
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/303
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/303
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/233/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/233/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/52/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:348
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/58/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/395/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/401
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/316
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/316
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/316
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/246/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/246/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/58/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:350
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/61/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/453/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/459
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/365
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/365
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/365
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/295/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/295/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/61/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:354
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/67/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/476/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/482
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/387
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/387
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/387
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/317/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/317
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/317/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/317/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/317/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/317
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/317/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/317/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/317/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/317
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/317/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/317/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/67/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

eod reached, review comments part 1 inline

Revision history for this message
Ted Gould (ted) wrote :
Download full text (13.0 KiB)

On Wed, 2016-08-24 at 23:35 +0000, Charles Kerr wrote:
> >   gchar * appid = g_strdup(instance);
> > - if (g_strcmp0(job, "application-legacy") == 0) {
> > + if (g_strcmp0(job, "application-legacy") == 0
> > + || g_strcmp0(job, "application-snap") ==
> > 0) {
> >   gchar * lasthyphenstanding = g_strrstr(appid, "-
> > ");
> (not-even-part-of-the-MP) why not strrchr()?

The answer is that everything else was g_, so went with g_.

> > +/** Path that snapd puts desktop files, we don't want to read
> > those directly
> > +    in the Legacy backend. We want to use the snap backend. */
> > +const std::string snappyDesktopPath{"/var/lib/snapd"};
> This is interesting, I haven't seen this before. Usually when I see a
> file-private variable it's placed inside an unnamed namespace to
> preserve anonymity, eg
>
> namespace
> {
> const std::string snappyDesktopPath{"/var/lib/snapd"};
> }
>
> but this variable here, even though not declared in any header, is
> put here inside an ubuntu::app_launch::app_impls namespace.
>
> Not a blocker, but I'd like to understand this more, what are the
> advantages/disadvantages of placing a private variable in the same
> namespace advertised in application-impl-legacy.h

So I've been trying the other way, trying to get someone to say why
those should be in the anonymous namespace. It seems to me, if it's
related and used by the object it should be in the same namespace as
the object. But I haven't seen any rationale other than "it makes the
symbol name smaller" which seems like a premature optimization at best.

> > +    if (std::equal(snappyDesktopPath.begin(),
> > snappyDesktopPath.end(), _basedir.begin()))
> (bikeshed) g_str_has_prefix() would be clearer

Eh, yeah, trying to keep C++ techniques with C++ strings and g_
techniques with gchar strings. I do hate that the C++ string class
doesn't have a "bool prefix(std::string)" on it. And a few other things
that it seems every other string library has. :-/

> > +    /** Return the xMirEnable value based on whether the interface
> > is
> > +        in the list of interfaces using XMir */
> > +    XMirEnable xMirEnable() override
> > +    {
> > +        if (XMIR_INTERFACES.find(interface_) !=
> > XMIR_INTERFACES.end())
> > +        {
> > +            return XMirEnable::from_raw(true);
> I should have called this out in snappy-backend-no-find-discover but
> I don't see what type-tagging a bool accomplishes. How does this
> improve on just using a bool?

Well, I think it is as useful as type-tagging any variable. Just to
keep the type in the code and say when you're using it. But this is
internal, and so that's not as big a reason. I did it here just to make
it feel like the other variables and have the same semantics of usage.

> > +    /** Figures out the exec line for a snappy command. We're not
> > using
> > +        the Exec in the desktop file exactly, but assuming that it
> > is kinda
> > +        what we want to be run. So we're replacing that with the
> > script, which
> > +        we have to use as we can't get the command that is in the
> > snap
> > +        metadata as Snapd won't give it to us. So we're parsing
> > the Exec line
> > +        and replaci...

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:368
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/71/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/491/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/497
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/402
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/402
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/402
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/332/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/332
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/332/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/332/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/332/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/332
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/332/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/332/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/332/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/332
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/332/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/332/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/71/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Some more comments

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:369
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/80/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/507/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/513
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/418
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/418
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/418
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/348/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/348
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/348/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/348/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/348/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/348
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/348/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/348/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/348/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/348
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/348/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/348/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/80/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:370
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/82/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/511/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/517
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/422
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/422
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/422
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/352/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/352
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/352/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/352/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/352/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/352
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/352/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/352/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/352/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/352
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/352/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/352/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/82/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:371
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/85/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/514/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/520
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/425
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/425
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/425
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/355/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/355
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/355/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/355/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/355/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/355
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/355/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/355/console
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/355/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/355
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/355/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/355/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/85/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:372
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/87/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/516/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/522
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/427
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/427
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/427
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/357
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/357
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/357/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/357/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/357
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/357
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/357/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/357/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/357
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/357/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/357
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/357/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/357/console

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/87/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:373
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/88/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/517
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/523
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/428
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/428
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/428
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/358/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/358
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/358/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/88/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ted Gould (ted) wrote :

On Thu, 2016-08-25 at 21:55 +0000, Charles Kerr wrote:
> > +        /******************************************/
> > +        /* Validation of the object we got        */
> > +        /******************************************/
> > +        for (auto member : {"name", "status", "revision", "type",
> > "version", "apps"})
> auto& :-)

Not sure it helps here since the auto resolves to "const char *" it
would just be copying the pointer. But consistency is good.

r374

> > +        {
> > +            if (!json_object_has_member(snapobject, member))
> > +            {
> > +                throw std::runtime_error("Snap JSON didn't have a
> > '" + std::string(member) + "'");
> > +            }
> > +        }
> > +
> > +        std::string namestr =
> > json_object_get_string_member(snapobject, "name");
> nullptr guard, here and below for 'status', 'type', 'version',
> 'revision', 'name'

r375

> > +static size_t snapd_writefunc(char *ptr, size_t size, size_t
> > nmemb, void *userdata)
> > +{
> > +    unsigned int i;
> > +    std::vector<char> *data = static_cast<std::vector<char>
> > *>(userdata);
> redundant type info, suggest auto on lhs

r376

> >
> > +    data->reserve(data->size() + (size * nmemb)); /* allocate once
> > */
> > +    for (i = 0; i < size * nmemb; i++)
> > +    {
> > +        data->push_back(ptr[i]);
> > +    }
> I'd replace 1033-1037 with "data->insert(data->end(), ptr,
> ptr+(size*nmemb));"

r377

> > +    if (error != nullptr)
> > +    {
> > +        g_warning("Can not parse! %s", error->message);
> > +        g_error_free(error);
> > +        throw std::runtime_error("Can not parse JSON response");
> marginally better if this contained error->message

r378

> > +    std::string statusstr = json_object_get_string_member(rootobj,
> > "status");
> nullptr guard here and "type" below

r379

Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:380
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/89/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/518
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/524
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=vivid+overlay/429
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=xenial+overlay/429
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-1-sourcepkg/release=yakkety/429
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/359/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/359
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/359/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/89/rebuild

review: Approve (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

FAILED: Continuous integration, rev:381
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/92/
Executed test runs:
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build/562/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/568
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/396/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/396/artifact/output/*zip*/output.zip
    FAILURE: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/396/console
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/396/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/396/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/396/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/396/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/396/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/396
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/396/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/92/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
unity-api-1-bot (unity-api-1-bot) wrote :

PASSED: Continuous integration, rev:382
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/95/
Executed test runs:
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build/592
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-0-fetch/598
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=vivid+overlay/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=xenial+overlay/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=amd64,release=yakkety/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=vivid+overlay/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=xenial+overlay/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=armhf,release=yakkety/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=vivid+overlay/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=xenial+overlay/422/artifact/output/*zip*/output.zip
    SUCCESS: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/422
        deb: https://jenkins.canonical.com/unity-api-1/job/build-2-binpkg/arch=i386,release=yakkety/422/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://jenkins.canonical.com/unity-api-1/job/lp-ubuntu-app-launch-ci/95/rebuild

review: Approve (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

Thanks for the fixes!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2015-12-10 21:01:26 +0000
3+++ .bzrignore 2016-09-13 17:38:18 +0000
4@@ -11,3 +11,4 @@
5 libubuntu-app-launch/proxy-socket-demangler.c
6 libubuntu-app-launch/proxy-socket-demangler.h
7 libubuntu-app-launch/ubuntu-app-launch-2.pc
8+application-snap.conf
9
10=== modified file 'CMakeLists.txt'
11--- CMakeLists.txt 2016-08-17 15:24:23 +0000
12+++ CMakeLists.txt 2016-09-13 17:38:18 +0000
13@@ -102,6 +102,9 @@
14 pkg_check_modules(LIBERTINE libertine)
15 include_directories(${LIBERTINE_INCLUDE_DIRS})
16
17+pkg_check_modules(CURL libcurl>=7.47)
18+include_directories(${CURL_INCLUDE_DIRS})
19+
20 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
21
22 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
23
24=== modified file 'application-failed.c'
25--- application-failed.c 2014-04-30 19:51:19 +0000
26+++ application-failed.c 2016-09-13 17:38:18 +0000
27@@ -35,7 +35,8 @@
28 }
29
30 gchar * appid = g_strdup(instance);
31- if (g_strcmp0(job, "application-legacy") == 0) {
32+ if (g_strcmp0(job, "application-legacy") == 0
33+ || g_strcmp0(job, "application-snap") == 0) {
34 gchar * lasthyphenstanding = g_strrstr(appid, "-");
35 if (lasthyphenstanding != NULL) {
36 lasthyphenstanding[0] = '\0';
37
38=== modified file 'debian/control'
39--- debian/control 2016-08-17 15:24:23 +0000
40+++ debian/control 2016-09-13 17:38:18 +0000
41@@ -11,6 +11,7 @@
42 debhelper (>= 9),
43 libcgmanager-dev,
44 libclick-0.4-dev,
45+ libcurl4-dev | libcurl4-gnutls-dev,
46 libdbus-1-dev,
47 libdbustest1-dev (>= 14.04.0),
48 libgirepository1.0-dev,
49
50=== modified file 'docs/index.rst'
51--- docs/index.rst 2016-09-13 17:38:17 +0000
52+++ docs/index.rst 2016-09-13 17:38:18 +0000
53@@ -96,6 +96,16 @@
54 :private-members:
55 :undoc-members:
56
57+Application Implementation Snappy
58+---------------------------------
59+
60+.. doxygenclass:: ubuntu::app_launch::app_impls::Snap
61+ :project: libubuntu-app-launch
62+ :members:
63+ :protected-members:
64+ :private-members:
65+ :undoc-members:
66+
67 Application Info Desktop
68 ------------------------
69
70@@ -106,6 +116,16 @@
71 :private-members:
72 :undoc-members:
73
74+Application Info Snap
75+------------------------
76+
77+.. doxygenclass:: ubuntu::app_launch::app_impls::SnapInfo
78+ :project: libubuntu-app-launch
79+ :members:
80+ :protected-members:
81+ :private-members:
82+ :undoc-members:
83+
84 Application Icon Finder
85 ------------------------
86
87@@ -136,6 +156,16 @@
88 :private-members:
89 :undoc-members:
90
91+Snapd Info
92+----------
93+
94+.. doxygenclass:: ubuntu::app_launch::snapd::Info
95+ :project: libubuntu-app-launch
96+ :members:
97+ :protected-members:
98+ :private-members:
99+ :undoc-members:
100+
101 Type Tagger
102 -----------
103
104
105=== modified file 'libubuntu-app-launch/CMakeLists.txt'
106--- libubuntu-app-launch/CMakeLists.txt 2016-09-13 17:38:17 +0000
107+++ libubuntu-app-launch/CMakeLists.txt 2016-09-13 17:38:18 +0000
108@@ -61,6 +61,16 @@
109 app-info.c
110 )
111
112+if(CURL_FOUND)
113+add_definitions ( -DENABLE_SNAPPY=1 )
114+list(APPEND LAUNCHER_CPP_SOURCES
115+application-impl-snap.h
116+application-impl-snap.cpp
117+snapd-info.h
118+snapd-info.cpp
119+)
120+endif()
121+
122 add_custom_target(format
123 COMMAND clang-format -i -style=file ${LAUNCHER_CPP_HEADERS} ${LAUNCHER_CPP_SOURCES}
124 )
125@@ -83,6 +93,7 @@
126 ${ZEITGEIST_LIBRARIES}
127 ${MIR_LIBRARIES}
128 ${LIBERTINE_LIBRARIES}
129+ ${CURL_LIBRARIES}
130 -lpthread
131 helpers
132 -Wl,--no-undefined
133@@ -109,6 +120,7 @@
134 ${ZEITGEIST_LIBRARIES}
135 ${MIR_LIBRARIES}
136 ${LIBERTINE_LIBRARIES}
137+ ${CURL_LIBRARIES}
138 -lpthread
139 helpers
140 -Wl,--no-undefined
141
142=== modified file 'libubuntu-app-launch/appid.h'
143--- libubuntu-app-launch/appid.h 2016-09-13 17:38:17 +0000
144+++ libubuntu-app-launch/appid.h 2016-09-13 17:38:18 +0000
145@@ -71,7 +71,10 @@
146 under the "hooks" key in the JSON manifest. */
147 AppName appname;
148 /** Version of the package that is installed. This is always resolved when
149- creating the struct. */
150+ creating the struct.
151+
152+ \note For snaps this is actually the 'revision' instead of the version
153+ since that is unique where 'version' is not. */
154 Version version;
155
156 /** Turn the structure into a string. This is required for many older C based
157
158=== modified file 'libubuntu-app-launch/application-impl-legacy.cpp'
159--- libubuntu-app-launch/application-impl-legacy.cpp 2016-09-13 17:38:17 +0000
160+++ libubuntu-app-launch/application-impl-legacy.cpp 2016-09-13 17:38:18 +0000
161@@ -30,8 +30,16 @@
162 namespace app_impls
163 {
164
165+/** Path that snapd puts desktop files, we don't want to read those directly
166+ in the Legacy backend. We want to use the snap backend. */
167+const std::string snappyDesktopPath{"/var/lib/snapd"};
168+
169+/***********************************
170+ Prototypes
171+ ***********************************/
172 std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name);
173
174+/** Helper function to put on shared_ptr's for keyfiles */
175 void clear_keyfile(GKeyFile* keyfile)
176 {
177 if (keyfile != nullptr)
178@@ -53,6 +61,11 @@
179 {
180 throw std::runtime_error{"Unable to find keyfile for legacy application: " + appname.value()};
181 }
182+
183+ if (std::equal(snappyDesktopPath.begin(), snappyDesktopPath.end(), _basedir.begin()))
184+ {
185+ throw std::runtime_error{"Looking like a legacy app, but should be a Snap: " + appname.value()};
186+ }
187 }
188
189 std::tuple<std::string, std::shared_ptr<GKeyFile>, std::string> keyfileForApp(const AppID::AppName& name)
190
191=== added file 'libubuntu-app-launch/application-impl-snap.cpp'
192--- libubuntu-app-launch/application-impl-snap.cpp 1970-01-01 00:00:00 +0000
193+++ libubuntu-app-launch/application-impl-snap.cpp 2016-09-13 17:38:18 +0000
194@@ -0,0 +1,459 @@
195+/*
196+ * Copyright © 2016 Canonical Ltd.
197+ *
198+ * This program is free software: you can redistribute it and/or modify it
199+ * under the terms of the GNU General Public License version 3, as published
200+ * by the Free Software Foundation.
201+ *
202+ * This program is distributed in the hope that it will be useful, but
203+ * WITHOUT ANY WARRANTY; without even the implied warranties of
204+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
205+ * PURPOSE. See the GNU General Public License for more details.
206+ *
207+ * You should have received a copy of the GNU General Public License along
208+ * with this program. If not, see <http://www.gnu.org/licenses/>.
209+ *
210+ * Authors:
211+ * Ted Gould <ted.gould@canonical.com>
212+ */
213+
214+#include <regex>
215+
216+#include "application-impl-snap.h"
217+#include "application-info-desktop.h"
218+#include "registry-impl.h"
219+
220+namespace ubuntu
221+{
222+namespace app_launch
223+{
224+namespace app_impls
225+{
226+
227+/************************
228+ ** Interface Lists
229+ ************************/
230+
231+/** All the interfaces that we support running applications with */
232+const std::set<std::string> SUPPORTED_INTERFACES{"unity8", "unity7", "x11"};
233+/** All the interfaces that we run XMir for by default */
234+const std::set<std::string> XMIR_INTERFACES{"unity7", "x11"};
235+/** All the interfaces that we tell Unity support lifecycle */
236+const std::set<std::string> LIFECYCLE_INTERFACES{"unity8"};
237+/** Snappy has more restrictive appnames than everyone else */
238+const std::regex appnameRegex{"^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$"};
239+
240+/************************
241+ ** Info support
242+ ************************/
243+
244+/** Subclassing the desktop info object so that we can override a couple
245+ of properties with interface definitions. This may grow as we add more
246+ fields to the desktop spec that come from Snappy interfaces. */
247+class SnapInfo : public app_info::Desktop
248+{
249+ /** The core interface for this snap */
250+ std::string interface_;
251+ /** AppID of snap */
252+ AppID appId_;
253+
254+public:
255+ SnapInfo(const AppID& appid,
256+ const std::shared_ptr<Registry>& registry,
257+ const std::string& interface,
258+ const std::string& snapDir)
259+ : Desktop(
260+ [appid, snapDir]() -> std::shared_ptr<GKeyFile> {
261+ /* This is a function to get the keyfile out of the snap using
262+ the paths that snappy places things inside the dir. */
263+ std::string path = snapDir + "/meta/gui/" + appid.appname.value() + ".desktop";
264+ std::shared_ptr<GKeyFile> keyfile(g_key_file_new(), g_key_file_free);
265+ GError* error = nullptr;
266+ g_key_file_load_from_file(keyfile.get(), path.c_str(), G_KEY_FILE_NONE, &error);
267+ if (error != nullptr)
268+ {
269+ auto perror = std::shared_ptr<GError>(error, g_error_free);
270+ throw std::runtime_error("Unable to find keyfile for '" + std::string(appid) + "' at '" + path +
271+ "' because: " + perror.get()->message);
272+ }
273+
274+ return keyfile;
275+ }(),
276+ snapDir,
277+ app_info::DesktopFlags::NONE,
278+ registry)
279+ , interface_(interface)
280+ , appId_(appid)
281+ {
282+ }
283+
284+ /** Return the xMirEnable value based on whether the interface is
285+ in the list of interfaces using XMir */
286+ XMirEnable xMirEnable() override
287+ {
288+ if (XMIR_INTERFACES.find(interface_) != XMIR_INTERFACES.end())
289+ {
290+ return XMirEnable::from_raw(true);
291+ }
292+ else
293+ {
294+ return XMirEnable::from_raw(false);
295+ }
296+ }
297+
298+ /** Return the xMirEnable value based on whether the interface is
299+ in the list of interfaces supporting the lifecycle */
300+ UbuntuLifecycle supportsUbuntuLifecycle() override
301+ {
302+ if (LIFECYCLE_INTERFACES.find(interface_) != LIFECYCLE_INTERFACES.end())
303+ {
304+ return UbuntuLifecycle::from_raw(true);
305+ }
306+ else
307+ {
308+ return UbuntuLifecycle::from_raw(false);
309+ }
310+ }
311+
312+ /** Figures out the exec line for a snappy command. We're not using
313+ the Exec in the desktop file exactly, but assuming that it is kinda
314+ what we want to be run. So we're replacing that with the script, which
315+ we have to use as we can't get the command that is in the snap
316+ metadata as Snapd won't give it to us. So we're parsing the Exec line
317+ and replacing the first entry. Then putting it back together again. */
318+ Exec execLine() override
319+ {
320+ std::string keyfile = _exec.value();
321+ gchar** parsed = nullptr;
322+ GError* error = nullptr;
323+
324+ g_shell_parse_argv(keyfile.c_str(), nullptr, &parsed, &error);
325+
326+ if (error != nullptr)
327+ {
328+ g_warning("Unable to parse exec line '%s': %s", keyfile.c_str(), error->message);
329+ g_error_free(error);
330+ return Exec::from_raw({});
331+ }
332+
333+ if (g_strv_length(parsed) == 0)
334+ {
335+ g_warning("Parse resulted in a blank line");
336+ g_strfreev(parsed);
337+ return Exec::from_raw({});
338+ }
339+
340+ /* Skip the first entry */
341+ gchar** parsedpp = &(parsed[1]);
342+
343+ gchar* params = g_strjoinv(" ", parsedpp);
344+ g_strfreev(parsed);
345+
346+ std::string binname;
347+ if (appId_.package.value() == appId_.appname.value())
348+ {
349+ binname = appId_.package.value();
350+ }
351+ else
352+ {
353+ binname = appId_.package.value() + " " + appId_.appname.value();
354+ }
355+
356+ binname = "/snap/bin/" + binname + " " + params;
357+ g_free(params);
358+
359+ return Exec::from_raw(binname);
360+ }
361+};
362+
363+/************************
364+ ** Snap implementation
365+ ************************/
366+
367+/** Creates a Snap application object. Will throw exceptions if the AppID
368+ doesn't resolve into a valid package or that package doesn't have a desktop
369+ file that matches the app name.
370+
371+ \param appid Application ID of the snap
372+ \param registry Registry to use for persistent connections
373+ \param interface Primary interface that we found this snap for
374+*/
375+Snap::Snap(const AppID& appid, const std::shared_ptr<Registry>& registry, const std::string& interface)
376+ : Base(registry)
377+ , appid_(appid)
378+ , interface_(interface)
379+{
380+ pkgInfo_ = registry->impl->snapdInfo.pkgInfo(appid.package);
381+ if (!pkgInfo_)
382+ {
383+ throw std::runtime_error("Unable to get snap package info for AppID: " + std::string(appid));
384+ }
385+
386+ if (!checkPkgInfo(pkgInfo_, appid_))
387+ {
388+ throw std::runtime_error("AppID does not match installed package for: " + std::string(appid));
389+ }
390+
391+ info_ = std::make_shared<SnapInfo>(appid_, _registry, interface_, pkgInfo_->directory);
392+}
393+
394+/** Uses the findInterface() function to find the interface if we don't
395+ have one.
396+
397+ \param appid Application ID of the snap
398+ \param registry Registry to use for persistent connections
399+*/
400+Snap::Snap(const AppID& appid, const std::shared_ptr<Registry>& registry)
401+ : Snap(appid, registry, findInterface(appid, registry))
402+{
403+}
404+
405+/** Lists all the Snappy apps that are using one of our supported interfaces.
406+ Also makes sure they're valid.
407+
408+ \param registry Registry to use for persistent connections
409+*/
410+std::list<std::shared_ptr<Application>> Snap::list(const std::shared_ptr<Registry>& registry)
411+{
412+ std::list<std::shared_ptr<Application>> apps;
413+
414+ for (const auto& interface : SUPPORTED_INTERFACES)
415+ {
416+ for (const auto& id : registry->impl->snapdInfo.appsForInterface(interface))
417+ {
418+ try
419+ {
420+ auto app = std::make_shared<Snap>(id, registry, interface);
421+ apps.emplace_back(app);
422+ }
423+ catch (std::runtime_error& e)
424+ {
425+ g_warning("Unable to make Snap object for '%s': %s", std::string(id).c_str(), e.what());
426+ }
427+ }
428+ }
429+
430+ return apps;
431+}
432+
433+/** Returns the stored AppID */
434+AppID Snap::appId()
435+{
436+ return appid_;
437+}
438+
439+/** Asks Snapd for the interfaces to determine which one the application
440+ can support.
441+
442+ \param appid Application ID of the snap
443+ \param registry Registry to use for persistent connections
444+*/
445+std::string Snap::findInterface(const AppID& appid, const std::shared_ptr<Registry>& registry)
446+{
447+ auto ifaceset = registry->impl->snapdInfo.interfacesForAppId(appid);
448+
449+ for (const auto& interface : SUPPORTED_INTERFACES)
450+ {
451+ if (ifaceset.find(interface) != ifaceset.end())
452+ {
453+ return interface;
454+ }
455+ }
456+
457+ throw std::runtime_error("Interface not found for: " + std::string(appid));
458+}
459+
460+/** Checks a PkgInfo structure to ensure that it matches the AppID */
461+bool Snap::checkPkgInfo(const std::shared_ptr<snapd::Info::PkgInfo>& pkginfo, const AppID& appid)
462+{
463+ if (!pkginfo)
464+ {
465+ return false;
466+ }
467+
468+ return pkginfo->revision == appid.version.value() &&
469+ pkginfo->appnames.find(appid.appname) != pkginfo->appnames.end();
470+}
471+
472+/** Checks if an AppID could be a snap. Note it doesn't look for a desktop
473+ file just the package, app and version. This is done to make the lookup
474+ quickly, as this function can be used to select which backend to use
475+ and we want to reject quickly.
476+
477+ \param appid Application ID of the snap
478+ \param registry Registry to use for persistent connections
479+*/
480+bool Snap::hasAppId(const AppID& appId, const std::shared_ptr<Registry>& registry)
481+{
482+ if (!std::regex_match(appId.appname.value(), appnameRegex))
483+ {
484+ return false;
485+ }
486+
487+ auto pkginfo = registry->impl->snapdInfo.pkgInfo(appId.package);
488+ return checkPkgInfo(pkginfo, appId);
489+}
490+
491+/** Look to see if a package is a valid Snap package name
492+
493+ \param package Package name
494+ \param registry Registry to use for persistent connections
495+*/
496+bool Snap::verifyPackage(const AppID::Package& package, const std::shared_ptr<Registry>& registry)
497+{
498+ try
499+ {
500+ auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package);
501+ return pkgInfo != nullptr;
502+ }
503+ catch (std::runtime_error& e)
504+ {
505+ return false;
506+ }
507+}
508+
509+/** Look to see if an appname is a valid for a Snap package
510+
511+ \param package Package name
512+ \param appname Command name
513+ \param registry Registry to use for persistent connections
514+*/
515+bool Snap::verifyAppname(const AppID::Package& package,
516+ const AppID::AppName& appname,
517+ const std::shared_ptr<Registry>& registry)
518+{
519+ if (!std::regex_match(appname.value(), appnameRegex))
520+ {
521+ return false;
522+ }
523+
524+ auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package);
525+ return pkgInfo->appnames.find(appname) != pkgInfo->appnames.end();
526+}
527+
528+/** Look for an application name on a Snap package based on a
529+ wildcard type.
530+
531+ \param package Package name
532+ \param card Wildcard to use for finding the appname
533+ \param registry Registry to use for persistent connections
534+*/
535+AppID::AppName Snap::findAppname(const AppID::Package& package,
536+ AppID::ApplicationWildcard card,
537+ const std::shared_ptr<Registry>& registry)
538+{
539+ auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package);
540+
541+ if (pkgInfo->appnames.empty())
542+ {
543+ throw std::runtime_error("No apps in package '" + package.value() + "' to find");
544+ }
545+
546+ switch (card)
547+ {
548+ case AppID::ApplicationWildcard::FIRST_LISTED:
549+ return AppID::AppName::from_raw(*pkgInfo->appnames.begin());
550+ case AppID::ApplicationWildcard::LAST_LISTED:
551+ return AppID::AppName::from_raw(*pkgInfo->appnames.rbegin());
552+ case AppID::ApplicationWildcard::ONLY_LISTED:
553+ if (pkgInfo->appnames.size() != 1)
554+ {
555+ throw std::runtime_error("More than a single app in package '" + package.value() +
556+ "' when requested to find only app");
557+ }
558+ return AppID::AppName::from_raw(*pkgInfo->appnames.begin());
559+ }
560+
561+ throw std::logic_error("Got a value of the app wildcard enum that can't exist");
562+}
563+
564+/** Look for a version of a Snap package
565+
566+ \param package Package name
567+ \param appname Not used for snaps
568+ \param registry Registry to use for persistent connections
569+*/
570+AppID::Version Snap::findVersion(const AppID::Package& package,
571+ const AppID::AppName& appname,
572+ const std::shared_ptr<Registry>& registry)
573+{
574+ auto pkgInfo = registry->impl->snapdInfo.pkgInfo(package);
575+ return AppID::Version::from_raw(pkgInfo->revision);
576+}
577+
578+/** Returns a reference to the info for the snap */
579+std::shared_ptr<Application::Info> Snap::info()
580+{
581+ return info_;
582+}
583+
584+/** Get all of the instances of this snap package that are running */
585+std::vector<std::shared_ptr<Application::Instance>> Snap::instances()
586+{
587+ std::vector<std::shared_ptr<Instance>> vect;
588+ auto startsWith = std::string(appid_) + "-";
589+
590+ for (const auto& instance : _registry->impl->upstartInstancesForJob("application-snap"))
591+ {
592+ if (std::equal(startsWith.begin(), startsWith.end(), instance.begin()))
593+ {
594+ vect.emplace_back(std::make_shared<UpstartInstance>(appid_, "application-snap", instance,
595+ std::vector<Application::URL>{}, _registry));
596+ }
597+ }
598+
599+ return vect;
600+}
601+
602+/** Return the launch environment for this snap. That includes whether
603+ or not it needs help from XMir (including Libertine helpers)
604+*/
605+std::list<std::pair<std::string, std::string>> Snap::launchEnv()
606+{
607+ g_debug("Getting snap specific environment");
608+ std::list<std::pair<std::string, std::string>> retval;
609+
610+ retval.emplace_back(std::make_pair("APP_XMIR_ENABLE", info_->xMirEnable().value() ? "1" : "0"));
611+ if (info_->xMirEnable())
612+ {
613+ /* If we're setting up XMir we also need the other helpers
614+ that libertine is helping with */
615+ /* retval.emplace_back(std::make_pair("APP_EXEC", "libertine-launch --no-container " +
616+ * info_->execLine().value())); */
617+ /* Not yet */
618+ retval.emplace_back(std::make_pair("APP_EXEC", info_->execLine().value()));
619+ }
620+ else
621+ {
622+ retval.emplace_back(std::make_pair("APP_EXEC", info_->execLine().value()));
623+ }
624+
625+ return retval;
626+}
627+
628+/** Create a new instance of this Snap
629+
630+ \param urls URLs to pass to the command
631+*/
632+std::shared_ptr<Application::Instance> Snap::launch(const std::vector<Application::URL>& urls)
633+{
634+ std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
635+ return UpstartInstance::launch(appid_, "application-snap", std::string(appid_) + "-", urls, _registry,
636+ UpstartInstance::launchMode::STANDARD, envfunc);
637+}
638+
639+/** Create a new instance of this Snap with a testing environment
640+ setup for it.
641+
642+ \param urls URLs to pass to the command
643+*/
644+std::shared_ptr<Application::Instance> Snap::launchTest(const std::vector<Application::URL>& urls)
645+{
646+ std::function<std::list<std::pair<std::string, std::string>>(void)> envfunc = [this]() { return launchEnv(); };
647+ return UpstartInstance::launch(appid_, "application-snap", std::string(appid_) + "-", urls, _registry,
648+ UpstartInstance::launchMode::TEST, envfunc);
649+}
650+
651+} // namespace app_impls
652+} // namespace app_launch
653+} // namespace ubuntu
654
655=== added file 'libubuntu-app-launch/application-impl-snap.h'
656--- libubuntu-app-launch/application-impl-snap.h 1970-01-01 00:00:00 +0000
657+++ libubuntu-app-launch/application-impl-snap.h 2016-09-13 17:38:18 +0000
658@@ -0,0 +1,98 @@
659+/*
660+ * Copyright © 2016 Canonical Ltd.
661+ *
662+ * This program is free software: you can redistribute it and/or modify it
663+ * under the terms of the GNU General Public License version 3, as published
664+ * by the Free Software Foundation.
665+ *
666+ * This program is distributed in the hope that it will be useful, but
667+ * WITHOUT ANY WARRANTY; without even the implied warranties of
668+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
669+ * PURPOSE. See the GNU General Public License for more details.
670+ *
671+ * You should have received a copy of the GNU General Public License along
672+ * with this program. If not, see <http://www.gnu.org/licenses/>.
673+ *
674+ * Authors:
675+ * Ted Gould <ted.gould@canonical.com>
676+ */
677+
678+#include "application-impl-base.h"
679+#include "application-info-desktop.h"
680+#include "snapd-info.h"
681+
682+#pragma once
683+
684+namespace ubuntu
685+{
686+namespace app_launch
687+{
688+namespace app_impls
689+{
690+
691+/** Class implementing a Applications that are installed in the
692+ system as Snaps. This class connects to snapd to get information
693+ on the interfaces of the installed snaps and sees if any of them
694+ are applicable to the user session. Currently that means if the
695+ command has the unity8, unity7 or x11 interface.
696+
697+ For Application IDs snaps use a very similar scheme to Click
698+ packages. The package field is the name of the snap package, typically
699+ this is the overall application name. The appname is the command
700+ in the snap package, which needs to be associated with one of our
701+ supported interfaces and have a desktop file. Lastly the version
702+ field is actually the snap revision, this value changes even on
703+ updates between channels of the same version so it provides a
704+ greater amount of uniqueness.
705+*/
706+class Snap : public Base
707+{
708+public:
709+ Snap(const AppID& appid, const std::shared_ptr<Registry>& registry);
710+ Snap(const AppID& appid, const std::shared_ptr<Registry>& registry, const std::string& interface);
711+
712+ static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry>& registry);
713+
714+ AppID appId() override;
715+
716+ std::shared_ptr<Info> info() override;
717+
718+ std::vector<std::shared_ptr<Instance>> instances() override;
719+
720+ std::shared_ptr<Instance> launch(const std::vector<Application::URL>& urls = {}) override;
721+ std::shared_ptr<Instance> launchTest(const std::vector<Application::URL>& urls = {}) override;
722+
723+ static bool hasAppId(const AppID& appId, const std::shared_ptr<Registry>& registry);
724+
725+ static bool verifyPackage(const AppID::Package& package, const std::shared_ptr<Registry>& registry);
726+ static bool verifyAppname(const AppID::Package& package,
727+ const AppID::AppName& appname,
728+ const std::shared_ptr<Registry>& registry);
729+ static AppID::AppName findAppname(const AppID::Package& package,
730+ AppID::ApplicationWildcard card,
731+ const std::shared_ptr<Registry>& registry);
732+ static AppID::Version findVersion(const AppID::Package& package,
733+ const AppID::AppName& appname,
734+ const std::shared_ptr<Registry>& registry);
735+
736+private:
737+ /** AppID of the Snap. Should be the name of the snap package.
738+ The name of the command. And then the revision. */
739+ AppID appid_;
740+ /** The app's displayed information. Should be from a desktop
741+ file that is put in ${SNAP_DIR}/meta/gui/${command}.desktop */
742+ std::shared_ptr<app_info::Desktop> info_;
743+ /** Which interface we thing we're coming from. Depends on how
744+ we handle setting up the environment for the command. */
745+ std::string interface_;
746+ /** Information that we get from Snapd on the package */
747+ std::shared_ptr<snapd::Info::PkgInfo> pkgInfo_;
748+
749+ std::list<std::pair<std::string, std::string>> launchEnv();
750+ static std::string findInterface(const AppID& appid, const std::shared_ptr<Registry>& registry);
751+ static bool checkPkgInfo(const std::shared_ptr<snapd::Info::PkgInfo>& pkginfo, const AppID& appid);
752+};
753+
754+} // namespace app_impls
755+} // namespace app_launch
756+} // namespace ubuntu
757
758=== modified file 'libubuntu-app-launch/application.cpp'
759--- libubuntu-app-launch/application.cpp 2016-09-13 17:38:17 +0000
760+++ libubuntu-app-launch/application.cpp 2016-09-13 17:38:18 +0000
761@@ -24,6 +24,9 @@
762 #include "application-impl-click.h"
763 #include "application-impl-legacy.h"
764 #include "application-impl-libertine.h"
765+#ifdef ENABLE_SNAPPY
766+#include "application-impl-snap.h"
767+#endif
768 #include "application.h"
769 #include "registry.h"
770
771@@ -47,6 +50,12 @@
772 {
773 return std::make_shared<app_impls::Click>(appid, registry);
774 }
775+#ifdef ENABLE_SNAPPY
776+ else if (app_impls::Snap::hasAppId(appid, registry))
777+ {
778+ return std::make_shared<app_impls::Snap>(appid, registry);
779+ }
780+#endif
781 else if (app_impls::Libertine::hasAppId(appid, registry))
782 {
783 return std::make_shared<app_impls::Libertine>(appid.package, appid.appname, registry);
784@@ -197,6 +206,11 @@
785 /* Click */
786 {app_impls::Click::verifyPackage, app_impls::Click::verifyAppname, app_impls::Click::findAppname,
787 app_impls::Click::findVersion, app_impls::Click::hasAppId},
788+#ifdef ENABLE_SNAPPY
789+ /* Snap */
790+ {app_impls::Snap::verifyPackage, app_impls::Snap::verifyAppname, app_impls::Snap::findAppname,
791+ app_impls::Snap::findVersion, app_impls::Snap::hasAppId},
792+#endif
793 /* Libertine */
794 {app_impls::Libertine::verifyPackage, app_impls::Libertine::verifyAppname, app_impls::Libertine::findAppname,
795 app_impls::Libertine::findVersion, app_impls::Libertine::hasAppId},
796
797=== modified file 'libubuntu-app-launch/registry-impl.h'
798--- libubuntu-app-launch/registry-impl.h 2016-09-13 17:38:17 +0000
799+++ libubuntu-app-launch/registry-impl.h 2016-09-13 17:38:18 +0000
800@@ -19,6 +19,7 @@
801
802 #include "glib-thread.h"
803 #include "registry.h"
804+#include "snapd-info.h"
805 #include <click.h>
806 #include <gio/gio.h>
807 #include <json-glib/json-glib.h>
808@@ -57,9 +58,17 @@
809 void clearManager ();
810 #endif
811
812+ /** Shared context thread for events and background tasks
813+ that UAL subtasks are doing */
814 GLib::ContextThread thread;
815+ /** DBus shared connection for the session bus */
816 std::shared_ptr<GDBusConnection> _dbus;
817
818+#ifdef ENABLE_SNAPPY
819+ /** Snapd information object */
820+ snapd::Info snapdInfo;
821+#endif
822+
823 std::shared_ptr<IconFinder> getIconFinder(std::string basePath);
824
825 void zgSendEvent(AppID appid, const std::string& eventtype);
826
827=== modified file 'libubuntu-app-launch/registry.cpp'
828--- libubuntu-app-launch/registry.cpp 2016-09-13 17:38:17 +0000
829+++ libubuntu-app-launch/registry.cpp 2016-09-13 17:38:18 +0000
830@@ -27,6 +27,9 @@
831 #include "application-impl-click.h"
832 #include "application-impl-legacy.h"
833 #include "application-impl-libertine.h"
834+#ifdef ENABLE_SNAPPY
835+#include "application-impl-snap.h"
836+#endif
837
838 #include "helper-impl-click.h"
839
840@@ -50,6 +53,8 @@
841
842 /* Get all the legacy instances */
843 instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-legacy"));
844+ /* Get all the snap instances */
845+ instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-snap"));
846
847 /* Remove the instance ID */
848 std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string &instancename) -> std::string {
849@@ -106,6 +111,9 @@
850 list.splice(list.begin(), app_impls::Click::list(connection));
851 list.splice(list.begin(), app_impls::Legacy::list(connection));
852 list.splice(list.begin(), app_impls::Libertine::list(connection));
853+#ifdef ENABLE_SNAPPY
854+ list.splice(list.begin(), app_impls::Snap::list(connection));
855+#endif
856
857 return list;
858 }
859
860=== added file 'libubuntu-app-launch/snapd-info.cpp'
861--- libubuntu-app-launch/snapd-info.cpp 1970-01-01 00:00:00 +0000
862+++ libubuntu-app-launch/snapd-info.cpp 2016-09-13 17:38:18 +0000
863@@ -0,0 +1,471 @@
864+/*
865+ * Copyright © 2016 Canonical Ltd.
866+ *
867+ * This program is free software: you can redistribute it and/or modify it
868+ * under the terms of the GNU General Public License version 3, as published
869+ * by the Free Software Foundation.
870+ *
871+ * This program is distributed in the hope that it will be useful, but
872+ * WITHOUT ANY WARRANTY; without even the implied warranties of
873+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
874+ * PURPOSE. See the GNU General Public License for more details.
875+ *
876+ * You should have received a copy of the GNU General Public License along
877+ * with this program. If not, see <http://www.gnu.org/licenses/>.
878+ *
879+ * Authors:
880+ * Ted Gould <ted.gould@canonical.com>
881+ */
882+
883+#include "snapd-info.h"
884+
885+#include "registry-impl.h"
886+
887+#include <curl/curl.h>
888+#include <vector>
889+
890+namespace ubuntu
891+{
892+namespace app_launch
893+{
894+namespace snapd
895+{
896+
897+/** Initializes the info object which mostly means checking what is overridden
898+ by environment variables (mostly for testing) and making sure there is a
899+ snapd socket available to us. */
900+Info::Info()
901+{
902+ auto snapdEnv = g_getenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET");
903+ if (G_UNLIKELY(snapdEnv != nullptr))
904+ {
905+ snapdSocket = snapdEnv;
906+ }
907+ else
908+ {
909+ snapdSocket = "/run/snapd.socket";
910+ }
911+
912+ auto snapcBasedir = g_getenv("UBUNTU_APP_LAUNCH_SNAP_BASEDIR");
913+ if (G_UNLIKELY(snapcBasedir != nullptr))
914+ {
915+ snapBasedir = snapcBasedir;
916+ }
917+ else
918+ {
919+ snapBasedir = "/snap";
920+ }
921+
922+ if (g_file_test(snapdSocket.c_str(), G_FILE_TEST_EXISTS))
923+ {
924+ snapdExists = true;
925+ }
926+}
927+
928+/** Gets package information out of snapd by using the REST
929+ interface and turning the JSON object into a C++ Struct
930+
931+ \param package Name of the package to look for
932+*/
933+std::shared_ptr<Info::PkgInfo> Info::pkgInfo(const AppID::Package &package) const
934+{
935+ if (!snapdExists)
936+ {
937+ return {};
938+ }
939+
940+ try
941+ {
942+ auto snapnode = snapdJson("/v2/snaps/" + package.value());
943+ auto snapobject = json_node_get_object(snapnode.get());
944+ if (snapobject == nullptr)
945+ {
946+ throw std::runtime_error("Results returned by snapd were not a valid JSON object");
947+ }
948+
949+ /******************************************/
950+ /* Validation of the object we got */
951+ /******************************************/
952+ for (const auto &member : {"apps"})
953+ {
954+ if (!json_object_has_member(snapobject, member))
955+ {
956+ throw std::runtime_error("Snap JSON didn't have a '" + std::string(member) + "'");
957+ }
958+ }
959+
960+ for (const auto &member : {"name", "status", "revision", "type", "version"})
961+ {
962+ if (!json_object_has_member(snapobject, member))
963+ {
964+ throw std::runtime_error("Snap JSON didn't have a '" + std::string(member) + "'");
965+ }
966+
967+ auto node = json_object_get_member(snapobject, member);
968+ if (json_node_get_node_type(node) != JSON_NODE_VALUE)
969+ {
970+ throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's an object!"};
971+ }
972+
973+ if (json_node_get_value_type(node) != G_TYPE_STRING)
974+ {
975+ throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's not a string!"};
976+ }
977+ }
978+
979+ std::string namestr = json_object_get_string_member(snapobject, "name");
980+ if (namestr != package.value())
981+ {
982+ throw std::runtime_error("Snapd returned information for snap '" + namestr + "' when we asked for '" +
983+ package.value() + "'");
984+ }
985+
986+ std::string statusstr = json_object_get_string_member(snapobject, "status");
987+ if (statusstr != "active")
988+ {
989+ throw std::runtime_error("Snap is not in the 'active' state.");
990+ }
991+
992+ std::string typestr = json_object_get_string_member(snapobject, "type");
993+ if (typestr != "app")
994+ {
995+ throw std::runtime_error("Specified snap is not an application, we only support applications");
996+ }
997+
998+ /******************************************/
999+ /* Validation complete — build the object */
1000+ /******************************************/
1001+
1002+ auto pkgstruct = std::make_shared<PkgInfo>();
1003+ pkgstruct->name = namestr;
1004+ pkgstruct->version = json_object_get_string_member(snapobject, "version");
1005+ std::string revisionstr = json_object_get_string_member(snapobject, "revision");
1006+ pkgstruct->revision = revisionstr;
1007+
1008+ /* TODO: Seems like snapd should give this to us */
1009+ auto gdir = g_build_filename(snapBasedir.c_str(), namestr.c_str(), revisionstr.c_str(), nullptr);
1010+ pkgstruct->directory = gdir;
1011+ g_free(gdir);
1012+
1013+ auto appsarray = json_object_get_array_member(snapobject, "apps");
1014+ for (unsigned int i = 0; i < json_array_get_length(appsarray); i++)
1015+ {
1016+ auto appobj = json_array_get_object_element(appsarray, i);
1017+ if (json_object_has_member(appobj, "name"))
1018+ {
1019+ auto appname = json_object_get_string_member(appobj, "name");
1020+ if (appname)
1021+ {
1022+ pkgstruct->appnames.insert(appname);
1023+ }
1024+ }
1025+ }
1026+
1027+ return pkgstruct;
1028+ }
1029+ catch (std::runtime_error &e)
1030+ {
1031+ g_warning("Unable to get snap information for '%s': %s", package.value().c_str(), e.what());
1032+ return {};
1033+ }
1034+}
1035+
1036+/** Function that acts as the return from cURL to add data to
1037+ our storage vector.
1038+
1039+ \param ptr incoming data
1040+ \param size block size
1041+ \param nmemb number of blocks
1042+ \param userdata our local vector to store things in
1043+*/
1044+static size_t snapd_writefunc(char *ptr, size_t size, size_t nmemb, void *userdata)
1045+{
1046+ auto data = static_cast<std::vector<char> *>(userdata);
1047+ data->insert(data->end(), ptr, ptr + (size * nmemb));
1048+ return size * nmemb;
1049+}
1050+
1051+/** Asks the snapd process for some JSON. This function parses the basic
1052+ response JSON that snapd returns and will error if a return code error
1053+ is in the JSON. It then passes on the "result" part of the response
1054+ to the caller.
1055+
1056+ \param endpoint End of the URL to pass to snapd
1057+*/
1058+std::shared_ptr<JsonNode> Info::snapdJson(const std::string &endpoint) const
1059+{
1060+ /* Setup the CURL connection and suck some data */
1061+ CURL *curl = curl_easy_init();
1062+ if (curl == nullptr)
1063+ {
1064+ throw std::runtime_error("Unable to create new cURL connection");
1065+ }
1066+
1067+ std::vector<char> data;
1068+
1069+ /* Configure the command */
1070+ // curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
1071+ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
1072+ curl_easy_setopt(curl, CURLOPT_URL, ("http://snapd" + endpoint).c_str());
1073+ curl_easy_setopt(curl, CURLOPT_UNIX_SOCKET_PATH, snapdSocket.c_str());
1074+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
1075+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, snapd_writefunc);
1076+
1077+ /* Overridable timeout */
1078+ if (g_getenv("UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT") != nullptr)
1079+ {
1080+ curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 100L);
1081+ }
1082+
1083+ /* Run the actual request (blocking) */
1084+ auto res = curl_easy_perform(curl);
1085+
1086+ if (res != CURLE_OK)
1087+ {
1088+ curl_easy_cleanup(curl);
1089+ throw std::runtime_error("snapd HTTP server returned an error: " + std::string(curl_easy_strerror(res)));
1090+ }
1091+ else
1092+ {
1093+ g_debug("Got %d bytes from snapd", int(data.size()));
1094+ }
1095+
1096+ curl_easy_cleanup(curl);
1097+
1098+ /* Cool, we have data */
1099+ auto parser = std::shared_ptr<JsonParser>(json_parser_new(), [](JsonParser *parser) { g_clear_object(&parser); });
1100+ GError *error = nullptr;
1101+ json_parser_load_from_data(parser.get(), /* parser */
1102+ data.data(), /* array */
1103+ data.size(), /* size */
1104+ &error); /* error */
1105+
1106+ if (error != nullptr)
1107+ {
1108+ std::string message{"Can not parse JSON: " + std::string(error->message)};
1109+ g_error_free(error);
1110+ throw std::runtime_error{message};
1111+ }
1112+
1113+ auto root = json_parser_get_root(parser.get());
1114+ auto rootobj = json_node_get_object(root);
1115+
1116+ if (rootobj == nullptr)
1117+ {
1118+ throw std::runtime_error("Root of JSON result isn't an object");
1119+ }
1120+
1121+ /* Check members */
1122+ for (const auto &member : {"status-code", "result"})
1123+ {
1124+ if (!json_object_has_member(rootobj, member))
1125+ {
1126+ throw std::runtime_error("Resulting JSON didn't have a '" + std::string(member) + "'");
1127+ }
1128+ }
1129+
1130+ for (const auto &member : {"status", "type"})
1131+ {
1132+ if (!json_object_has_member(rootobj, member))
1133+ {
1134+ throw std::runtime_error("Snap JSON didn't have a '" + std::string(member) + "'");
1135+ }
1136+
1137+ auto node = json_object_get_member(rootobj, member);
1138+ if (json_node_get_node_type(node) != JSON_NODE_VALUE)
1139+ {
1140+ throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's an object!"};
1141+ }
1142+
1143+ if (json_node_get_value_type(node) != G_TYPE_STRING)
1144+ {
1145+ throw std::runtime_error{"Snap JSON had a '" + std::string(member) + "' but it's not a string!"};
1146+ }
1147+ }
1148+
1149+ auto status = json_object_get_int_member(rootobj, "status-code");
1150+ if (status != 200)
1151+ {
1152+ throw std::runtime_error("Status code is: " + std::to_string(status));
1153+ }
1154+
1155+ std::string statusstr = json_object_get_string_member(rootobj, "status");
1156+ if (statusstr != "OK")
1157+ {
1158+ throw std::runtime_error("Status string is: " + statusstr);
1159+ }
1160+
1161+ std::string typestr = json_object_get_string_member(rootobj, "type");
1162+ if (typestr != "sync")
1163+ {
1164+ throw std::runtime_error("We only support 'sync' results right now, but we got a: " + typestr);
1165+ }
1166+
1167+ auto result = std::shared_ptr<JsonNode>(json_node_ref(json_object_get_member(rootobj, "result")), json_node_unref);
1168+
1169+ return result;
1170+}
1171+
1172+/** Looks through all the plugs in the interfaces and runs a function
1173+ based on them. Avoids pulling objects out of the parsed JSON structure
1174+ from Snappy and making sure they have the same lifecycle as the parser
1175+ object which seems to destroy them when it dies.
1176+
1177+ \param plugfunc Function to execute on each plug
1178+*/
1179+void Info::forAllPlugs(std::function<void(JsonObject *plugobj)> plugfunc) const
1180+{
1181+ if (!snapdExists)
1182+ {
1183+ return;
1184+ }
1185+
1186+ auto interfacesnode = snapdJson("/v2/interfaces");
1187+ auto interface = json_node_get_object(interfacesnode.get());
1188+ if (interface == nullptr)
1189+ {
1190+ throw std::runtime_error("Interfaces result isn't an object: " + Registry::Impl::printJson(interfacesnode));
1191+ }
1192+
1193+ for (const auto &member : {"plugs", "slots"})
1194+ {
1195+ if (!json_object_has_member(interface, member))
1196+ {
1197+ throw std::runtime_error("Interface JSON didn't have a '" + std::string(member) + "'");
1198+ }
1199+ }
1200+
1201+ auto plugarray = json_object_get_array_member(interface, "plugs");
1202+ for (unsigned int i = 0; i < json_array_get_length(plugarray); i++)
1203+ {
1204+ auto ifaceobj = json_array_get_object_element(plugarray, i);
1205+ try
1206+ {
1207+ for (const auto &member : {"snap", "interface", "apps"})
1208+ {
1209+ if (!json_object_has_member(ifaceobj, member))
1210+ {
1211+ throw std::runtime_error("Interface JSON didn't have a '" + std::string(member) + "'");
1212+ }
1213+ }
1214+
1215+ plugfunc(ifaceobj);
1216+ }
1217+ catch (std::runtime_error &e)
1218+ {
1219+ /* We'll check the others even if one is bad */
1220+ // g_debug("Malformed inteface instance: %s", e.what());
1221+ continue;
1222+ }
1223+ }
1224+}
1225+
1226+/** Gets all the apps that are available for a given interface. It asks snapd
1227+ for the list of interfaces and then finds this one, turning it into a set
1228+ of AppIDs
1229+
1230+ \param in_interface Which interface to get the set of apps for
1231+*/
1232+std::set<AppID> Info::appsForInterface(const std::string &in_interface) const
1233+{
1234+ bool interfacefound = false;
1235+ std::set<AppID> appids;
1236+
1237+ try
1238+ {
1239+ forAllPlugs([this, &interfacefound, &appids, in_interface](JsonObject *ifaceobj) {
1240+ std::string interfacename = json_object_get_string_member(ifaceobj, "interface");
1241+ if (interfacename != in_interface)
1242+ {
1243+ return;
1244+ }
1245+
1246+ interfacefound = true;
1247+
1248+ auto cname = json_object_get_string_member(ifaceobj, "snap");
1249+ if (cname == nullptr)
1250+ {
1251+ return;
1252+ }
1253+ std::string snapname(cname);
1254+
1255+ auto pkginfo = pkgInfo(AppID::Package::from_raw(snapname));
1256+ if (!pkginfo)
1257+ {
1258+ return;
1259+ }
1260+
1261+ std::string revision = pkginfo->revision;
1262+
1263+ auto apps = json_object_get_array_member(ifaceobj, "apps");
1264+ for (unsigned int k = 0; k < json_array_get_length(apps); k++)
1265+ {
1266+ std::string appname = json_array_get_string_element(apps, k);
1267+
1268+ appids.emplace(AppID(AppID::Package::from_raw(snapname), /* package */
1269+ AppID::AppName::from_raw(appname), /* appname */
1270+ AppID::Version::from_raw(revision))); /* version */
1271+ }
1272+ });
1273+
1274+ if (!interfacefound)
1275+ {
1276+ g_debug("Unable to find information on interface '%s'", in_interface.c_str());
1277+ }
1278+ }
1279+ catch (std::runtime_error &e)
1280+ {
1281+ g_warning("Unable to get interface information: %s", e.what());
1282+ }
1283+
1284+ return appids;
1285+}
1286+
1287+/** Finds all the interfaces for a specific appid
1288+
1289+ \param appid AppID to search for
1290+*/
1291+std::set<std::string> Info::interfacesForAppId(const AppID &appid) const
1292+{
1293+
1294+ std::set<std::string> interfaces;
1295+
1296+ try
1297+ {
1298+ forAllPlugs([&interfaces, appid](JsonObject *ifaceobj) {
1299+ auto snapname = json_object_get_string_member(ifaceobj, "snap");
1300+ if (snapname != appid.package.value())
1301+ {
1302+ return;
1303+ }
1304+
1305+ auto cinterfacename = json_object_get_string_member(ifaceobj, "interface");
1306+ if (cinterfacename == nullptr)
1307+ {
1308+ return;
1309+ }
1310+
1311+ std::string interfacename(cinterfacename);
1312+
1313+ auto apps = json_object_get_array_member(ifaceobj, "apps");
1314+ for (unsigned int k = 0; k < json_array_get_length(apps); k++)
1315+ {
1316+ std::string appname = json_array_get_string_element(apps, k);
1317+ if (appname == appid.appname.value())
1318+ {
1319+ interfaces.insert(interfacename);
1320+ }
1321+ }
1322+ });
1323+ }
1324+ catch (std::runtime_error &e)
1325+ {
1326+ g_warning("Unable to get interface information: %s", e.what());
1327+ }
1328+
1329+ return interfaces;
1330+}
1331+
1332+} // namespace snapd
1333+} // namespace app_launch
1334+} // namespace ubuntu
1335
1336=== added file 'libubuntu-app-launch/snapd-info.h'
1337--- libubuntu-app-launch/snapd-info.h 1970-01-01 00:00:00 +0000
1338+++ libubuntu-app-launch/snapd-info.h 2016-09-13 17:38:18 +0000
1339@@ -0,0 +1,76 @@
1340+/*
1341+ * Copyright © 2016 Canonical Ltd.
1342+ *
1343+ * This program is free software: you can redistribute it and/or modify it
1344+ * under the terms of the GNU General Public License version 3, as published
1345+ * by the Free Software Foundation.
1346+ *
1347+ * This program is distributed in the hope that it will be useful, but
1348+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1349+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1350+ * PURPOSE. See the GNU General Public License for more details.
1351+ *
1352+ * You should have received a copy of the GNU General Public License along
1353+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1354+ *
1355+ * Authors:
1356+ * Ted Gould <ted.gould@canonical.com>
1357+ */
1358+
1359+#pragma once
1360+
1361+#include <list>
1362+#include <memory>
1363+#include <set>
1364+
1365+#include <json-glib/json-glib.h>
1366+
1367+#include "appid.h"
1368+
1369+namespace ubuntu
1370+{
1371+namespace app_launch
1372+{
1373+namespace snapd
1374+{
1375+
1376+/** Class that implements the connection to Snapd allowing us to get info
1377+ from it in a C++ friendly way. */
1378+class Info
1379+{
1380+public:
1381+ Info();
1382+ virtual ~Info() = default;
1383+
1384+ /** Information that we can get from snapd about a package */
1385+ struct PkgInfo
1386+ {
1387+ std::string name; /**< Name of the package */
1388+ std::string version; /**< Version string provided by the package */
1389+ std::string revision; /**< Numerical always incrementing revision of the package */
1390+ std::string directory; /**< Directory that the snap is uncompressed into */
1391+ std::set<std::string> appnames; /**< List of appnames in the snap */
1392+ };
1393+ std::shared_ptr<PkgInfo> pkgInfo(const AppID::Package &package) const;
1394+
1395+ std::set<AppID> appsForInterface(const std::string &interface) const;
1396+
1397+ std::set<std::string> interfacesForAppId(const AppID &appid) const;
1398+
1399+private:
1400+ /** Path to the socket of snapd */
1401+ std::string snapdSocket;
1402+ /** Directory to use as the base for all snap packages when making paths. This
1403+ can be overridden with UBUNTU_APP_LAUNCH_SNAP_BASEDIR */
1404+ std::string snapBasedir;
1405+ /** Result of a check at init to see if the socket is available. If
1406+ not all functions will return null results. */
1407+ bool snapdExists = false;
1408+
1409+ std::shared_ptr<JsonNode> snapdJson(const std::string &endpoint) const;
1410+ void forAllPlugs(std::function<void(JsonObject *plugobj)> plugfunc) const;
1411+};
1412+
1413+} // namespace snapd
1414+} // namespace app_launch
1415+} // namespace ubuntu
1416
1417=== modified file 'libubuntu-app-launch/ubuntu-app-launch.cpp'
1418--- libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-13 17:38:17 +0000
1419+++ libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-13 17:38:18 +0000
1420@@ -316,6 +316,9 @@
1421 } else if (g_strcmp0(env, "JOB=application-legacy") == 0) {
1422 job_found = TRUE;
1423 job_legacy = TRUE;
1424+ } else if (g_strcmp0(env, "JOB=application-snap") == 0) {
1425+ job_found = TRUE;
1426+ job_legacy = TRUE;
1427 } else if (g_str_has_prefix(env, "INSTANCE=")) {
1428 instance = g_strdup(env + strlen("INSTANCE="));
1429 }
1430
1431=== modified file 'tests/CMakeLists.txt'
1432--- tests/CMakeLists.txt 2016-09-13 17:38:17 +0000
1433+++ tests/CMakeLists.txt 2016-09-13 17:38:18 +0000
1434@@ -3,6 +3,11 @@
1435 configure_file ("click-db-dir/test.conf.in" "${CMAKE_CURRENT_BINARY_DIR}/click-db-dir/test.conf" @ONLY)
1436 set_directory_properties (PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/click-db-dir/test.conf")
1437
1438+if(CURL_FOUND)
1439+add_definitions ( -DENABLE_SNAPPY=1 )
1440+endif()
1441+
1442+
1443 # Google Test
1444
1445 include_directories(${GTEST_INCLUDE_DIR})
1446@@ -35,6 +40,7 @@
1447 add_definitions ( -DSOCKET_DEMANGLER="${CMAKE_BINARY_DIR}/socket-demangler" )
1448 add_definitions ( -DSOCKET_DEMANGLER_INSTALL="${pkglibexecdir}/socket-demangler" )
1449 add_definitions ( -DSOCKET_TOOL="${CMAKE_CURRENT_BINARY_DIR}/socket-tool" )
1450+add_definitions ( -DSNAP_BASEDIR="${CMAKE_CURRENT_SOURCE_DIR}/snap-basedir" )
1451
1452 add_executable (libual-test
1453 libual-test.cc
1454@@ -57,6 +63,16 @@
1455 add_test (NAME libual-test COMMAND libual-test)
1456 add_test (NAME libual-cpp-test COMMAND libual-cpp-test)
1457
1458+# Snapd Info Test
1459+
1460+if(CURL_FOUND)
1461+add_definitions ( -DSNAPD_TEST_SOCKET="/tmp/snapd-test-socket" )
1462+add_executable (snapd-info-test
1463+ snapd-info-test.cpp)
1464+target_link_libraries (snapd-info-test gtest ${GTEST_LIBS} launcher-static)
1465+add_test (NAME snapd-info-test COMMAND snapd-info-test)
1466+endif()
1467+
1468 # List Apps
1469
1470 add_executable (list-apps
1471@@ -144,5 +160,7 @@
1472 libual-cpp-test.cc
1473 list-apps.cpp
1474 eventually-fixture.h
1475+ snapd-info-test.cpp
1476+ snapd-mock.h
1477 zg-test.cc
1478 )
1479
1480=== modified file 'tests/exec-util-test.cc'
1481--- tests/exec-util-test.cc 2016-09-13 17:38:17 +0000
1482+++ tests/exec-util-test.cc 2016-09-13 17:38:18 +0000
1483@@ -44,6 +44,7 @@
1484 g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
1485 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
1486 g_setenv("UBUNTU_APP_LAUNCH_LIBERTINE_LAUNCH", "libertine-launch", TRUE);
1487+ g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE);
1488
1489 service = dbus_test_service_new(NULL);
1490
1491
1492=== modified file 'tests/failure-test.cc'
1493--- tests/failure-test.cc 2016-08-06 02:57:13 +0000
1494+++ tests/failure-test.cc 2016-09-13 17:38:18 +0000
1495@@ -37,7 +37,6 @@
1496 virtual void TearDown() {
1497 g_test_dbus_down(testbus);
1498 g_clear_object(&testbus);
1499- return;
1500 }
1501 };
1502
1503@@ -48,7 +47,6 @@
1504 std::string * last = static_cast<std::string *>(user_data);
1505 *last = appid;
1506 }
1507- return;
1508 }
1509
1510 TEST_F(FailureTest, CrashTest)
1511@@ -75,8 +73,6 @@
1512 EXPECT_EVENTUALLY_EQ("foo", last_observer);
1513
1514 ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
1515-
1516- return;
1517 }
1518
1519 TEST_F(FailureTest, LegacyTest)
1520@@ -94,8 +90,23 @@
1521 EXPECT_EVENTUALLY_EQ("foo", last_observer);
1522
1523 ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
1524-
1525- return;
1526+}
1527+
1528+TEST_F(FailureTest, SnapTest)
1529+{
1530+ g_setenv("EXIT_STATUS", "-100", TRUE);
1531+ g_setenv("JOB", "application-snap", TRUE);
1532+ g_setenv("INSTANCE", "foo_bar_x123-1234", TRUE);
1533+
1534+ std::string last_observer;
1535+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
1536+
1537+ /* Status based */
1538+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1539+
1540+ EXPECT_EVENTUALLY_EQ("foo_bar_x123", last_observer);
1541+
1542+ ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
1543 }
1544
1545 static void
1546@@ -105,7 +116,6 @@
1547 std::string * last = static_cast<std::string *>(user_data);
1548 *last = appid;
1549 }
1550- return;
1551 }
1552
1553 TEST_F(FailureTest, StartTest)
1554@@ -123,6 +133,4 @@
1555 EXPECT_EVENTUALLY_EQ("foo", last_observer);
1556
1557 ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_start_observer, &last_observer));
1558-
1559- return;
1560 }
1561
1562=== modified file 'tests/libual-cpp-test.cc'
1563--- tests/libual-cpp-test.cc 2016-09-13 17:38:17 +0000
1564+++ tests/libual-cpp-test.cc 2016-09-13 17:38:18 +0000
1565@@ -38,6 +38,10 @@
1566 #include "eventually-fixture.h"
1567 #include "mir-mock.h"
1568
1569+#ifdef ENABLE_SNAPPY
1570+#include "snapd-mock.h"
1571+#endif
1572+
1573 class LibUAL : public EventuallyFixture
1574 {
1575 protected:
1576@@ -103,6 +107,13 @@
1577 g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
1578 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
1579
1580+#ifdef ENABLE_SNAPPY
1581+ g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", SNAPD_TEST_SOCKET, TRUE);
1582+ g_setenv("UBUNTU_APP_LAUNCH_SNAP_BASEDIR", SNAP_BASEDIR, TRUE);
1583+ g_setenv("UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT", "You betcha!", TRUE);
1584+ g_unlink(SNAPD_TEST_SOCKET);
1585+#endif
1586+
1587 service = dbus_test_service_new(NULL);
1588
1589 debugConnection();
1590@@ -117,6 +128,8 @@
1591 dbus_test_dbus_mock_object_add_method(mock, obj, "GetJobByName", G_VARIANT_TYPE("s"), G_VARIANT_TYPE("o"),
1592 "if args[0] == 'application-click':\n"
1593 " ret = dbus.ObjectPath('/com/test/application_click')\n"
1594+ "elif args[0] == 'application-snap':\n"
1595+ " ret = dbus.ObjectPath('/com/test/application_snap')\n"
1596 "elif args[0] == 'application-legacy':\n"
1597 " ret = dbus.ObjectPath('/com/test/application_legacy')\n"
1598 "elif args[0] == 'untrusted-helper':\n"
1599@@ -153,6 +166,36 @@
1600 g_variant_new_parsed(process_var), NULL);
1601 g_free(process_var);
1602
1603+#ifdef ENABLE_SNAPPY
1604+ /* Snap App */
1605+ auto snapjobobj =
1606+ dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL);
1607+
1608+ dbus_test_dbus_mock_object_add_method(
1609+ mock, snapjobobj, "Start", G_VARIANT_TYPE("(asb)"), NULL,
1610+ "if args[0][0] == 'APP_ID=unity8-package_foo_x123':"
1611+ " raise dbus.exceptions.DBusException('Foo running', name='com.ubuntu.Upstart0_6.Error.AlreadyStarted')",
1612+ NULL);
1613+
1614+ dbus_test_dbus_mock_object_add_method(mock, snapjobobj, "Stop", G_VARIANT_TYPE("(asb)"), NULL, "", NULL);
1615+
1616+ dbus_test_dbus_mock_object_add_method(mock, snapjobobj, "GetAllInstances", NULL, G_VARIANT_TYPE("ao"),
1617+ "ret = [ dbus.ObjectPath('/com/test/snapp_instance') ]", NULL);
1618+
1619+ dbus_test_dbus_mock_object_add_method(mock, snapjobobj, "GetInstanceByName", G_VARIANT_TYPE_STRING,
1620+ G_VARIANT_TYPE("o"), "ret = dbus.ObjectPath('/com/test/snapp_instance')",
1621+ NULL);
1622+
1623+ auto snapinstobj =
1624+ dbus_test_dbus_mock_get_object(mock, "/com/test/snapp_instance", "com.ubuntu.Upstart0_6.Instance", NULL);
1625+ dbus_test_dbus_mock_object_add_property(mock, snapinstobj, "name", G_VARIANT_TYPE_STRING,
1626+ g_variant_new_string("unity8-package_foo_x123-"), NULL);
1627+ gchar* snapprocess_var = g_strdup_printf("[('main', %d)]", getpid());
1628+ dbus_test_dbus_mock_object_add_property(mock, snapinstobj, "processes", G_VARIANT_TYPE("a(si)"),
1629+ g_variant_new_parsed(snapprocess_var), NULL);
1630+ g_free(snapprocess_var);
1631+#endif
1632+
1633 /* Legacy App */
1634 DbusTestDbusMockObject* ljobobj =
1635 dbus_test_dbus_mock_get_object(mock, "/com/test/application_legacy", "com.ubuntu.Upstart0_6.Job", NULL);
1636@@ -249,6 +292,10 @@
1637 g_object_unref(bus);
1638
1639 ASSERT_EVENTUALLY_EQ(nullptr, bus);
1640+
1641+#ifdef ENABLE_SNAPPY
1642+ g_unlink(SNAPD_TEST_SOCKET);
1643+#endif
1644 }
1645
1646 GVariant* find_env(GVariant* env_array, const gchar* var)
1647@@ -412,6 +459,146 @@
1648 ASSERT_EQ(dbus_test_dbus_mock_object_check_method_call(mock, obj, "Stop", NULL, NULL), 1);
1649 }
1650
1651+#ifdef ENABLE_SNAPPY
1652+/* Snapd mock data */
1653+static std::pair<std::string, std::string> interfaces{
1654+ "GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
1655+ SnapdMock::httpJsonResponse(
1656+ SnapdMock::snapdOkay(SnapdMock::interfacesJson({{"unity8", "unity8-package", {"foo", "single"}}})))};
1657+static std::pair<std::string, std::string> u8Package{
1658+ "GET /v2/snaps/unity8-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
1659+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(
1660+ SnapdMock::packageJson("unity8-package", "active", "app", "1.2.3.4", "x123", {"foo", "single"})))};
1661+
1662+TEST_F(LibUAL, ApplicationIdSnap)
1663+{
1664+ SnapdMock snapd{SNAPD_TEST_SOCKET,
1665+ {u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package,
1666+ u8Package, u8Package, u8Package, u8Package, u8Package, u8Package, u8Package}};
1667+ registry = std::make_shared<ubuntu::app_launch::Registry>();
1668+
1669+ EXPECT_EQ("unity8-package_foo_x123", (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package"));
1670+ EXPECT_EQ("unity8-package_foo_x123",
1671+ (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package", "foo"));
1672+ EXPECT_EQ("unity8-package_single_x123",
1673+ (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package", "single"));
1674+ EXPECT_EQ("unity8-package_single_x123",
1675+ (std::string)ubuntu::app_launch::AppID::discover(
1676+ registry, "unity8-package", ubuntu::app_launch::AppID::ApplicationWildcard::LAST_LISTED));
1677+ EXPECT_EQ("unity8-package_foo_x123",
1678+ (std::string)ubuntu::app_launch::AppID::discover(registry, "unity8-package", "foo", "x123"));
1679+
1680+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover(registry, "unity7-package"));
1681+}
1682+
1683+TEST_F(LibUAL, StartSnapApplication)
1684+{
1685+ SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, interfaces, u8Package}};
1686+ registry = std::make_shared<ubuntu::app_launch::Registry>();
1687+
1688+ auto obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL);
1689+
1690+ /* Basic make sure we can send the event */
1691+ auto appid = ubuntu::app_launch::AppID::parse("unity8-package_single_x123");
1692+ auto app = ubuntu::app_launch::Application::create(appid, registry);
1693+ app->launch();
1694+
1695+ EXPECT_EQ(1, dbus_test_dbus_mock_object_check_method_call(mock, obj, "Start", NULL, NULL));
1696+
1697+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
1698+
1699+ /* Now look at the details of the call */
1700+ app->launch();
1701+
1702+ guint len = 0;
1703+ const DbusTestDbusMockCall* calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
1704+ EXPECT_NE(nullptr, calls);
1705+ EXPECT_EQ(1, len);
1706+
1707+ EXPECT_STREQ("Start", calls->name);
1708+ EXPECT_EQ(2, g_variant_n_children(calls->params));
1709+
1710+ GVariant* block = g_variant_get_child_value(calls->params, 1);
1711+ EXPECT_TRUE(g_variant_get_boolean(block));
1712+ g_variant_unref(block);
1713+
1714+ GVariant* env = g_variant_get_child_value(calls->params, 0);
1715+ EXPECT_TRUE(check_env(env, "APP_ID", "unity8-package_single_x123"));
1716+ g_variant_unref(env);
1717+
1718+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
1719+
1720+ /* Let's pass some URLs */
1721+ std::vector<ubuntu::app_launch::Application::URL> urls{
1722+ ubuntu::app_launch::Application::URL::from_raw("http://ubuntu.com/"),
1723+ ubuntu::app_launch::Application::URL::from_raw("https://ubuntu.com/"),
1724+ ubuntu::app_launch::Application::URL::from_raw("file:///home/phablet/test.txt")};
1725+
1726+ app->launch(urls);
1727+
1728+ len = 0;
1729+ calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
1730+ EXPECT_NE(nullptr, calls);
1731+ EXPECT_EQ(1, len);
1732+
1733+ env = g_variant_get_child_value(calls->params, 0);
1734+ EXPECT_TRUE(check_env(env, "APP_ID", "unity8-package_single_x123"));
1735+ EXPECT_TRUE(
1736+ check_env(env, "APP_URIS", "'http://ubuntu.com/' 'https://ubuntu.com/' 'file:///home/phablet/test.txt'"));
1737+ g_variant_unref(env);
1738+
1739+ return;
1740+}
1741+
1742+TEST_F(LibUAL, StartSnapApplicationTest)
1743+{
1744+ SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, interfaces, u8Package}};
1745+ registry = std::make_shared<ubuntu::app_launch::Registry>();
1746+
1747+ auto obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL);
1748+
1749+ /* Basic make sure we can send the event */
1750+ auto appid = ubuntu::app_launch::AppID::parse("unity8-package_single_x123");
1751+ auto app = ubuntu::app_launch::Application::create(appid, registry);
1752+ app->launchTest();
1753+
1754+ guint len = 0;
1755+ auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
1756+ EXPECT_NE(nullptr, calls);
1757+ EXPECT_EQ(1, len);
1758+
1759+ EXPECT_STREQ("Start", calls->name);
1760+ EXPECT_EQ(2, g_variant_n_children(calls->params));
1761+
1762+ GVariant* block = g_variant_get_child_value(calls->params, 1);
1763+ EXPECT_TRUE(g_variant_get_boolean(block));
1764+ g_variant_unref(block);
1765+
1766+ GVariant* env = g_variant_get_child_value(calls->params, 0);
1767+ EXPECT_TRUE(check_env(env, "APP_ID", "unity8-package_single_x123"));
1768+ EXPECT_TRUE(check_env(env, "QT_LOAD_TESTABILITY", "1"));
1769+ g_variant_unref(env);
1770+}
1771+
1772+TEST_F(LibUAL, StopSnapApplication)
1773+{
1774+ SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, interfaces, u8Package}};
1775+ registry = std::make_shared<ubuntu::app_launch::Registry>();
1776+
1777+ auto obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_snap", "com.ubuntu.Upstart0_6.Job", NULL);
1778+
1779+ auto appid = ubuntu::app_launch::AppID::parse("unity8-package_foo_x123");
1780+ auto app = ubuntu::app_launch::Application::create(appid, registry);
1781+
1782+ ASSERT_TRUE(app->hasInstances());
1783+ EXPECT_EQ(1, app->instances().size());
1784+
1785+ app->instances()[0]->stop();
1786+
1787+ ASSERT_EQ(dbus_test_dbus_mock_object_check_method_call(mock, obj, "Stop", NULL, NULL), 1);
1788+}
1789+#endif
1790+
1791 /* NOTE: The fact that there is 'libertine-data' in these strings is because
1792 we're using one CACHE_HOME for this test suite and the libertine functions
1793 need to pull things from there, where these are only comparisons. It's just
1794@@ -579,9 +766,18 @@
1795
1796 TEST_F(LibUAL, ApplicationList)
1797 {
1798+#ifdef ENABLE_SNAPPY
1799+ SnapdMock snapd{SNAPD_TEST_SOCKET, {u8Package, u8Package, u8Package, interfaces, u8Package}};
1800+ registry = std::make_shared<ubuntu::app_launch::Registry>();
1801+#endif
1802+
1803 auto apps = ubuntu::app_launch::Registry::runningApps(registry);
1804
1805+#ifdef ENABLE_SNAPPY
1806+ ASSERT_EQ(4, apps.size());
1807+#else
1808 ASSERT_EQ(3, apps.size());
1809+#endif
1810
1811 apps.sort([](const std::shared_ptr<ubuntu::app_launch::Application>& a,
1812 const std::shared_ptr<ubuntu::app_launch::Application>& b) {
1813@@ -592,7 +788,9 @@
1814 });
1815
1816 EXPECT_EQ("com.test.good_application_1.2.3", (std::string)apps.front()->appId());
1817- EXPECT_EQ("single", (std::string)apps.back()->appId());
1818+#ifdef ENABLE_SNAPPY
1819+ EXPECT_EQ("unity8-package_foo_x123", (std::string)apps.back()->appId());
1820+#endif
1821 }
1822
1823 typedef struct
1824
1825=== modified file 'tests/libual-test.cc'
1826--- tests/libual-test.cc 2016-09-13 17:38:17 +0000
1827+++ tests/libual-test.cc 2016-09-13 17:38:18 +0000
1828@@ -89,6 +89,8 @@
1829 g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
1830 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
1831
1832+ g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", "/this/should/not/exist", TRUE);
1833+
1834 service = dbus_test_service_new(NULL);
1835
1836 debugConnection();
1837
1838=== modified file 'tests/list-apps.cpp'
1839--- tests/list-apps.cpp 2016-09-13 17:38:17 +0000
1840+++ tests/list-apps.cpp 2016-09-13 17:38:18 +0000
1841@@ -27,9 +27,15 @@
1842 #include "application-impl-click.h"
1843 #include "application-impl-legacy.h"
1844 #include "application-impl-libertine.h"
1845+#include "application-impl-snap.h"
1846 #include "application.h"
1847 #include "registry.h"
1848
1849+#ifdef ENABLE_SNAPPY
1850+#include "snapd-mock.h"
1851+#define SNAPD_LIST_APPS_SOCKET SNAPD_TEST_SOCKET "-list-apps"
1852+#endif
1853+
1854 class ListApps : public EventuallyFixture
1855 {
1856 protected:
1857@@ -38,6 +44,11 @@
1858
1859 virtual void SetUp()
1860 {
1861+#ifdef ENABLE_SNAPPY
1862+ /* Ensure it is cleared */
1863+ g_unlink(SNAPD_LIST_APPS_SOCKET);
1864+#endif
1865+
1866 /* Click DB test mode */
1867 g_setenv("TEST_CLICK_DB", CMAKE_BINARY_DIR "/click-db-dir", TRUE);
1868 g_setenv("TEST_CLICK_USER", "test-user", TRUE);
1869@@ -50,6 +61,12 @@
1870 g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
1871 g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
1872
1873+#ifdef ENABLE_SNAPPY
1874+ g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", SNAPD_LIST_APPS_SOCKET, TRUE);
1875+ g_setenv("UBUNTU_APP_LAUNCH_SNAP_BASEDIR", SNAP_BASEDIR, TRUE);
1876+ g_setenv("UBUNTU_APP_LAUNCH_DISABLE_SNAPD_TIMEOUT", "You betcha!", TRUE);
1877+#endif
1878+
1879 testbus = g_test_dbus_new(G_TEST_DBUS_NONE);
1880 g_test_dbus_up(testbus);
1881
1882@@ -60,6 +77,10 @@
1883
1884 virtual void TearDown()
1885 {
1886+#ifdef ENABLE_SNAPPY
1887+ g_unlink(SNAPD_LIST_APPS_SOCKET);
1888+#endif
1889+
1890 g_object_unref(bus);
1891
1892 g_test_dbus_down(testbus);
1893@@ -159,8 +180,62 @@
1894 EXPECT_TRUE(findApp(apps, "container-name_user-app_0.0"));
1895 }
1896
1897+#ifdef ENABLE_SNAPPY
1898+static std::pair<std::string, std::string> interfaces{
1899+ "GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
1900+ SnapdMock::httpJsonResponse(
1901+ SnapdMock::snapdOkay(SnapdMock::interfacesJson({{"unity8", "unity8-package", {"foo", "bar"}},
1902+ {"unity7", "unity7-package", {"single", "multiple"}},
1903+ {"x11", "x11-package", {"multiple", "hidden"}}
1904+
1905+ })))};
1906+static std::pair<std::string, std::string> u8Package{
1907+ "GET /v2/snaps/unity8-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
1908+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(
1909+ SnapdMock::packageJson("unity8-package", "active", "app", "1.2.3.4", "x123", {"foo", "bar"})))};
1910+static std::pair<std::string, std::string> u7Package{
1911+ "GET /v2/snaps/unity7-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
1912+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(SnapdMock::packageJson(
1913+ "unity7-package", "active", "app", "1.2.3.4", "x123", {"scope", "single", "multiple"})))};
1914+static std::pair<std::string, std::string> x11Package{
1915+ "GET /v2/snaps/x11-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
1916+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(
1917+ SnapdMock::packageJson("x11-package", "active", "app", "1.2.3.4", "x123", {"multiple", "hidden"})))};
1918+
1919+TEST_F(ListApps, ListSnap)
1920+{
1921+ SnapdMock mock{SNAPD_LIST_APPS_SOCKET,
1922+ {interfaces, u7Package, u7Package, u7Package, /* unity7 check */
1923+ interfaces, u8Package, u8Package, u8Package, /* unity8 check */
1924+ interfaces, x11Package, x11Package, x11Package}}; /* x11 check */
1925+ auto registry = std::make_shared<ubuntu::app_launch::Registry>();
1926+
1927+ auto apps = ubuntu::app_launch::app_impls::Snap::list(registry);
1928+
1929+ printApps(apps);
1930+
1931+ mock.result();
1932+
1933+ EXPECT_EQ(4, apps.size());
1934+ EXPECT_TRUE(findApp(apps, "unity8-package_foo_x123"));
1935+ EXPECT_TRUE(findApp(apps, "unity7-package_single_x123"));
1936+ EXPECT_TRUE(findApp(apps, "unity7-package_multiple_x123"));
1937+ EXPECT_TRUE(findApp(apps, "x11-package_multiple_x123"));
1938+
1939+ EXPECT_FALSE(findApp(apps, "unity8-package_bar_x123"));
1940+ EXPECT_FALSE(findApp(apps, "unity7-package_scope_x123"));
1941+ EXPECT_FALSE(findApp(apps, "x11-package_hidden_x123"));
1942+}
1943+#endif
1944+
1945 TEST_F(ListApps, ListAll)
1946 {
1947+#ifdef ENABLE_SNAPPY
1948+ SnapdMock mock{SNAPD_LIST_APPS_SOCKET,
1949+ {interfaces, u7Package, u7Package, u7Package, /* unity7 check */
1950+ interfaces, u8Package, u8Package, u8Package, /* unity8 check */
1951+ interfaces, x11Package, x11Package, x11Package}}; /* x11 check */
1952+#endif
1953 auto registry = std::make_shared<ubuntu::app_launch::Registry>();
1954
1955 /* Get all the apps */
1956@@ -168,5 +243,9 @@
1957
1958 printApps(apps);
1959
1960+#ifdef ENABLE_SNAPPY
1961+ EXPECT_EQ(18, apps.size());
1962+#else
1963 EXPECT_EQ(14, apps.size());
1964+#endif
1965 }
1966
1967=== added directory 'tests/snap-basedir'
1968=== added directory 'tests/snap-basedir/unity7-package'
1969=== added directory 'tests/snap-basedir/unity7-package/x123'
1970=== added directory 'tests/snap-basedir/unity7-package/x123/meta'
1971=== added symlink 'tests/snap-basedir/unity7-package/x123/meta/gui'
1972=== target is u'../../../../applications/'
1973=== added directory 'tests/snap-basedir/unity8-package'
1974=== added directory 'tests/snap-basedir/unity8-package/x123'
1975=== added directory 'tests/snap-basedir/unity8-package/x123/meta'
1976=== added symlink 'tests/snap-basedir/unity8-package/x123/meta/gui'
1977=== target is u'../../../../applications/'
1978=== added directory 'tests/snap-basedir/x11-package'
1979=== added directory 'tests/snap-basedir/x11-package/x123'
1980=== added directory 'tests/snap-basedir/x11-package/x123/meta'
1981=== added symlink 'tests/snap-basedir/x11-package/x123/meta/gui'
1982=== target is u'../../../../applications/'
1983=== added file 'tests/snapd-info-test.cpp'
1984--- tests/snapd-info-test.cpp 1970-01-01 00:00:00 +0000
1985+++ tests/snapd-info-test.cpp 2016-09-13 17:38:18 +0000
1986@@ -0,0 +1,150 @@
1987+/*
1988+ * Copyright © 2016 Canonical Ltd.
1989+ *
1990+ * This program is free software: you can redistribute it and/or modify it
1991+ * under the terms of the GNU General Public License version 3, as published
1992+ * by the Free Software Foundation.
1993+ *
1994+ * This program is distributed in the hope that it will be useful, but
1995+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1996+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1997+ * PURPOSE. See the GNU General Public License for more details.
1998+ *
1999+ * You should have received a copy of the GNU General Public License along
2000+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2001+ *
2002+ * Authors:
2003+ * Ted Gould <ted.gould@canonical.com>
2004+ */
2005+
2006+#include "snapd-info.h"
2007+#include "snapd-mock.h"
2008+#include <gio/gio.h>
2009+#include <glib/gstdio.h>
2010+#include <gtest/gtest.h>
2011+
2012+class SnapdInfo : public ::testing::Test
2013+{
2014+protected:
2015+ virtual void SetUp()
2016+ {
2017+ g_setenv("UBUNTU_APP_LAUNCH_SNAPD_SOCKET", SNAPD_TEST_SOCKET, TRUE);
2018+ }
2019+
2020+ virtual void TearDown()
2021+ {
2022+ g_unlink(SNAPD_TEST_SOCKET);
2023+ }
2024+};
2025+
2026+TEST_F(SnapdInfo, Init)
2027+{
2028+ auto info = std::make_shared<ubuntu::app_launch::snapd::Info>();
2029+
2030+ info.reset();
2031+}
2032+
2033+TEST_F(SnapdInfo, PackageInfo)
2034+{
2035+ SnapdMock mock{SNAPD_TEST_SOCKET,
2036+ {{"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2037+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(SnapdMock::packageJson(
2038+ "test-package", "active", "app", "1.2.3.4", "x123", {"foo", "bar"})))}}};
2039+ auto info = std::make_shared<ubuntu::app_launch::snapd::Info>();
2040+
2041+ auto pkginfo = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package"));
2042+
2043+ mock.result();
2044+
2045+ ASSERT_NE(nullptr, pkginfo);
2046+ EXPECT_EQ("test-package", pkginfo->name);
2047+ EXPECT_EQ("1.2.3.4", pkginfo->version);
2048+ EXPECT_EQ("x123", pkginfo->revision);
2049+ EXPECT_EQ("/snap/test-package/x123", pkginfo->directory);
2050+ EXPECT_NE(pkginfo->appnames.end(), pkginfo->appnames.find("foo"));
2051+ EXPECT_NE(pkginfo->appnames.end(), pkginfo->appnames.find("bar"));
2052+}
2053+
2054+TEST_F(SnapdInfo, AppsForInterface)
2055+{
2056+ SnapdMock mock{SNAPD_TEST_SOCKET,
2057+ {{"GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2058+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(
2059+ SnapdMock::interfacesJson({{"unity8", "test-package", {"foo", "bar"}}})))},
2060+ {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2061+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(SnapdMock::packageJson(
2062+ "test-package", "active", "app", "1.2.3.4", "x123", {"foo", "bar"})))}}};
2063+
2064+ auto info = std::make_shared<ubuntu::app_launch::snapd::Info>();
2065+
2066+ auto apps = info->appsForInterface("unity8");
2067+
2068+ mock.result();
2069+
2070+ EXPECT_EQ(2, apps.size());
2071+ EXPECT_NE(apps.end(), apps.find(ubuntu::app_launch::AppID::parse("test-package_foo_x123")));
2072+ EXPECT_NE(apps.end(), apps.find(ubuntu::app_launch::AppID::parse("test-package_bar_x123")));
2073+}
2074+
2075+TEST_F(SnapdInfo, InterfacesForAppID)
2076+{
2077+ SnapdMock mock{SNAPD_TEST_SOCKET,
2078+ {{"GET /v2/interfaces HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2079+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay(
2080+ SnapdMock::interfacesJson({{"unity8", "test-package", {"foo"}},
2081+ {"noniface", "test-package", {"bar", "bamf", "bunny"}},
2082+ {"unity7", "test-package", {"bar", "foo"}}})))}
2083+
2084+ }};
2085+
2086+ auto info = std::make_shared<ubuntu::app_launch::snapd::Info>();
2087+ auto appid = ubuntu::app_launch::AppID::parse("test-package_foo_x123");
2088+
2089+ auto ifaces = info->interfacesForAppId(appid);
2090+
2091+ mock.result();
2092+
2093+ EXPECT_EQ(2, ifaces.size());
2094+ EXPECT_NE(ifaces.end(), ifaces.find("unity7"));
2095+ EXPECT_NE(ifaces.end(), ifaces.find("unity8"));
2096+}
2097+
2098+TEST_F(SnapdInfo, BadJson)
2099+{
2100+ SnapdMock mock{
2101+ SNAPD_TEST_SOCKET,
2102+ {
2103+ {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2104+ SnapdMock::httpJsonResponse("«This is not valid JSON»")},
2105+ {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2106+ SnapdMock::httpJsonResponse("{ 'status': 'FAIL', 'status-code': 404, 'type': 'sync', 'result': { } }")},
2107+ {"GET /v2/snaps/test-package HTTP/1.1\r\nHost: snapd\r\nAccept: */*\r\n\r\n",
2108+ SnapdMock::httpJsonResponse(SnapdMock::snapdOkay("'«This is not an object»'"))},
2109+
2110+ }};
2111+ auto info = std::make_shared<ubuntu::app_launch::snapd::Info>();
2112+
2113+ auto badjson = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package"));
2114+
2115+ EXPECT_EQ(nullptr, badjson);
2116+
2117+ auto err404 = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package"));
2118+
2119+ EXPECT_EQ(nullptr, err404);
2120+
2121+ auto noobj = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package"));
2122+
2123+ EXPECT_EQ(nullptr, noobj);
2124+
2125+ /* We should still be getting good requests, so let's check them */
2126+ mock.result();
2127+}
2128+
2129+TEST_F(SnapdInfo, NoSocket)
2130+{
2131+ auto info = std::make_shared<ubuntu::app_launch::snapd::Info>();
2132+
2133+ auto nosocket = info->pkgInfo(ubuntu::app_launch::AppID::Package::from_raw("test-package"));
2134+
2135+ EXPECT_EQ(nullptr, nosocket);
2136+}
2137
2138=== added file 'tests/snapd-mock.h'
2139--- tests/snapd-mock.h 1970-01-01 00:00:00 +0000
2140+++ tests/snapd-mock.h 2016-09-13 17:38:18 +0000
2141@@ -0,0 +1,384 @@
2142+/*
2143+ * Copyright © 2016 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+ * Authors:
2158+ * Ted Gould <ted.gould@canonical.com>
2159+ */
2160+
2161+#include "glib-thread.h"
2162+#include <future>
2163+#include <gio/gio.h>
2164+#include <gio/gunixsocketaddress.h>
2165+#include <gtest/gtest.h>
2166+#include <list>
2167+#include <numeric>
2168+
2169+class SnapdMock
2170+{
2171+public:
2172+ /** Initialize the mock with a list of files to use as
2173+ input and output. Each will be sent in order. */
2174+ SnapdMock(const std::string &socketPath, std::list<std::pair<std::string, std::string>> interactions)
2175+ : thread()
2176+ {
2177+ for (auto interaction : interactions)
2178+ {
2179+ TestCase testcase{interaction.first, interaction.second, {}, {}};
2180+ testCases.push_back(testcase);
2181+ }
2182+
2183+ /* Build the socket */
2184+ socketService = thread.executeOnThread<std::shared_ptr<GSocketService>>([this, socketPath]() {
2185+ auto service = std::shared_ptr<GSocketService>(g_socket_service_new(), [](GSocketService *service) {
2186+ if (service != nullptr)
2187+ {
2188+ g_socket_service_stop(service);
2189+ g_object_unref(service);
2190+ }
2191+ });
2192+
2193+ GError *error = nullptr;
2194+ auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX, /* unix */
2195+ G_SOCKET_TYPE_STREAM, /* type */
2196+ G_SOCKET_PROTOCOL_DEFAULT, /* protocol */
2197+ &error);
2198+
2199+ if (error != nullptr)
2200+ {
2201+ std::string message = "Failed to create socket: " + std::string(error->message);
2202+ g_error_free(error);
2203+ throw std::runtime_error(message);
2204+ }
2205+
2206+ auto socketaddr = g_unix_socket_address_new(socketPath.c_str());
2207+ if (socketaddr == nullptr)
2208+ {
2209+ throw std::runtime_error("Unable to create a socket address for: " + socketPath);
2210+ }
2211+
2212+ g_socket_bind(socket, socketaddr, TRUE, &error);
2213+ if (error != nullptr)
2214+ {
2215+ std::string message =
2216+ "Unable to connect socket to address '" + socketPath + "': " + std::string(error->message);
2217+ g_error_free(error);
2218+ throw std::runtime_error(message);
2219+ }
2220+
2221+ g_socket_listener_add_socket(G_SOCKET_LISTENER(service.get()), socket, nullptr, &error);
2222+ if (error != nullptr)
2223+ {
2224+ std::string message = "Unable to listen to socket: " + std::string(error->message);
2225+ g_error_free(error);
2226+ throw std::runtime_error(message);
2227+ }
2228+
2229+ g_signal_connect(service.get(), "incoming", G_CALLBACK(serviceConnectedStatic), this);
2230+
2231+ g_socket_service_start(service.get());
2232+
2233+ g_socket_listen(socket, &error);
2234+ if (error != nullptr)
2235+ {
2236+ std::string message = "Unable to listen to socket: " + std::string(error->message);
2237+ g_error_free(error);
2238+ throw std::runtime_error(message);
2239+ }
2240+
2241+ g_debug("Initialized snapd-mock with %d test cases", int(testCases.size()));
2242+ return service;
2243+ });
2244+ }
2245+
2246+ ~SnapdMock()
2247+ {
2248+ thread.executeOnThread<bool>([this]() {
2249+ for (auto testcase : testCases)
2250+ {
2251+ testcase.connection.reset(); /* ensure these get dropped on teh thread */
2252+ }
2253+ socketService.reset();
2254+
2255+ return true;
2256+ });
2257+ thread.quit();
2258+ }
2259+
2260+ /** Check to see if the mock was used successfully */
2261+ inline void result()
2262+ {
2263+ /* Ensure we get queued events off the mainloop */
2264+ std::promise<void> promise;
2265+ thread.timeout(std::chrono::milliseconds{10}, [&promise]() { promise.set_value(); });
2266+ promise.get_future().wait();
2267+
2268+ for (auto testcase : testCases)
2269+ {
2270+ EXPECT_EQ(testcase.input, testcase.result);
2271+ }
2272+
2273+ EXPECT_EQ(0, extraCases.size());
2274+
2275+ for (auto testcase : extraCases)
2276+ {
2277+ EXPECT_EQ(std::string{}, testcase.result);
2278+ }
2279+ }
2280+
2281+private:
2282+ GLib::ContextThread thread;
2283+ std::shared_ptr<GSocketService> socketService;
2284+
2285+ struct TestCase
2286+ {
2287+ std::string input;
2288+ std::string output;
2289+ std::string result;
2290+ std::shared_ptr<GSocketConnection> connection;
2291+ };
2292+
2293+ std::list<TestCase> testCases;
2294+ std::list<TestCase> extraCases;
2295+
2296+ static gboolean serviceConnectedStatic(GSocketService *service,
2297+ GSocketConnection *connection,
2298+ GObject *source_obj,
2299+ gpointer userdata) noexcept
2300+ {
2301+ auto obj = reinterpret_cast<SnapdMock *>(userdata);
2302+ auto cppconn = std::shared_ptr<GSocketConnection>(G_SOCKET_CONNECTION(g_object_ref(connection)),
2303+ [](GSocketConnection *con) { g_clear_object(&con); });
2304+ return obj->serviceConnected(cppconn) ? TRUE : FALSE;
2305+ }
2306+
2307+ bool serviceConnected(std::shared_ptr<GSocketConnection> connection)
2308+ {
2309+ for (auto &testcase : testCases)
2310+ {
2311+ if (testcase.connection)
2312+ {
2313+ /* We don't want ones that already have a connection */
2314+ continue;
2315+ }
2316+
2317+ testcase.connection = connection;
2318+
2319+ auto input = g_io_stream_get_input_stream(G_IO_STREAM(connection.get())); // transfer: none
2320+ g_input_stream_read_bytes_async(input, /* stream */
2321+ 1024, /* 1K at a time */
2322+ G_PRIORITY_DEFAULT, /* default priority */
2323+ thread.getCancellable().get(), /* cancel */
2324+ caseInputStatic, /* callback */
2325+ &testcase);
2326+
2327+ auto output = g_io_stream_get_output_stream(G_IO_STREAM(connection.get())); // transfer: none
2328+ if (output == nullptr)
2329+ {
2330+ g_warning("No output stream avilable with connection!");
2331+ }
2332+
2333+ g_output_stream_write_all_async(
2334+ output, /* output stream */
2335+ testcase.output.c_str(), /* data */
2336+ testcase.output.size(), /* size */
2337+ G_PRIORITY_DEFAULT, /* priority */
2338+ thread.getCancellable().get(), /* cancel */
2339+ [](GObject *obj, GAsyncResult *res, gpointer userdata) -> void {
2340+ auto testcase = reinterpret_cast<TestCase *>(userdata);
2341+ gsize bytesout = 0;
2342+ GError *error = nullptr;
2343+
2344+ g_output_stream_write_all_finish(G_OUTPUT_STREAM(obj), res, &bytesout, &error);
2345+
2346+ if (error != nullptr)
2347+ {
2348+ g_warning("Unable to write out snapd connection: %s", error->message);
2349+ g_error_free(error);
2350+ return;
2351+ }
2352+
2353+ if (bytesout != testcase->output.size())
2354+ {
2355+ g_warning("Wrote out %d bytes in snapd socket but expected to write out %d", int(bytesout),
2356+ int(testcase->output.size()));
2357+ }
2358+
2359+ g_output_stream_close(G_OUTPUT_STREAM(obj), nullptr, nullptr);
2360+ checkConnection(testcase);
2361+ }, /* callback */
2362+ &testcase); /* expected size */
2363+
2364+ /* We got this one */
2365+ return true;
2366+ }
2367+
2368+ g_warning("Couldn't find a test case to use for the connection");
2369+ return false;
2370+ }
2371+
2372+ static void caseInputStatic(GObject *obj, GAsyncResult *res, gpointer userdata) noexcept
2373+ {
2374+ auto testcase = reinterpret_cast<TestCase *>(userdata);
2375+ GError *error = nullptr;
2376+ auto bytes = g_input_stream_read_bytes_finish(G_INPUT_STREAM(obj), res, &error);
2377+
2378+ if (error != nullptr)
2379+ {
2380+ g_warning("Error reading input socket: %s", error->message);
2381+ g_error_free(error);
2382+ return;
2383+ }
2384+
2385+ auto bytessize = g_bytes_get_size(bytes);
2386+ if (bytessize > 0) // zero means closed
2387+ {
2388+ auto data = reinterpret_cast<const char *>(g_bytes_get_data(bytes, nullptr));
2389+
2390+ for (unsigned int i = 0; i < bytessize; i++)
2391+ {
2392+ testcase->result.push_back(data[i]);
2393+ }
2394+
2395+ g_input_stream_read_bytes_async(G_INPUT_STREAM(obj), /* stream */
2396+ 1024, /* 1K at a time */
2397+ G_PRIORITY_DEFAULT, /* default priority */
2398+ nullptr, /* TODO? cancel */
2399+ caseInputStatic, /* callback */
2400+ userdata);
2401+ }
2402+ else
2403+ {
2404+ // g_debug("Request: %s", testcase->result.c_str());
2405+ g_input_stream_close(G_INPUT_STREAM(obj), nullptr, nullptr);
2406+ checkConnection(testcase);
2407+ }
2408+
2409+ g_bytes_unref(bytes);
2410+ }
2411+
2412+ static void checkConnection(TestCase *testcase)
2413+ {
2414+ auto input = g_io_stream_get_input_stream(G_IO_STREAM(testcase->connection.get())); // transfer: none
2415+ auto output = g_io_stream_get_output_stream(G_IO_STREAM(testcase->connection.get())); // transfer: none
2416+
2417+ if (g_input_stream_is_closed(input) && g_output_stream_is_closed(output))
2418+ {
2419+ g_io_stream_close(G_IO_STREAM(testcase->connection.get()), nullptr, nullptr);
2420+ }
2421+ }
2422+
2423+public:
2424+ static std::string httpJsonResponse(const std::string &json)
2425+ {
2426+ return "HTTP/1.1 200 OK\r\n" /* okay */
2427+ "Content-Type: application/json\r\n" /* json stuff */
2428+ "Content-Length: " +
2429+ std::to_string(json.size()) + "\r\n\r\n" + /* size of data */
2430+ json;
2431+ }
2432+
2433+ static std::string snapdOkay(const std::string &result)
2434+ {
2435+ return "{ 'status': 'OK', 'status-code': 200, " /* all okay */
2436+ " 'type': 'sync', " /* This lib is only sync */
2437+ " 'result': " +
2438+ result + /* here is what we were given */
2439+ "}";
2440+ }
2441+
2442+ static std::string packageJson(const std::string &name,
2443+ const std::string &status,
2444+ const std::string &type,
2445+ const std::string &version,
2446+ const std::string &revision,
2447+ std::list<std::string> apps)
2448+ {
2449+ std::string response = "{\n";
2450+
2451+ response += "'name': '" + name + "',\n";
2452+ response += "'status': '" + status + "',\n";
2453+ response += "'type': '" + type + "',\n";
2454+ response += "'version': '" + version + "',\n";
2455+ response += "'revision': '" + revision + "',\n";
2456+
2457+ response += "'apps': [ " + std::accumulate(apps.begin(), apps.end(), std::string{},
2458+ [](const std::string &builder, std::string entry) {
2459+ std::string json = "\n { 'name': '" + entry + "' }";
2460+ if (builder.empty())
2461+ {
2462+ return json;
2463+ }
2464+ else
2465+ {
2466+ return builder + "," + json;
2467+ }
2468+ }) +
2469+ "\n]\n";
2470+
2471+ return response + "}";
2472+ }
2473+
2474+ struct SnapdPlug
2475+ {
2476+ std::string interface;
2477+ std::string snap;
2478+ std::list<std::string> apps;
2479+ };
2480+
2481+ static std::string interfacesJson(const std::list<SnapdPlug> &plugs)
2482+ {
2483+ std::string response = "{\n";
2484+
2485+ response += "'plugs': [ " +
2486+ std::accumulate(plugs.begin(), plugs.end(), std::string{},
2487+ [](const std::string &builder, SnapdPlug plug) {
2488+ std::string json = "\n{\n";
2489+
2490+ json += "'interface': '" + plug.interface + "',\n";
2491+ json += "'snap': '" + plug.snap + "',\n";
2492+
2493+ json += "'apps': [ " +
2494+ std::accumulate(plug.apps.begin(), plug.apps.end(), std::string{},
2495+ [](const std::string &builder, std::string entry) {
2496+ std::string json = "'" + entry + "'";
2497+ if (builder.empty())
2498+ {
2499+ return json;
2500+ }
2501+ else
2502+ {
2503+ return builder + ", " + json;
2504+ }
2505+ }) +
2506+ " ]\n";
2507+
2508+ json += "}";
2509+
2510+ if (builder.empty())
2511+ {
2512+ return json;
2513+ }
2514+ else
2515+ {
2516+ return builder + "," + json;
2517+ }
2518+ }) +
2519+ "\n],\n";
2520+
2521+ response += "'slots': [ { 'foo': 'bar' } ]\n"; /* TODO: For future use */
2522+
2523+ return response + "}";
2524+ }
2525+};
2526
2527=== modified file 'upstart-jobs/CMakeLists.txt'
2528--- upstart-jobs/CMakeLists.txt 2014-08-30 02:31:30 +0000
2529+++ upstart-jobs/CMakeLists.txt 2016-09-13 17:38:18 +0000
2530@@ -24,6 +24,14 @@
2531 add_test(application-click.conf.test "${CMAKE_CURRENT_SOURCE_DIR}/test-conffile.sh" "${CMAKE_CURRENT_BINARY_DIR}/application-click.conf")
2532
2533 ####################
2534+# application-snap.conf
2535+####################
2536+
2537+configure_file("application-snap.conf.in" "${CMAKE_CURRENT_BINARY_DIR}/application-snap.conf" @ONLY)
2538+install(FILES "${CMAKE_CURRENT_BINARY_DIR}/application-snap.conf" DESTINATION "${CMAKE_INSTALL_DATADIR}/upstart/sessions")
2539+add_test(application-snap.conf.test "${CMAKE_CURRENT_SOURCE_DIR}/test-conffile.sh" "${CMAKE_CURRENT_BINARY_DIR}/application-snap.conf")
2540+
2541+####################
2542 # application-failed.conf
2543 ####################
2544
2545
2546=== modified file 'upstart-jobs/application-failed.conf.in'
2547--- upstart-jobs/application-failed.conf.in 2014-01-22 16:28:06 +0000
2548+++ upstart-jobs/application-failed.conf.in 2016-09-13 17:38:18 +0000
2549@@ -1,6 +1,6 @@
2550 description "Application Failing"
2551
2552-start on stopped application-legacy RESULT=failed or stopped application-click RESULT=failed
2553+start on stopped application-legacy RESULT=failed or stopped application-click RESULT=failed or stopped application-snap RESULT=failed
2554 task
2555
2556 exec @pkglibexecdir@/application-failed
2557
2558=== modified file 'upstart-jobs/application-logrotate.conf'
2559--- upstart-jobs/application-logrotate.conf 2014-08-30 02:23:36 +0000
2560+++ upstart-jobs/application-logrotate.conf 2016-09-13 17:38:18 +0000
2561@@ -11,4 +11,5 @@
2562 #
2563 find ${logdir} -mtime +2 -name "application-click-*.log.[1-9].gz" -delete
2564 find ${logdir} -mtime +2 -name "application-legacy-*.log.[1-9].gz" -delete
2565+ find ${logdir} -mtime +2 -name "application-snap-*.log.[1-9].gz" -delete
2566 end script
2567
2568=== added file 'upstart-jobs/application-snap.conf.in'
2569--- upstart-jobs/application-snap.conf.in 1970-01-01 00:00:00 +0000
2570+++ upstart-jobs/application-snap.conf.in 2016-09-13 17:38:18 +0000
2571@@ -0,0 +1,38 @@
2572+description "Application Launching for Snap Applications"
2573+author "Ted Gould <ted@canonical.com>"
2574+
2575+instance ${APP_ID}-${INSTANCE_ID}
2576+
2577+start on application-snap-start
2578+stop on application-end or desktop-end
2579+
2580+env APP_ID
2581+env APP_EXEC
2582+env APP_URIS
2583+env APP_DIR
2584+env APP_DESKTOP_FILE_PATH
2585+env APP_XMIR_ENABLE
2586+env INSTANCE_ID=""
2587+
2588+env UBUNTU_APP_LAUNCH_ARCH="@ubuntu_app_launch_arch@"
2589+export UBUNTU_APP_LAUNCH_ARCH
2590+
2591+# apparmor is taken care of by confine
2592+cgroup freezer
2593+
2594+# Initial OOM Score
2595+oom score 100
2596+
2597+# Remember, this is confined
2598+exec @pkglibexecdir@/exec-line-exec
2599+
2600+post-start exec @pkglibexecdir@/zg-report-app open
2601+post-stop script
2602+ @pkglibexecdir@/zg-report-app close
2603+ @pkglibexecdir@/cgroup-reap-all
2604+
2605+ DEVELOPER_MODE=`gdbus call --system --dest com.canonical.PropertyService --object-path /com/canonical/PropertyService --method com.canonical.PropertyService.GetProperty adb`
2606+ if [ "$DEVELOPER_MODE" != "(true,)" ] ; then
2607+ rm -f ${HOME}/.cache/upstart/application-snap-${APP_ID}-${INSTANCE_ID}.log*
2608+ fi
2609+end script

Subscribers

People subscribed via source and target branches