Tuesday, July 23, 2013

Chronopost Lost Parcel

Chronopost runs an ad to advise you to use their service to send your forgotten keys on your holiday location. I would definitively advise you against using their service for things with value.

Here's why.

(This post will be updated if anything worthwhile happens.)

I ordered some custom tee-shirts online and they were supposed to be delivered through Chronopost.

My order was supposed to be delivered on Friday 19/07/2013 between 9 and 13 at the address I gave for delivery.

On the preceding Thursday I received an email telling me:

"Vous pensez être absent à cette date ? Cliquez ici, avant ce soir minuit, pour programmer une nouvelle date de livraison ou demander une mise à disposition dans un point de proximité.

Sans instruction de votre part, Chronopost vous livrera à la date et à l'adresse indiquée dans ce mail."

So I changed the delivery address to the post office nearest to my work and received a confirmation email:

"L'envoi XW---------FR sera mis à disposition le vendredi 19 juillet 2013 dans le bureau de poste choisi. Un e-mail ou SMS vous avertira dès que le colis sera prêt à être retiré."

At 08:13 the next day, delivery started:

08:13 PARIS BD NEY CHRONOPOST
Envoi en cours de livraison
Then things went down hill..

At 10:59 the next day, the status was changed to delivered!

ven 19/07/2013
10:59 PARIS BD NEY CHRONOPOST
Livraison effectuée Nom du réceptionnaire:michel
ven 19/07/2013
Delivered to Michel ? Who is Michel ? Maybe the post office man ? I was confused but trustful.

Note that I never received a notification (email nor SMS). Strange, but why not.

  • I went to the BOINOD post office at 13:00 and they didn't know where the parcel was and told me to come back
  • I went to the BOINOD post office again at 16:30 and they told me the parcel was at Blrd Ney, a nearby Chronopost office
  • I went to Blrd Ney (had to pay for bus/tram), and 'Olivier' told me the parcel was a BOINOD. And told me to go back and ask them to call him back. It was now over 1815, I didn't trust going back to BOINOD a third time, and I was already late for a meeting so I tried something else.
  • I called support multiple times to try to get some information. Unfortunately I got cut multiple times (sometimes after queing for more than 10 minutes) and didn't manage to get more information before the phone service was closed. BTW, the automated voice recognition system is awfull, only picks up my instructions twice out of 14 tries, doesn't allow you to talk directly to a person (you have to spend ~2/3 min talking to their broken voice recognition system first, etc)
  • I called support the next day (Saturday) and they told me the parcel was at La Poste Chateau Rouge. As again I had no way to check if the parcel was there without going there (no direct phone number to POST office). I travelled there (with my family...), queued and got told the parcel wasn't there. The person in charge told me she would call me on the Monday to let me know what was going on.
  • On Sunday I left France... Goodbye simple way to retrieve my parcel.
  • On Monday no one called me...
  • On Tuesday I called Chronopost from Norway, and they told me the parcel had been delivered to the original address. That the ability to change the delivery address is only effective after a first failed delivery attempt, contrary to both what the email I received and the information on the web site. They told me the parcel was probably delivered to the building caretaker, while there's no caretaker at the delivery address. They took my phone number (again), and said they would call the next day after talking to the driver
  • On Saturday, I called. Apparently they haven't talked with the driver at all. They asked me again for my phone number (3rd or 4th time!). I give up. Chargeback it is!

To summarize: chronopost doesn't deliver where I asked them to deliver, doesn't deliver to whom I asked them to deliver. Also employees understand the system features (delivery address change) in different ways, read the tracking statuses in different ways, are unable to communicate with each other in a timely manner, are unable to keep me updated when they said they would (not even sure they are able to keep track of my contact information).

No wonder they lose parcels.

On top of that, they have no email address for contact. (*)

I wonder if they even try to do things like e.g. Toyota (http://en.wikipedia.org/wiki/The_Toyota_Way)

(*) a few cross search on Google and I have the CEO, Quality director and Client director emails. Mailing...

Update: my email helped it seems. Someone called me and a week later the parcel was found again. Note that I don't know where it was (neither Chronopost) and the statuses on the tracking page indicate that the parcel was delivered to the BOINOD post office the day after I went there!? And that information never made it into Chronopost's systems until almost 2 weeks after. Fishy...

Friday, January 11, 2013

Find command - find all files excluding some matching one path

Objective: So to list all files in current directory but those under .git.

I am often using find in combination to grep to exclude some of them:

    find . -type f | grep -v .git
but today I needed to move the logic under the find expression. It's a bit confusing to go from one tool to another when regular expressions don't work exactly the same. I spent a while getting this right so here's my findings.

This won't work:

    find . -type f -and -not -path ".git" -print
It should be -path "./.git*"

In other words this will work

    find . -type d -and -name .git -prune -o -print
    find . -type d -and -path "./.git" -prune -o -type f -print
    find . -type f -and -not -path "./.git*" -print
Now I can list all extensions of our project files in one line
    find . -type f -name "*.*" -and -not \( -path "./.git*" -or -path "./Library*" \) -print0 | xargs -0 -L1 basename | sed 's/.*\.//' | sort -u
Useful before starting to update a .gitattributes file.

Tuesday, December 25, 2012

Livebox2, episode 2

De retour chez mes parents qui ont changé leur Livebox d'Orange, et encore des problèmes de connectivité...


Malheureusement, le mode telnet n'étant plus disponible, les scripts de monitoring que j'avais écrit précédement ne fonctionnent plus...

J'ai jeté un oeil et l'interface de la livebox expose ses donnéees en REST au format JSON. Il serait dont possible de faire un programme qui récupère les données de manière automatique. Ici une preuve de faisabilité en 2 lignes de curl. Aussi sur le gist, une copie du javascript qui tourne sur la boite pour les curieux.

Je devrais pouvoir facilement mettre a jour mon script initial, mais au final celui ci est difficile d'utilisation sur d'autres plateformes.

Je me demande si je ne devrais pas faire un programme plus générique, capable de faire du monitoring des différentes Box ADSL. J'hésite entre une solution nodejs+highcharts ou une solution ruby... Suite au prochain épisode.

Mise à jour: ce projet python ligne de commande permet d automatiser le site speedtest.net en ligne de commande. Pratique.

[...]
Hosted by MEDIACTIVE (Toulouse): 1034.339ms
Testing download speed........................................
Download: 0.69 Mbit/s
Testing upload speed..................................................
Upload speed: 0.37 Mbit/s

Lexmark S305 on Ubuntu 11.04 and 12.04 - fixing the packages

My parent's S305 Lexmark printer was sitting unused in the 1.5 years since it had been purchased.

Yes it was compatible with Linux claimed the box, but the installation scripts we had were not working with our Ubuntu 11.04 computer.

Thanks to an update in Lexmark packages, I finally might have something that works.

A year ago, the printer was malfunctionning. Oh yes, you could download the packages from Lexmark's site (lexmark-inkjet-legacy-wJRE-1.0-1.i386.deb.sh), but first you had to edit the installer, and even then the printer was not working on our Ubuntu 1104 computer. The lexmark programs were either crashing, being unresponsive, or going 100%CPU. In the best case, half of the page was printed.

I gave it a last try today, before returning it to the shop (we re patient here).

I found out that Lexmark had new packages, downloaded them (lexmark-inkjet-legacy-1.0-1.i386.deb  lexmark-printer-utility-1.0-2.i386.deb  lexmark-scan-legacy-1.1-1.i386.deb), and installed them (dpkg -i lexmark*.deb)

Alas, the printer utility installer is broken:
Installing %%JARVIS_VENDOR_PREFIX%%hcp backend ...
cp: impossible d'évaluer «/usr/local/lexmark/printer_utility/bin/%%JARVIS_VENDOR_PREFIX%%hcp»: Aucun fichier ou dossier de ce type

You can fix the installer using this script. To install it, run this from the directory where you have downloaded the debs.

wget https://gist.github.com/raw/4374626/e946d2a3ad2e24657ddd32a84f31927970773745/fix_lexmark.sh
chmod +x fix_lexmark.sh
./fix_lexmark.sh
dpkg -i build/lexmark-printer-utility_1.0-1_i386.deb

You will notice that the new package is one revision below the original, that's because the DEBIAN/control in lexmark-printer-utility_1.0-2_i386.deb was invalid I guess...

Now from the first time the printers installs and prints without crashing. Unfortunately my black ink dried, and I get garbage out, but it does exactly the same from my Mac, so I guess it works now. Update as soon as we change the ink...

Lexmark, you still have work to do to claim you support Linux. The quality of those Ubuntu packages is crap.

Friday, November 9, 2012

Merging most of one git branch into another, without history rewrite

This little post explains how to partially commit a feature branch into a stable branch in git, without rewriting the history of any of the branches.

Having worked on a feature branch for a few weeks, I am left with 50+ commits on top of the common ancestor point. Yet I cannot merge all those changes back to stable, as some are still feature branch specific. And the feature branch is public (pushed to a remote repo) and used by our CI server. So I don't really want to rewrite its history.


S1                                (stable)
   \                            
    C1-F1-C2-C3-F2    (feature)

Basically I want to merge into stable C1, C2, C3 but keep F1, F2 in my branch.



So to be able to merge most of my changes into the stable branch, I am going to create an intermediate branch containing the changes I can have.

In effect I will have 3 branches

  • the stable branch
  • the feature branch
  • the stable_feature_common branch: where I keep work in sync between the 2 others.
Here's how I did it.

1. Find the common ancestor between my normal branch and my feature branch

$ git merge-base stable feature
0e43ddd8857fdcff0f50258cde194b6dd2ef26ff

2. find how many changes we had

$ git log --pretty=oneline 0e43ddd8857fdcff0f50258cde194b6dd2ef26ff..HEAD | wc -l
57

3. create a new stable_feature_common branch to contain my changes

$ git checkout -b stable_feature_common 0e43ddd8857fdcff0f50258cde194b6dd2ef26ff

4. take note of the current HEAD of my feature branch

 f19869fd6ef69186bb2e8172833b09521e7aacb5

5. create a rebased copy of my feature branch into my common branch.

$ git rebase --onto stable_feature_common stable_feature_common feature

When using git rebase --onto 1 2 3, git rebase all changes between 2 and 3 onto 1. As consequence of this operation, 3 becomes the new 1. In my case, the resulting work is referenced as feature. We will have to rename it.

Note the new HEAD of the feature branch (for the future feature_common branch)

697176ed7370099a9e559b6c82f3ac12caa36d47

6. force the feature branch reference back to where it was

$ git checkout stable_feature
$ git reset --hard f19869f

7. force the feature_common branch reference

$ git branch -f stable_metro_common 697176ed7370099a9e559b6c82f3ac12caa36d47

Now feature_stable_common and feature are identical code wise (but with different commit ids and istory caused by the rebase). I am going to edit feature_stable_common since the branch point commit and remove/edit the things I need only in that branch.

$ git checkout stable_feature_common
$ git branch -i 0e43ddd8857fdcff0f50258cde194b6dd2ef26ff

[lots of editing/commiting]

8. merge feature_stable_common into feature and stable (to easily track future changes)

$ git checkout stable_feature
$ git merge stable_feature_common

$ git checkout stable
$ git merge stable_feature_common

I now have

S1                     S2           (stable)
  \                      /
    C1´-C2´-C3´                (stable_feature_common)
   \                        \
    C1-F1-C2-C3-F2-M    (feature)

C1´, C2´, C3´ are rebased and potentially edited versions of C1, C2 and C3
S2 is my merged common into stable
M is my merged common into feature

This should allow me to track the future commits and merge them back properly.

Some links I've found entertaining:


Wednesday, May 2, 2012

JUC Paris, Unity3d build pipeline talk slides

A few days ago I was in Paris at the Jenkins User Conference where I hold a talk on how we use jenkins to power our unity3d build pipeline. This can serve as entry point to someone who wants to do CI using that platform.

You can find the slides from the talk in this PDF.

If you wish more editing options or more Unity3d material, this zip file contains the original LibreOffice slides and attachments. The original presentation contain a few hidden slides that are a bit Unity3d specific and didn't deserve to be shown to a Jenkins user audience.

As for the tools used or created to support the pipeline, here are the relevant links:
Feel free to edit / reuse / etc. The slides theme and sponsor logos aren't (obviously) mine.

Thursday, March 29, 2012

Unity3d and custom AndroidManifest.xml: post processing when pre-processing doesn't work

In our Unity3d project, we need a custom AndroidManifest.xml. Our first use was to increase the build number. This is required so that our files can be deployed to appaloosa-store. Like testflight the store requires new versions to have an increased version number.

Unity3d: generating files using stringtemplate during the build

In our Unity3d project, we have a need to generate a few files from templates. We use Antlr (3) stringtemplate library to perform the operation. This is how we do it:

Deploying to appaloosa-store from the command line

Those using http://appaloosa-store.com for storing their private applications can now use the following ruby client to deploy applications from the command line. It's heavily inspired by the Jenkins plugin.

Thursday, March 1, 2012

Unity3d: from commit to deployment onto tester devices in 20 min using Jenkins

In our goal to automate most of the development process, at We Want To Know, we've set up a Jenkins build server that continuously generates our builds and distributes them to our testers.

There are quite a few pages on the Web to explain how to make testflight deployment for xcode projects. Yet I haven't found a page that describes step by step instructions to set up a build pipeline for unity3d projects, which bear a few differences and caveats.

So here are some notes that describe the current setup of our CI server, the tradeoffs, and some of the future improvements. We use Jenkins, but you can reuse most of the notes here to set up your own solution.

Wednesday, February 29, 2012

Downloading and installing/upgrading Unity3d on Mac OS X from the command line

If you ever need to install/upgrade Unity3d on a remote mac OS X machine and you only have shell access, here is how you can do it:

Monday, February 20, 2012

Automating the management of iOS provisioning profiles with Jenkins

Manupulating iOS provisioning profiles is a bothersome process. Once you have added a device to a provisioning profile, you need to distribute it to your team, add it to the XCode Organizer, and register it in your XCode project sign configuration. Most of this can be now be completely automated.

Sunday, August 14, 2011

Bislet bad web site

Trying to find information about the Bislet swimming pool. The bisletbad.no site is parked. The bisletbad.com web site is unmaintained (no opening hours, incorrect phone number) but the swimming pool is functionning and isn't that bad !

Looks like the new owners are in some kind of domain dispute with the previous ones...

The whois entry of the .com site contains:

Domain Name: BISLETBAD.COM
Registrar: ENOM, INC.
Whois Server: whois.enom.com
Referral URL: http://www.enom.com
Name Server: NS3.NETBOX.NO
Name Server: NS4.NETBOX.NO
Name Server: NS5.NETBOX.NO
Status: clientTransferProhibited
Updated Date: 23-sep-2010
Creation Date: 23-sep-2010
Expiration Date: 23-sep-2011

Domain name: bisletbad.com

Registrant Contact:
BISLET BAD LIMITED
Dawson E. Hamilton ()

Fax:
Pilestredet 60
995901099
Oslo, Oslo 0167
NO

Administrative Contact:
BISLET BAD LIMITED
Dawson E. Hamilton (pt@jamfit.no)
+47.23333810
Fax:
Pilestredet 60
995901099
Oslo, Oslo 0167
NO

Technical Contact:
BISLET BAD LIMITED
Dawson E. Hamilton (pt@jamfit.no)
+47.23333810
Fax:
Pilestredet 60
995901099
Oslo, Oslo 0167
NO

Status: Locked

Creation date: 23 Sep 2010 16:03:00
Expiration date: 23 Sep 2011 11:03:00


The old Norwegian company points to http://w2.brreg.no/enhet/sok/detalj.jsp?orgnr=995901099 and the company is non existant.

This has been ongoing for months. Why don't they set up a temporary site with basic information (phone number, prices, opening hours) until they resolve their dispute.

Wednesday, April 20, 2011

Scripts pour surveiller une Livebox 2

[For once something in French, as it's targeted to customers of a French ISP]

Mes parents ont des problemes de deconnections sur leur Livebox 2 d'Orange. J'ai ecris un petit script bash pour recuperer les donnees presentes sur les pages de status, generer un fichier de donnees.

Les donnees sont actuellement extraites sous la forme suivante:

20110419-094018;27.5 dB;49.6 dB;8 dB;17 dB;640 kb/s;2304 kb/s;0;0;1850;10091;0;86.201.220.177;00:19:55;mardi 19 avril, 08:22:02;00:18:15;mardi 19 avril, 08:20:04


Le script peut etre utilisee en ligne de commande, ou plus simplement dans un cron.

Une fois les donnees recuperees, on peut par example utiliser gnuplot afin de generer des graphes. Un example de fichier de configuration pour gnuplot est fourni. Dans mon cas il m'a permit de generer:

bandwidth and noise margin over time

Sachant que la livebox tourne probablement sous Linux, il est dommage que la live box n'expose pas ces donnees dans un format facilement lisible par une machine, ainsi que donne access aux logs du systeme. Le kernel etant en GPLv2, il permet cependant la tivoisation...

Dans le cas de mes parents, il semblerait que le probleme soit lie a une marge au bruit fluctuante et trop faible, occasionant les deconnections. La bande passante negociee est parfois relativement faible (e.g. 700 kbps descendant!) et la marge au bruit associee est large ce qui donne une certaine stabilite. Nous allons laisser le script tourner quelques jours afin de voir si certaines periodes sont plus instables que d'autres... Affaire a suivre

Wednesday, March 23, 2011

Trafikanten & open data update - things happened

Following up my post from about 2 months ago, I was announcing good news on the way. As you may already know, the bigs news is finally out there: Trafikanten has opened up their data!

I would like to thank the various people involved in getting this in place, in no particular order, Bent Flyen, Christopher Worren and Jarl Eliassen from Trafikanten, Bjorn Tennøe from Iterate, Marius Mårnes Mathiesen from Shortcut and the numerous people whose name I've forgot along the way.

What's next ?

* I hope that now some people will come up with cool things building upon this free data & API

* yesterday I found farebot for android, and I would love to see the Flexus card data supported by similar applications. I already contacted trafikanten, and will follow up here.

Friday, February 25, 2011

getting svn and git svn to work long paths on windows

It appears that no latest stable SVN client (1.6.15 latest today) work well when working with projects with long paths (i.e. > ~ 250 characters). Noone on the team had problems as they used TortoiseSVN, but I insist on checkout out from the command line, and I also want to use git svn.

Now there's a work-around, but it seems relatively unknown. Yes you read well: use absolute paths instead of relative paths. That's it. Basically do:


$ svn co http://server/project/trunk c:\Projects\project.git


For git svn, from the git batch command line:

$ git svn clone [options] http://server/project/trunk /c/Projects/project.git


It also appears that svn client 1.7, out Q1 2011, will also fix that. But I couldn't confirm it from the roadmap.

Saturday, February 12, 2011

Ignoring JUnit tests at runtime

Sometimes I want to ignore particular test failures. In fact I don't want the tests to run at all because I know they will fail. Not that they are broken, but because they depend on part of the infrastructure that isn't available right now.

E.g. in the web-validators project, I wanted to have the ability to ignore the integration tests (which test remote validation services integration) when the remote sites aren't reachable.

I could also have used Assume, but somewhat I am not completely satisfied with that solution. The Assume calls go in the test code and

  • can't be used at a class level

  • aren't as good from a self-documenting perspective

  • don't have access to method information, e.g. other annotations

  • will execute my test code anyway, which can cause slow-downs, etc (time outs generally take a bit of time to trigger)



I could also have grouped my network dependent tests in a special suite or using a Category, but then I have to enable or disable it myself (i.e. make suites that match my grouping).

I prefer tests that enable themselves automatically.

So I came up with this solution: I have created a new Runner, and a new version of the Ignore annotation called RuntimeIgnore to which I specify optional extra argument(s): the name of a class to instantiate and execute to check whether an ignore condition is satisfied.

The disadvantage of the current patch is that I still have to have my own Runner. I don't like that too much (because to combine multiple enhancements you might have to combine runners) so I submitted an enhancement to JUnit as I merged that approach into the existing JUnit 4 Runner and @Ignore annotation.
Note: the patch isn't fully complete (cleanups, documentation left aside), it's more like a RFC. Feel free to comment.

How could you use this ? Instead of doing something like:


@Category("LinuxIT")
@Test thisTestShouldOnlyRunOnLinux() {
// my test code
}


and then enable your group of tests using Suites.

or

@Test thisTestShouldOnlyRunOnLinux() {
if (notOnLinux()) {
// my test code
}
}


You can do:


@Ignore(ifTrue=NotOnLinux.class)
@Test thisTestShouldOnlyRunOnLinux() {
// my test code
}


The same apply to classes, e.g.


@Ignore(if=NotOnLinux.class)
class MyLinuxTests {
@Test ...
@Test ...
}

You can also take advantage of DRY principles on Convention over Configuration doing


@Ignore(ifTrue=TestNameDoesntMatchPlatform.class)
@Test onLinux_aTest() {
// my test code for Linux
}

@Ignore(ifTrue=TestNameDoesntMatchPlatform.class)
@Test onWindows_aTest() {
// my test code for Windows
}


( in case you haven't followed the above code will not run in code taken from the JUnit official repository ! )

Update 1: after a comment from a JUnit commiter on my pull request/RFC, I made a slightly different version of the patch that doesn't require any change made to JUnit. By using MethodRules intead of a custom Runner, I am achieving almost the same thing. See the change here.

Saturday, February 5, 2011

Sonatype's Hudson's bright future - answer

So Sonatype's Jason Van Zyl posted about Hudson's future. I wonder if that response is related to Hudson-Dev's email thread "The Future of Hudson".

While I agree with some of the things Oracle and Sonatype are doing, I strongly disagree with the wording of this post and some of the comments.


  • "mostly dependent on a single individual for core changes" (aka hudson dependance on KK). There are 115 different contributors in core according to git (for jenkins, but that shouldn't be a big difference in hudson). yes he made the main decisions at start, but there are now more than 350 hosted plugins. From that perspective, his decisions sounded right

  • criticizing jelly ? I agree that it's not the best choice for a project starting today. But it's not that bad. And have you looked at how much code jelly represents in the plugins ? If I look at the official plugins from middle July 2010: 420k lines java, 44k jelly. That's 10%, a huge problem, right? And it's not because jelly didn't make sense for maven that it didn't for hudson. Actually it makes much more sense in hudson than in maven.

  • lack of documentation: most projects lack it. Even Maven

  • "too many forked dependencies". Think of them as patched dependencies, not forks. Who hasn't done that in the enterprise world? You know how hard it is to get a commit upstream sometimes. And it's easy to know why they were forked. Check the release logs for the deps. Or ask !

  • provenance: commits come with issue number and/or link to online discussion / report in the mailing list. Code comes from commits. Do you mean it could have been copied from another project without appropriate copyright ? This can happen anywhere. And it's solved the same way everywhere: by rewriting the code. Nothing dramatic.

  • licensing issues: yes you're right. BTW, mentionning Saas really sounds like attacking Cloudbees...

  • automation tests. Yeah ! All for it. There are 800+ tests right now in test harness. It's not that bad then. And you know how hard it is to come up with a good coverage right ? Maven 2.0.x took a long time to stabilize... As for providing tests for all plugins, it's hard. In maven it's easier because most are not hosted by Apache itself (think codehaus, etc)

  • "maven 3.0 support" in your worklog sounds like hudson/jenkins doesn't support it. How does that run then ? You might want improving it, or providing an alternative. Fine. But don't do as there's no support for maven 3.0 today in hudson/jenkins.

I could go on but I don't want to be too picky.

To summarize. Technical debt ? Oh yeah. Old dependencies? Double that. What kind of old projects doesn't have that ? How many times did you rewrite maven already ? You know it's hard to get it right the first time.

The reason I wanted to answer Sonatype's post was that line.

"Working with the community, Oracle and Sonatype [...]"


I think (and I may be wrong) that you've been working with a subset of the community. You didn't seem to publish your RFEs on the wiki on in Jira when the hudson developers asked (did you?).

You probably worked with Oracle and some of its customers, perhaps getting paid for your company's work. Nothing wrong about that in itself. Even great!

But please don't say 'the community'...

Anyway, the wording aside, I can't wait to see your contributions. I am sure they will help to make Hudson (and indirectly Jenkins) a better product.

Wednesday, February 2, 2011

The Future of Hudson

Unsubscribe @ hudson

Looks like quite a few people think that unssubcribing the developper mailing list is the way to put "the saga behind".

Hudson vs CruiseControl, presentation from 2008

(Note: Because it's historical, I will still keep the hudson name in that post, but that content of course also applies to Jenkins.)

Looks like Google Docs doesn't allow viewing my Open Office presentations. I digged that old Hudson vs CC presentation I made in 2008. You may find some interesting notes in particular:

  • Slide 13: some stats on hudson source

  • Slide 14: some stats on hudson releases where I plotted releases from 1.04 to 1.210 (pretty impressive rate at the time !)

  • Slides 15&16: Hudson success factors

  • Slides 17: Benefits

  • Slides 18: 4 things to do:

    1. Try it

    2. Like it

    3. Use it

    4. Contribute





Enjoy!