Random posts about coding

Mostly blogging about dart.

Gplus Quickstart With Dart

| Comments

Tonights mash-up was taking the gplus-quickstart-dart and wiring it up for server side support. Similar to the gplus-quickstart-java, the client will use the gplus login button to do the OAuth2WebServer flow and send the code over to the server. The server can then verify and make calls on behalf of the client since an ‘offline’ token was requested. This demo just features the server side and what was used to put it together. Yulian Kuncheff has been the primary developer behind fukiya which is an express like framework for dart. The thing I liked most about fukiya was how simple and easy it was to setup URL handlers.

First off, setting up some dependencies.

1
2
3
4
5
6
dependencies:
  google_plus_v1_api: any
  browser: any
  fukiya: '>=0.0.11'
  html5lib: ">=0.4.1 <0.4.2"
  logging: ">=0.4.3+5"

A quick outline of what URLs fukiya handles. Dead simple to setup!

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
  new Fukiya()
  ..get('/', getIndexHandler)
  ..get('/index.html', getIndexHandler)
  ..get('/index', getIndexHandler)
  ..post('/connect', postConnectDataHandler)
  ..get('/people', getPeopleHandler)
  ..post('/disconnect', postDisconnectHandler)
  ..staticFiles('./web')
  ..use(new FukiyaJsonParser())
  ..listen('127.0.0.1', 3333);
}

The index handler is special cause we needed to inject a state token into the page and HTTP session. The state token is then verified on the /connect post. The one-time token helps avoid any Confused_deputy_problems.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void getIndexHandler(FukiyaContext context) {
  // Create a state token. 
  context.request.session["state_token"] = _createStateToken();

  // Readin the index file and add state token into the meta element. 
  var file = new File(INDEX_HTML);
  file.exists().then((bool exists) {
    if (exists) {
      file.readAsString().then((String indexDocument) {
        Document doc = new Document.html(indexDocument);
        Element metaState = new Element.html('<meta name="state_token" content="${context.request.session["state_token"]}">');
        doc.head.children.add(metaState);
        context.response.writeBytes(doc.outerHtml.codeUnits);
        context.response.done.catchError((e) => serverLogger.fine("File Response error: ${e}"));
        context.response.close();
      }, onError: (error) => serverLogger.fine("error = $error"));
    } else {
      context.response.statusCode = 404;
      context.response.close();
    }
  });
}

On the /connect post we will expect a gplus id to be passed to the query parameters and some token data posted. We can then verify the state token and use the token data for accessing the Google APIs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
void postConnectDataHandler(FukiyaContext context) {
  serverLogger.fine("postConnectDataHandler");
  String tokenData = context.request.session.containsKey("access_token") ? context.request.session["access_token"] : null; // TODO: handle missing token
  String stateToken = context.request.session.containsKey("state_token") ? context.request.session["state_token"] : null;
  String queryStateToken = context.request.queryParameters.containsKey("state_token") ? context.request.queryParameters["state_token"] : null;

  // Check if the token already exists for this session. 
  if (tokenData != null) {
    context.send("Current user is already connected.");
    return;
  }

  // Check if any of the needed token values are null or mismatched.
  if (stateToken == null || queryStateToken == null || stateToken != queryStateToken) {
    context.response.statusCode = 401;
    context.send("Invalid state parameter.");
    return;
  }

  // Normally the state would be a one-time use token, however in our
  // simple case, we want a user to be able to connect and disconnect
  // without reloading the page.  Thus, for demonstration, we don't
  // implement this best practice.
  context.request.session.remove("state_token");

  String gPlusId = context.request.queryParameters["gplus_id"];
  StringBuffer sb = new StringBuffer();
  // Read data from request.
  context.request
  .transform(new StringDecoder())
  .listen((data) => sb.write(data), onDone: () {
    serverLogger.fine("context.request.listen.onDone = ${sb.toString()}");
    Map requestData = JSON.parse(sb.toString());

    Map fields = {
              "grant_type": "authorization_code",
              "code": requestData["code"],
              // http://www.riskcompletefailure.com/2013/03/postmessage-oauth-20.html
              "redirect_uri": "postmessage",
              "client_id": CLIENT_ID,
              "client_secret": CLIENT_SECRET
    };

    http.Client _httpClient = new http.Client();
    _httpClient.post(TOKEN_ENDPOINT, fields: fields).then((http.Response response) {
      // At this point we have the token and refresh token.
      var credentials = JSON.parse(response.body);
      _httpClient.close();

      var verifyTokenUrl = '${TOKENINFO_URL}?access_token=${credentials["access_token"]}';
      new http.Client()
      ..get(verifyTokenUrl).then((http.Response response)  {
        serverLogger.fine("response = ${response.body}");

        var verifyResponse = JSON.parse(response.body);
        String userId = verifyResponse.containsKey("user_id") ? verifyResponse["user_id"] : null;
        String accessToken = credentials.containsKey("access_token") ? credentials["access_token"] : null;
        if (userId != null && userId == gPlusId && accessToken != null) {
          context.request.session["access_token"] = accessToken;
          context.send("POST OK");
        } else {
          context.response.statusCode = 401;
          context.send("POST FAILED ${userId} != ${gPlusId}");
        }
      });
    });
  });
}

Now the HTTP session has the full ability to make calls on behalf of the user. The /people method will be called from the client to retrieve the list of visible friends of that user.

1
2
3
4
5
6
7
8
9
10
void getPeopleHandler(FukiyaContext context) {
  String accessToken = context.request.session.containsKey("access_token") ? context.request.session["access_token"] : null;
  SimpleOAuth2 simpleOAuth2 = new SimpleOAuth2()..credentials = new console_auth.Credentials(accessToken);
  plus.Plus plusclient = new plus.Plus(simpleOAuth2);
  plusclient.makeAuthRequests = true;
  plusclient.people.list("me", "visible").then((plus.PeopleFeed people) {
    serverLogger.fine("/people = $people");
    context.send(people.toString());
  });
}

The final responsibility we can bestow upon the server is allowing the client to disconnect by revoking OAuth access.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void postDisconnectHandler(FukiyaContext context) {
  String tokenData = context.request.session.containsKey("access_token") ? context.request.session["access_token"] : null;
  if (tokenData == null) {
    context.response.statusCode = 401;
    context.send("Current user not connected.");
    return;
  }

  final String revokeTokenUrl = "${TOKEN_REVOKE_ENDPOINT}?token=${tokenData}";
  context.request.session.remove("access_token");

  new http.Client()..get(revokeTokenUrl).then((http.Response response) {
    context.request.session["state_token"] = _createStateToken();
    Map data = {
                "state_token": context.request.session["state_token"],
                "message" : "Successfully disconnected."
                };
    context.send(JSON.stringify(data));
  });
}

Thats about it, Happy Dart Hacking! Special thanks to Gerwin Sturm for putting together the original example for client side. Full source code can be found at gplus-quickstart-dart in the server folder. Please replace your own keys cause mine will be removed at some point.

Dart Multi Touch Canvas With Realtime APIs

| Comments

Google has made the realtime api available for developers. Realtime api provides operational transformation on strings, lists, maps and custom objects. The application data gets stored on Google Drive and is available from any supported browser. This is going to be the tooling of the future for collaborative applications.

I took some time to see what it would take for implementing a sample realtime application in dart. Also wanted to make sure my sample could run on mobile chrome.

Since realtime api is new, dart bindings don’t really exist. Lucky for us we have js-interop library. The js-interop library provides communications to existing javascript code from dart. I consider this mostly a quick hack to get started with the realtime api until a more native interface exists.

The sample realtime_touch_canvas demonstrates a multi touch canvas surface that updates in realtime with all clients that have the application open.

Most of the heavy lifting is done by rtclient.dart. I ported the code from the javascript version. Its enough code to get started right away but a more structured solution should be done. The main class is RealTimeLoader used for realtime loading.

1
2
3
4
5
  rtl = new RealTimeLoader(clientId: 'CLIENTID.apps.googleusercontent.com', apiKey: 'KEY');
  rtl.start().then((bool isComplete) {
    /* RealTimeLoader has authenticated the application and is ready to load a file */
    loadRealTimeFile(fileId, model.onFileLoaded, model.initializeModel);
  });

model.onFileLoaded and model.initializeModel handle the creating of model data and loading of model data.

In the realtime_touch_canvas, model data was a simple list of json strings. The ticky part here is you need to remember that your working with the realtime api within the javascript vm. So an array needs to be allocated from js-interop.

1
2
3
4
  void _createNewModel(js.Proxy model) {
    var list = model.createList(js.array(_defaultLines));
    model.getRoot().set(_linesName, list);
  }

After the model is created we then get called to load the file. Loading the file for our purposes is binding the collaborative objects. Some tricky things to note here is we are retaining the javascript objects so we can access them after exit of the callback. Also the callbacks have to be wrapped within js-interop js.Callback.many proxy object. The callbacks _linesOnAddValuesChangedEvent and _linesOnRemovedValuesChangedEvent are fired off when the collaborative list object has items added or removed.

1
2
3
4
5
6
7
8
9
10
11
12
  js.Proxy _doc;
  String _linesName = "lines";
  js.Proxy _lines;

  void _bindModel(js.Proxy doc) {
    _doc = doc;
    js.retain(_doc);
    _lines = doc.getModel().getRoot().get(_linesName);
    _lines.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, new js.Callback.many(_linesOnAddValuesChangedEvent));
    _lines.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, new js.Callback.many(_linesOnRemovedValuesChangedEvent));
    js.retain(_lines);
  }

When the callback is called the data would be in the javascript virtual machine so we can parse it and store in our native dart code. This is more of a convenience then a must do, that way we can expose plan old dart objects to our other parts of the dart application.

1
2
3
4
5
  void _linesOnAddValuesChangedEvent(addedValue) {
    var insertedLine = _lines.get(addedValue.index);
    var line = new Line.fromJson(insertedLine);
    realtimeTouchCanvas.move(line, line.moveX, line.moveY);
  }

Now when we want to store a line in the application we simply convert it to json and push it into the collaborative list. The little tick here is to make sure we are scoped when accessing the _lines object since it lives in the javascript virtual machine.

1
2
3
4
5
  void addLine(Line line) {
    js.scoped(() {
      _lines.push(line.toJson());
    });
  }

The realtime_touch_canvas is live on github gh-pages and realtime_touch_canvas source is available.

Rikulo Stream on Heroku

| Comments

Tonights hacking was with stream and heroku. Stream is a Dart web server supporting request routing, filtering, template technology, file-based static resources and MVC design pattern. I just planned on serving static content from heroku using full dart based web server.

First setup the dart build pack

shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
adam@Adams-MacBook-Air:~/dart
$ mkdir stream_todomvc

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ cd stream_todomvc

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ heroku create stream-todomvc

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ heroku config:add BUILDPACK_URL=https://github.com/igrigorik/heroku-buildpack-dart.git

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ git init

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ git remote add heroku git@heroku.com:stream-todomvc.git

Creating a new project called stream-todomvc. Going to use the todomvc from the web-ui project as our content for the stream server. First thing that should be done is adding the dependencies to the pubspec.yaml file.

pubspec.yaml
1
2
3
4
5
6
7
name: stream_todomvc
description: A sample WebUI application
dependencies:
  browser: any
  js: any
  web_ui: 0.4.1+7
  stream: 0.5.5+1

Next I simply compied the existing todomvc project out into my stream-todomvc project.

shell
1
2
adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ cp ~/dart/web-ui/example/todomvc/* ./web/

stream intro documentation goes over some basic configurations and settings. I’m just going to use them for now to get something running right away. The key to note when serving code from the web/ folder in dart projects is having the stream server code in web/webapp/. That way stream can find all your resources with little configuration. With very little dart code we can have static web server going.

web/webapp/server.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
library server;

import 'dart:io';
import "package:stream/stream.dart";

void main() {
  var port = Platform.environment.containsKey('PORT') ? int.parse(Platform.environment['PORT']) : 8080;
  var host = '0.0.0.0';
  var streamServer = new StreamServer();
  streamServer
  ..port = port
  ..host = host
  ..start();
}

Since this was a web-ui project we need to have a build.dart file help us with transforming the polyfill web components.

build.dart
1
2
3
4
import 'dart:io';
import 'package:web_ui/component_build.dart';

main() => build(new Options().arguments, ['web/index.html']);

The heroku environment requires a procfile configuration to let the service know the type of commands to run.

Procfile
1
web: ./dart-sdk/bin/dart --package-root=./packages/ web/webapp/server.dart

Next we build all the static data for our webapp to function. This will include calling build.dart and dart2js. The second step of calling dart2js helps with clients that do not have the dartvm built in.

shell
1
2
3
4
5
6
7
8
9
10
11
12
13
adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ pub install
Resolving dependencies...
Dependencies installed!

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ dart build.dart
Total time spent on web/index.html                           -- 839 ms
Total time                                                   -- 863 ms

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ dart2js -oweb/out/index.html_bootstrap.dart.js web/out/index.html_bootstrap.dart
Using snapshot /Users/adam/Documents/DartEditor/dart/dart-sdk/lib/_internal/compiler/implementation/dart2js.dart.snapshot

Now everything should be ready for deployment.

shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ git add -a -m "ready for deploy"

adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ git push -v --set-upstream heroku master:master
Pushing to git@heroku.com:stream-todomvc.git
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 283 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)

-----> Fetching custom git buildpack... done
-----> Dart app detected
-----> Installing Dart VM, build: latest
-----> Copy Dart binaries to app root
-----> Install packages
*** Found pubspec.yaml in .
Resolving dependencies...
Dependencies installed!
Fixed web symlink
-----> Discovering process types
       Procfile declares types -> web

-----> Compiled slug size: 8.9MB
-----> Launching... done, v7
       http://stream-todomvc.herokuapp.com deployed to Heroku

To git@heroku.com:stream-todomvc.git
   042f1f4..b35984b  master -> master
updating local tracking ref 'refs/remotes/heroku/master'

Deploying to heroku in this style is just a good starting point. web-ui and dart in general is still working on a deployment story. The URL for the stream-todomvc will contain out in its location, not very desirable. In the future a buildtool will aid the deployment story for dart.

Check out the live version of stream-todomvc with full source code available at the stream-todomvc github project.

Dart Google Client Apis Now Available on Pub

| Comments

The dart-gde team now brings you not only a generator to create google client apis but also pub.dartlang.org hosted packages. Lot of thanks goes to Gerwin Sturm for all of his hard work over the last few weeks developing discovery_api_dart_client_generator and dart-google-oauth2-library.

We plan to keep the client libraries up to date with uploader.dart script. Still somewhat a manual process, future automation could happen when/if we have the ability to get notified about google api changes. For now we will push updates when appropriate. This will ensure that we can push the latest versions of the apis to pub and still have previous revisions available. Some of the more intricate parts of this script include auto version incrementing pubspec files and syncing to github, then pushing to pub.

Would you want to contribute to this project? Please feel free to ping us Adam Singer/Gerwin Sturm on g+, we’re definitely looking to refactor some parts and test others. Our main focus for this release was to get something out the door that is pleasantly usable.

Many hours of testing and development was done to have a simple and easy way to use the google client apis in dart! We hope you enjoy and look forward to seeing what you build. To get started with client api examples check out dart_api_client_examples. The github hosted source code can be found at dart-google-apis

Full list of available Google client apis on pub.dartlang.org

Using createDartAnalyzerTask in bot.dart to Ensure Sanity

| Comments

drone.io has really proven to be useful for dart projects. One common pattern I find my self doing with projects these days is running the dart_analyzer before running unit tests. Had a discussion with Kevin Moore about having a dart_analyzer task included in hop, part of the bot.dart framework. We both agreed it would be nice to automate that task and not have shell scripts running the show. Thus createDartAnalyzerTask was born. createDartAnalyzerTask allows you to add dart scripts that are libraries or main entry points to be analyzed by dart_analyzer, this allows for a first step of safety, so that code you have passes the static checker. It does not mean your code is perfect but can help you find warnings and errors. A great combination for this is pairing it up with drone.io, that way when a new sdk comes out drone can let you know automatically if it passes static checker.

Lets see this in action

First step is to add bot to your pubspec.yaml

1
2
3
4
dependencies:
  browser: ">=0.3.4"
  unittest: ">=0.3.4"
  bot: ">=0.12.0"

Create a minimal tool/hop_runner.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
library hop_runner;

import 'dart:async';
import 'dart:io';
import 'package:bot/bot.dart';
import 'package:bot/hop.dart';
import 'package:bot/hop_tasks.dart';

void main() {
  //
  // Analyzer
  //
  addTask('analyze_lib', createDartAnalyzerTask(['lib/stats.dart']));

  //
  // Hop away!
  //
  runHopCore();
}

Add a bin/hop script to your project, bin/hop script is not required but does help manage some flags you might be interested in having.

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
PACK_DIR=`pwd`/packages/
cmd="dart
--enable_asserts
--enable_checked_mode
--enable_type_checks
--error_on_malformed_type
--package-root=$PACK_DIR
--verbose_debug
--warning_as_error
./tool/hop_runner.dart $@"
exec $cmd

Now hop away!

1
2
3
4
5
19:41:54-adam@Adams-MacBook-Air:~/dart/stats.dart
$ hop analyze_lib
analyze_lib: PASSED - lib/stats.dart
analyze_lib: PASSED: 1, WARNING: 0, ERROR: 0
Finished

Now lets say we have the following warning of String i = 42;, bot can provide us a summary of warnings we might have in our code.

1
2
3
4
5
library not_cool_lib;

void main() {
  String i = 42;
}

Adding the library to our task

1
2
3
4
  //
  // Analyzer
  //
  addTask('analyze_lib', createDartAnalyzerTask(['lib/stats.dart', 'lib/notcoollib.dart']));

Executing hop

1
2
3
4
5
6
20:04:55-adam@Adams-MacBook-Air:~/dart/stats.dart
$ hop analyze_lib
analyze_lib: PASSED - lib/stats.dart
analyze_lib: WARNING - lib/notcoollib.dart
analyze_lib: PASSED: 1, WARNING: 1, ERROR: 0
Finished

Now we have a warning, but since dart is a dynamic langauge we should not treat that as an error (at some point we might provide the option of making warnings fail drone.io).

If we introduce a real error, the task runner should yell at us.

1
2
3
4
5
6
library not_cool_lib;

void main() {
  String i = 42;
  bam(i);
}

And it does!

1
2
3
4
5
6
7
8
9
10
20:11:38-adam@Adams-MacBook-Air:~/dart/stats.dart
$ hop analyze_lib
analyze_lib: PASSED - lib/stats.dart
analyze_lib: ERROR - lib/notcoollib.dart
analyze_lib: PASSED: 1, WARNING: 0, ERROR: 1
analyze_lib: Failed
Failed
20:18:11-adam@Adams-MacBook-Air:~/dart/stats.dart
$ echo $?
80

The exit code will let drone.io know that we did not exit cleanly. I love drone.io for this reason, it’s simple to get setup right away with little fuss.

A complete run on drone.io might look something as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ git clone git://github.com/Dartist/stats.dart.git /home/ubuntu/src/github.com/Dartist/stats.dart 
Cloning into '/home/ubuntu/src/github.com/Dartist/stats.dart'...
$ dart --version
Dart VM version: 0.3.4.0_r18115 (Tue Feb  5 05:53:42 2013)
$ cat $DART_SDK/revision
18137
$ sudo start xvfb
xvfb start/running, process 1017
$ pub install
Resolving dependencies...
Downloading browser 0.3.4...
Downloading bot 0.12.1...
Downloading unittest 0.3.4...
Downloading logging 0.3.4...
Downloading args 0.3.4...
Downloading meta 0.3.4...
Dependencies installed!
$ export PATH=./bin:$PATH
$ hop analyze_lib
analyze_lib: PASSED - lib/stats.dart
analyze_lib: PASSED: 1, WARNING: 0, ERROR: 0
Finished
$ hop headless_test
unittest-suite-wait-for-done
headless_test: DumpRenderTree - test/tests_browser.html
headless_test: 1 PASSED, 0 FAILED, 0 ERRORS
Finished

For a full example of a project that uses bot.dart for testing and analyzer please refer to stats.dart

Using Dart for Website Publishing on Google Drive

| Comments

After watching a Generating a Google Drive Hosted Website with tools you have lying around in your kitchen by Ali Afshar I got interested in doing the same thing with dart.

Had to ask my self a few questions first?

At the time I started this investigation the discovery_api_dart_client_generator did not have OAuth2 support for installed applications. Knowing that pub does use OAuth2 in a similar fashion, I decided to peek inside of pub to see what it is doing.

After reviewing the pub source code, it’s interested to see how they handle getting tokens from the Google OAuth2 Authorization Server back to the client application. pub first checks if the client applications has a ~/.pub-cache/credentials.json file stored, if the credentials.json is not found or invalid pub will generate an authorization url. The authorization url is copied and pasted by the user into a web browser and asks the user if they will allow access to some scopes. When the user accepts access the redirect url with token the Google OAuth2 Authorization Server redirects to a listening http server on localhost. This server was started by the pub application, now pub has the token and stores it for later use.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
12:22:46-adam@Adams-MacBook-Air:~/dart/stats.dart
$ pub publish
Publishing "stats" 0.0.4:
|-- .gitignore
|-- AUTHORS
|-- LICENSE
|-- README.md
|-- asset
|   |-- stats_dart_fps.png
|   '-- stats_dart_ms.png
|-- example
|   |-- basic
|   |   |-- statsdart.dart
|   |   '-- statsdart.html
|   '-- theming
|       |-- theming.dart
|       '-- theming.html
|-- lib
|   |-- src
|   |   '-- stats.dart
|   '-- stats.dart
|-- pubspec.yaml
'-- test
    |-- run.sh
    |-- tests_browser.dart
    '-- tests_browser.html

Looks great! Are you ready to upload your package (y/n)? y
Pub needs your authorization to upload packages on your behalf.
In a web browser, go to https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A62462&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".

Waiting for your authorization...
Authorization received, processing...
Successfully authorized.

Package "stats" already has version "0.0.4".

allow_access

success.png

After some initial slicing and dicing I was able to take what was needed from pub and merge it into dart-google-oauth2-library that discovery_api_dart_client_generator depends on for OAuth2 support. pub was structured such that all files are dart libraries, so ripping out parts was easy, I found the most issues with http package. The client requests ContentType could only be set as application/x-www-form-urlencoded when making requests, this did not seem to play nicely with Google APIs. Took many hours of debugging to figure out why POST method would fail but GET method would not. Stumbled on some Google mailing lists that mentioned some services do work with ContentType application/x-www-form-urlencoded and some only with application/json. So I created a separate client that does the POST, PATCH, PUT, DELETE methods required for the Google Client APIs.

Big thanks to Gerwin Sturm for working on this merge with me, in a very short amount of time we’ve been able to push the dart-google-oauth2-library and discovery_api_dart_client_generator into almost complete solutions for dart and Google Client API services.

The markdown.dart processor was for the most part a total freebie, Bob Nystrom had already created a markdown processor which eventually got rolled into Dartdoc. All that was needed here is picking out the code from the dart repo, updating libraries and import, minor code clean up and hosting it on github.

One of my all time favorite Google APIs is the url-shortener API. Even without OAuth2 you can use this service, now that we have patched up dart-google-oauth2-library and discovery_api_dart_client_generator shortening the long webViewLink will be easy. A site note, goo.gl links work great as for landing slides when giving a presentation, easy and short enough for audience to type into mobile phone, tablet or laptop to pull down your slides and follow along.

Now we have all the parts needed to create a simple application that reads in some markdown, processes it to html, creates a website folder on drive, uploads the final html, then generates a shortened url.

We are still working on the best way to publish the Dart Google Client APIs, so please don’t depend on the links below for too long, they will be out dated soon. This was just for testing.

Starting off with our pubspec.yaml file we add the dependencies needed for the application.

1
2
3
4
5
6
7
8
9
name:  drive_publish_markdown
description:  Publishing markdown content to a public drive site
dependencies:
  urlshortener_v1_api:
    git: https://github.com/financeCoding/urlshortener_v1_api_client.git
  drive_v2_api:
    git: https://github.com/financeCoding/drive_v2_api_client.git
  markdown:
    git: https://github.com/financeCoding/markdown.dart.git

Getting a identifier and secret is simple, just goto your Google APIs Console and pull out Client ID and Client secret for a Client ID for installed applications that was previously created. If you don’t have one already they are easy to create.

  • Goto API access in a project.
    api_access

  • Click Create another client ID. create_another_client_id

  • Choose Installed application and Installed type Other. Your Done! create_client_id

Apply identifier and secret to your code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import "dart:io";
import "dart:async";
import 'dart:crypto';
import "dart:json" as JSON;
import "package:google_oauth2_client/google_oauth2_console.dart";
import "package:drive_v2_api/drive_v2_api_console.dart" as drivelib;
import "package:urlshortener_v1_api/urlshortener_v1_api_console.dart" as urllib;
import "package:markdown/lib.dart" as markdown;

String identifier = "<IDENTIFIER>";
String secret = "<SECRET>";
List scopes = [drivelib.Drive.DRIVE_FILE_SCOPE, drivelib.Drive.DRIVE_SCOPE, urllib.Urlshortener.URLSHORTENER_SCOPE];
drivelib.Drive drive;
urllib.Urlshortener urlshort;
OAuth2Console auth;

Now with identifier and secret the setup for a dart application to use Drive and Urlshortener is easy, create a new OAuth2Console and pass them along to the constructor of Drive and Urlshortener. Note that the flag makeAuthRequests has been set on both objects, that allows us to make authroized calls on behalf of the user. You could create the Google Client API objects without a OAuth2 token and leave the makeAuthRequests as false, the objects would then only be allowed to access non-authenticated resources on the Google servers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
  /**
   * Create new or load existing oauth2 token.
   */
  auth = new OAuth2Console(identifier: identifier, secret: secret, scopes: scopes);

  /**
   * Create a new [drivelib.Drive] object with authenticated requests.
   */
  drive = new drivelib.Drive(auth);
  drive.makeAuthRequests = true;

  /**
   * Create a new [urllib.Urlshortener] object with authenticated requests.
   */
  urlshort = new urllib.Urlshortener(auth);
  urlshort.makeAuthRequests = true;

  /**
   * Create a new 'public_folder' and insert markdown as html
   */
  createPublicFolder("public_folder").then(processMarkdown);
}

I have to admit, updating permissions took me awhile to figure out. I first tried to update by calling drive.files.update with a modified drivelib.File. That was not the proper way to change permissions on drive. The correct way is to call the drive.permissions.* methods, lesson learned after some Googling and searching on stackoverflow drive-sdk. Setting the Permissions and mimeType are the most important parts to note here, thats what change the folder into a public website hosted on drive viewable to anyone.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void insertPermissions(drivelib.File file, Completer completer) {
  /**
   * Create new [drivelib.Permission] for insertion to the
   * drive permissions list. This will mark the folder publicly
   * readable by anyone.
   */
  var permissions = new drivelib.Permission.fromJson({
    "value": "",
    "type": "anyone",
    "role": "reader"
  });

  drive.permissions.insert(permissions, file.id).then((drivelib.Permission permission) => completer.complete(file));
}

Future<drivelib.File> createPublicFolder(String folderName) {
  var completer = new Completer();

  /**
   * Create the [drivelib.File] with a web folder app mime type.
   */
  drivelib.File file = new drivelib.File.fromJson({
    'title': folderName,
    'mimeType': "application/vnd.google-apps.folder"
  });

  /**
   * Insert the [drivelib.File] to google drive.
   */
  drive.files.insert(file).then((drivelib.File newfile) => insertPermissions(newfile, completer));

  return completer.future;
}

Now we get to the meat and potatoes of our application. At this point we have a public web folder that can host our content. We do the follow steps

  • Read in the markdown and html template file
  • Replace a tag in the template with the markdown
  • Insert the file into drive
  • Add a parent reference to the file
  • Get the url to the folder
  • Shorten the url
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
processMarkdown(drivelib.File folder) {
  /**
   * Read in both markdown and html template
   */
  var markdownFile = new File('markdown.md');
  var templateFile = new File('template.html');
  var markdownStr = markdownFile.readAsStringSync();
  var templateStr = templateFile.readAsStringSync();

  /**
   * Convert markdown to html.
   */
  var page = markdown.markdownToHtml(markdownStr);

  /**
   * Replace $page with converted markdown.
   */
  templateStr = templateStr.replaceFirst('\$page', page);

  /**
   * Create a new [drivelib.File] to hold the html content.
   */
  drivelib.File file = new drivelib.File.fromJson({
    'title': 'index.html',
    'mimeType': "text/html"
  });

  /**
   * Create a new [drivelib.ParentReference] to link the html file to.
   */
  drivelib.ParentReference newParent = new drivelib.ParentReference.fromJson({'id': folder.id});

  /**
   * Encode the content to Base64 for inserting into drive.
   */
  var content = CryptoUtils.bytesToBase64(templateStr.charCodes);

  /**
   * 1) Insert the new file with title index.html and type text/html
   * 2) Insert the new parent of the file (i.e. place the file in the folder)
   * 3) Get the folders web view link
   * 4) Shorten the web view link with UrlShortener
   */
  drive.files.insert(file, content: content).then((drivelib.File insertedFile) {
    drive.parents.insert(newParent, insertedFile.id).then((drivelib.ParentReference parentReference) {
      drive.files.get(folder.id).then((folder) {
        print("Web View Link: ${folder.webViewLink}");
        var url = new urllib.Url.fromJson({'longUrl': folder.webViewLink});
        urlshort.url.insert(url).then((url) {
          print("Short Url ${url.id}");
        });
      });
    });
  });
}

The code for this sample can be found on github drive_publish_markdown. Executing the sample follows the flow explained above

  • Ask user to generate token
  • Get token from redirect
  • Store token
  • Make drive and url-shortener requests.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
13:32:40-adam@Adams-MacBook-Air:~/dart/drive_publish_markdown/bin
$ dart drive_publish_markdown.dart
Client needs your authorization for scopes [https://www.googleapis.com/auth/drive.file, https://www.googleapis.com/auth/drive, https://www.googleapis.com/auth/urlshortener]
In a web browser, go to https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=299615367852-n0kfup30mfj5emlclfgud9g76itapvk9.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A62900&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Furlshortener
Then click "Allow access".

Waiting for your authorization...
Authorization received, processing...
Calling curl with --insecure, ca-certificates.crt not found.
Successfully authorized.

Calling curl with --insecure, ca-certificates.crt not found.
Web View Link: https://www.googledrive.com/host/0B29MR2FlgtejWnh6SS03LWFnVE0/
Short Url http://goo.gl/3fGi3

allow_access_drive_publish_markdown

generated_markdown_published_on_drive

Thats all, have fun with this really simple and easy to get started sample.

Darting a Full Stack

| Comments

tl;dr A common question that comes up is connecting all parts of dart together. Here I will show a simple example of connecting up web-ui and objectory.

web-ui is a WebComponents framework provided by the dart-lang team. objectory is a data persistence layer for MongoDB that provides typed, checked environment for models, saving and queries. With these two components and a very simple web server we could have a nice full stack solution in dart.

Initially Vadim Tsushko did some work to wire up TodoMVC from the web-ui examples with indexdb. Using the same code and updated version of TodoMVC I was able to connect it to mongodb using the web socket connection provided with objectory.

It would of been nice if this solution could possibly be deployed onto heroku, ended up not possible since heroku does not support native web socket connections with their internal routing. socket.io does work on heroku but uses long polling instead of true web sockets.

Another note, since dart is in a transitional phase right now for implementing streams, some of this code depends packages that have not yet been pushed to pub.dartlang.org

So lets get started with the basics, getting up and running with objectory. Install mongo if its not already and fire up a mongo database.

mongo install
1
2
3
4
brew install mongo
cd /tmp/
mkdir db
mongod -dbpath .

The version of dart being used is the latest trunk build 17072

1
2
$ cat ~/dart/dart-sdk/version
0.3.0.1_r17072

clone and test objectory as a sanity check

objectory
1
2
3
4
5
6
7
$ cd /Users/adam/dart/
$ git clone https://github.com/vadimtsushko/objectory.git
$ cd objectory
$ pub install
$ cd bin
$ dart objectory_server.dart
listing on http://127.0.0.1:8080

Open up another terminal and run the example/blog_console.dart application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ cd ~/dart/objectory/example
$ dart blog_console.dart
===================================================================================
>> Adding Authors
===================================================================================
>> Authors ordered by age ascending
[Jorge Luis Borges]:[jorge@borges.com]:[123]
[William Shakespeare]:[william@shakespeare.com]:[587]
===================================================================================
>> Adding Users
===================================================================================
>> >> Users ordered by login ascending
[jdoe]:[John Doe]:[john@doe.com]
[lsmith]:[Lucy Smith]:[lucy@smith.com]
===================================================================================
>> Adding articles
===================================================================================
>> Printing articles
Jorge Luis Borges:Caminando por Buenos Aires:Las callecitas de Buenos Aires tienen ese no se que...
     2013-01-07 16:21:26.437:Lucy Smith: Well, you may do better...
     2013-01-15 16:01:26.445:John Doe: I love this article!
William Shakespeare:I must have seen thy face before:Thine eyes call me in a new way
     2013-01-16 11:28:06.453:John Doe: great article!

Now if we want to see the blog stored in mongo we need to remove the line in blog_console.dart that drops the database collection and rerun.

blog_console.dart.diff
1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/example/blog_console.dart b/example/blog_console.dart
index 6775427..fd1d918 100644
--- a/example/blog_console.dart
+++ b/example/blog_console.dart
@@ -84,7 +84,7 @@ main(){
     print(">> Printing articles");
     return Future.wait(articles.mappedBy((article) => printArticle(article)));
   }).then((_) {
-    return objectory.dropCollections();
+    return objectory;
   }).then((_) {
    objectory.close();
   });

Opening up mongo and peeking in side we see the following entries have been stored.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ mongo
MongoDB shell version: 2.2.2
connecting to: test
> show dbs
local (empty)
objectory_blog    0.203125GB
> use objectory_blog
switched to db objectory_blog
> show collections
Article
Author
User
system.indexes
> db.Article.find()
{ "_id" : ObjectId("50f751f09925f54002000008"), "title" : "Caminando por Buenos Aires", "body" : "Las callecitas de Buenos Aires tienen ese no se que...", "author" : DBPointer("Author", ObjectId("50f751f09925f54002000001")), "comments" : [  {"date" : ISODate("2013-01-08T00:24:21.201Z"),     "body" : "Well, you may do better...",  "user" : DBPointer("User", ObjectId("50f751f09925f54002000005")) },   {    "date" : ISODate("2013-01-16T00:04:21.206Z"),     "body" : "I love this article!",    "user" : DBPointer("User", ObjectId("50f751f09925f54002000004")) } ] }
{ "_id" : ObjectId("50f751f09925f54002000009"), "title" : "I must have seen thy face before", "body" : "Thine eyes call me in a new way", "author" : DBPointer("Author", ObjectId("50f751f09925f54002000000")), "comments" : [   {    "date" : ISODate("2013-01-16T19:31:01.212Z"),     "body" : "great article!",  "user" : DBPointer("User", ObjectId("50f751f09925f54002000004")) } ] }
>

Now that we have done a sanity check we can start to move forward with TodoMVC side of things.

I’ve provided a web-ui branch, the branch fixes up some of the pubspec inconsistencies between the projects on pub.dartlang.org. In the future this would not be needed.

1
2
3
$ git clone -b objectory_example git://github.com/financeCoding/web-ui.git
$ cd ~/dart/web-ui
$ pub install

objectory provides a very nice browser based web socket connector to the objectory server named ObjectoryWebsocketBrowserImpl. This allows us to register our models. The TodoMVC application model has a Todo object we’d like to persist.

model.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Todo extends PersistentObject {

  String get task => getProperty('task');
  set task(String value) => setProperty('task',value);

  bool get done => getProperty('done');
  set done(bool value) => setProperty('done',value);

  Todo(String newTask) {
    done = false;
    task = newTask;
    saveOnUpdate = true;
  }

  String toString() => "$task ${done ? '(done)' : '(not done)'}";
}

By extending PersistentObject and adding the getProperty and setProperty methods to our getters and setters we have easily transformed this object to be persisted by objectory.

To bind this up to objectory we new up a ObjectoryWebsocketBrowserImpl, when the application model is created, register the Todo class.

model.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'package:objectory/objectory_browser.dart';
ObjectoryQueryBuilder get $Todo => new ObjectoryQueryBuilder('Todo');
const DefaultUri = '127.0.0.1:8080';
AppModel _app;
AppModel get app {
  if (_app == null) {
    _app = new AppModel();
     objectory = new ObjectoryWebsocketBrowserImpl(DefaultUri, () =>
         objectory.registerClass('Todo',()=>new Todo('')), false); // set to true to drop models
     objectory.initDomainModel().then((_) {
       objectory.find($Todo).then((todos) {
         app.resetTodos(todos);
       });
     });
  }
  return _app;
}

The final step is when a todo is added we also save it in objectory by calling objectory.save().

main.dart
1
2
3
4
5
6
7
8
9
10
11
import 'package:objectory/objectory.dart';
[...]
void addTodo(Event e) {
  e.preventDefault(); // don't submit the form
  var input = query('#new-todo');
  if (input.value == '') return;
  var todo = new Todo(input.value);
  app.todos.add(todo);
  objectory.save(todo);
  input.value = '';
}

In the custom branch provided, I renamed the standard build.dart to x_build.dart in hope to save my poor laptop from needlessly building. On a faster system this is not needed. Now we can launch the x_build.dart to generate the output build from the web components sample.

1
2
$ dart x_build.dart
Total time spent on example/todomvc/main.html                -- 276 ms

From the dart editor we can now launch the output folder main.html.

launch-dartium

todo_entered

This is a nice start, we can launch a web-ui application from dartium and connect up mongo. The issues now… how do we get the kind of application live and not launched on localhost?

  • Create a http server
  • Add objectory to it
  • Compile todomvc application to javascript

Why are the steps above needed? First the objectory ObjectoryServerImpl used in objectory_server.dart hides HttpServer which doesn’t really work for us if we need to serve up static content. Good thing that the class is relativity simple to modify. Compiling todomvc to javascript is an additional step also, but helps us greatly the server logic, only a few files will need to be matched. When a complete and updated web server framework comes to town for dart this would also not be needed.

Modifying the DefaultUri for ObjectoryWebsocketBrowserImpl so it looks at the host’s location will help for deploying it on a server. The web socket location can then be resolved dynamically.

model.dart
1
2
3
import 'dart:html';
[...]
var DefaultUri = window.location.host;

Compile the web component and update the base.css and dart.js.

main.html
1
2
3
4
[...]
  <link rel="stylesheet" href="base.css">
[...]
  <script type="text/javascript" src="dart.js"></script>

Then compile down to javascript after modifying the html.

1
2
$ cd ~/dart/web-ui/example/todomvc/out
$ dart2js -omain.html_bootstrap.dart.js main.html_bootstrap.dart
1
2
cd ~/dart/web-ui/example/todomvc/out
$ cp ../base.css main.html main.html_bootstrap.dart.js dart.js ~/dart/dart-full-stack-example/

*The dart_full_stack_example can be found on github, it contains the server code *

1
2
$ cd ~/dart/
$ git clone https://github.com/financeCoding/dart-full-stack-example.git

Moving onto dart_full_stack_example, this project will contain the compiled js and html code along with a server. The server will handle delivering the static content also provide the interfaces for the web socket connection.

server.dart
1
2
3
final IP = '0.0.0.0';
final PORT = '8080';
final URI = 'mongodb://127.0.0.1/objectory_server_test';

The server only handles a few requests as shown. Setting IP to 0.0.0.0 will listen on all IP addresses.

server.dart
1
2
3
4
5
6
7
8
  server = new HttpServer();
  WebSocketHandler wsHandler = new WebSocketHandler();
  server.addRequestHandler((req) => req.path == '/ws', wsHandler.onRequest);
  server.defaultRequestHandler = _loadIndex;
  server.addRequestHandler((req) => req.path == '/main.html', _loadFile);
  server.addRequestHandler((req) => req.path == '/main.html_bootstrap.dart.js', _loadFile);
  server.addRequestHandler((req) => req.path == '/base.css', _loadFile);
  server.addRequestHandler((req) => req.path == '/dart.js', _loadFile);

Looking at the server.dart source code will give you a fuller example, all that is needed now is to run dart server.dart and you’ll have server running.

This is a more complicated process then it has to be, at some point it will get easier when a true deployment and configuration story happen.

Happy Dart Hacking!!!

Dart Generating and Executing Snapshots

| Comments

Todays random walk of dartness has lead me to generating and executing snapshots.

What is a snapshot in terms of Dart? Serialized binary heaps. It has been said that snapshots can help apps startup 10X faster. dart2js is a working example of this in action, when you execute the dart2js compiler it is actually running from a snapshot file.

How can I currently generate them? (Might not be this way in the future) As of now you need to build from source so the gen_snapshot binary is built. gen_snapshot is the tool built from gen_snapshot.cc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~/dart_bleeding/dart/xcodebuild/DebugIA32/test
$ cd dart

~/dart_bleeding/dart/xcodebuild/DebugIA32/test
$ git svn fetch

~/dart_bleeding/dart/xcodebuild/DebugIA32/test
$ git merge git-svn

~/dart_bleeding/dart/xcodebuild/DebugIA32/test
$ gclient sync

~/dart_bleeding/dart/xcodebuild/DebugIA32/test
$ gclient runhooks

~/dart_bleeding/dart/xcodebuild/DebugIA32/test
$ ./tools/build.py -m all --verbose -a ia32 --os=host -j 4

After that song and dance is finished the release build directory should contain the gen_snapshot executable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/dart_bleeding
$ cd dart/xcodebuild/ReleaseIA32/

~/dart_bleeding/dart/xcodebuild/ReleaseIA32
$ ls
analyzer                  libcrnspr.a               libdart_vm.a              libv8_base.a
api_docs                  libcrnss.a                libdart_withcore.a        libv8_nosnapshot.a
d8                        libcrnssckbi.a            libdouble_conversion.a    libv8_snapshot.a
dart                      libdart.a                 libjscre.a                mksnapshot
dart-sdk                  libdart_builtin.a         libnss_static_dart.a      packages
dart2js.snapshot          libdart_dependency_helper libsample_extension.dylib process_test
dart_no_snapshot          libdart_io.a              libsqlite3.a              run_vm_tests
gen_snapshot              libdart_lib.a             libssl_dart.a
libchrome_zlib.a          libdart_lib_withcore.a    libtest_extension.dylib

Running gen_snapshot --help we find the flags needed to generate a snapshot script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
~/dart_bleeding/dart/xcodebuild/ReleaseIA32
$ ./gen_snapshot --help
No snapshot output file specified.

Usage:

  gen_snapshot [<vm-flags>] [<options>] \
               {--script_snapshot=<out-file> | --snapshot=<out-file>} \
               [<dart-script-file>]

  Writes a snapshot of <dart-script-file> to <out-file>. If no
  <dart-script-file> is passed, a generic snapshot of all the corelibs is
  created. One of the following is required:

    --script_snapshot=<file>   Generates a script snapshot.
    --snapshot=<file>          Generates a complete snapshot. Uses the url
                               mapping specified on the command line to load
                               the libraries.
Supported options:

--package_root=<path>
  Where to find packages, that is, "package:..." imports.

--url_mapping=<mapping>
  Uses the URL mapping(s) specified on the command line to load the
  libraries. For use only with --snapshot=.

The dart vm provides a flag that allows the vm to load the dart script from a snapshot.

1
2
--use_script_snapshot=<file_name>
  executes Dart script present in the specified snapshot file

Combining all this together and using the benchmark_harness we can test out creating and running a dart application from a snapshot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
git clone https://github.com/financeCoding/benchmark_harness.git

~/dart
$ cd benchmark_harness/

~/dart/benchmark_harness
$ pub install
Resolving dependencies...
Dependencies installed!

~/dart/benchmark_harness
$ cd example/

~/dart/benchmark_harness/example
$ ls
DeltaBlue.dart     Richards.dart      Template.dart      packages

~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/dart DeltaBlue.dart
DeltaBlue(RunTime): 4326.133909287257 us.

~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/dart Richards.dart
Richards(RunTime): 2135.538954108858 us.

~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/gen_snapshot --script_snapshot=DeltaBlue.snapshot DeltaBlue.dart

~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/dart --use_script_snapshot=./DeltaBlue.snapshot DeltaBlue.dart
DeltaBlue(RunTime): 4268.6567164179105 us.

~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/gen_snapshot --script_snapshot=Richards.snapshot Richards.dart

~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/dart --use_script_snapshot=./Richards.snapshot Richards.dart
Richards(RunTime): 2082.206035379813 us.

~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/dart --use_script_snapshot=./Richards.snapshot Richards.dart
Richards(RunTime): 2079.002079002079 us.

The above examples might not be the best, but it’s a start to using snapshots and loading them from dart vm.

After reading John McCutchan comment below I’ve generated much more interesting output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
23:49:47-adam@Adams-MacBook-Air:~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/dart  --compiler_stats  --use_script_snapshot=./Richards.snapshot Richards.dart
Richards(RunTime): 2069.2864529472595 us.
==== Compiler Stats ====
Number of tokens:   0
  Literal tokens:   0
  Ident tokens:     0
Tokens consumed:    6973 (inf times number of tokens)
Tokens checked:     49617  (7.12 times tokens consumed)
Token rewind:       3643 (7% of tokens checked)
Token lookahead:    2361 (4% of tokens checked)
Source length:      0 characters
Scanner time:       0 msecs
Parser time:        18 msecs
Code gen. time:     66 msecs
  Graph builder:    7 msecs
  Graph SSA:        0 msecs
  Graph inliner:    10 msecs
    Parsing:        2 msecs
    Building:       1 msecs
    SSA:            1 msecs
    Optimization:   2 msecs
    Substitution:   0 msecs
  Graph optimizer:  13 msecs
  Graph compiler:   29 msecs
  Code finalizer:   4 msecs
Compilation speed:  0 tokens per msec
Code size:          56 KB
Code density:       0 tokens per KB
23:50:06-adam@Adams-MacBook-Air:~/dart/benchmark_harness/example
$ ~/dart_bleeding/dart/xcodebuild/DebugIA32/dart  --compiler_stats Richards.dart
Richards(RunTime): 2073.5751295336786 us.
==== Compiler Stats ====
Number of tokens:   1981
  Literal tokens:   68
  Ident tokens:     692
Tokens consumed:    13539 (6.83 times number of tokens)
Tokens checked:     87271  (6.45 times tokens consumed)
Token rewind:       5849 (6% of tokens checked)
Token lookahead:    3954 (4% of tokens checked)
Source length:      15543 characters
Scanner time:       3 msecs
Parser time:        23 msecs
Code gen. time:     110 msecs
  Graph builder:    5 msecs
  Graph SSA:        0 msecs
  Graph inliner:    13 msecs
    Parsing:        2 msecs
    Building:       2 msecs
    SSA:            1 msecs
    Optimization:   3 msecs
    Substitution:   1 msecs
  Graph optimizer:  13 msecs
  Graph compiler:   69 msecs
  Code finalizer:   6 msecs
Compilation speed:  14 tokens per msec
Code size:          89 KB
Code density:       22 tokens per KB
23:50:26-adam@Adams-MacBook-Air:~/dart/benchmark_harness/example

References made about snapshots in no particular order

Listing Files With Google Drive and Dart

| Comments

Moving forward with my curiosity of Google drive and Dart, today was spent creating a small sample that lists files. For the most part this is a continuation of the following post getting-started-with-dart-and-google-drive.

Decided to move the loading of client.js into its own helper class for now. When dart has real support for Google client apis none of this will be needed. As of now this serves as good practice and examples of javascript/dart interop.

dart_drive_files_list.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
 * Sample google api client loader.
 */
class GoogleApiClientLoader {
  static const String _CLIENT_ID = '299615367852.apps.googleusercontent.com';
  static const String _SCOPE = 'https://www.googleapis.com/auth/drive';
  static const String _handleClientLoadName = "handleClientLoad";

  static void _loadScript() {
    /**
     * Create and load script element.
     */
    ScriptElement script = new ScriptElement();
    script.src = "http://apis.google.com/js/client.js?onload=$_handleClientLoadName";
    script.type = "text/javascript";
    document.body.children.add(script);
  }

  static void _createScopedCallbacks(var completer) {
    js.scoped((){
      /**
       * handleAuthResult is called from javascript when
       * the function to call once the login process is complete.
       */
      js.context.handleAuthResult = new js.Callback.many((js.Proxy authResult) {
        Map dartAuthResult = JSON.parse(js.context.JSON.stringify(authResult));
        completer.complete(dartAuthResult);
      });

      /**
       * This javascript method is called when the client.js script
       * is loaded.
       */
      js.context.handleClientLoad =  new js.Callback.many(() {
        js.context.window.setTimeout(js.context.checkAuth, 1);
      });

      /**
       * Authorization check if the client allows this
       * application to access its google drive.
       */
      js.context.checkAuth = new js.Callback.many(() {
        js.context.gapi.auth.authorize(
            js.map({
              'client_id': _CLIENT_ID,
              'scope': _SCOPE,
              'immediate': true
            }),
            js.context.handleAuthResult);
      });

    });
  }

  /**
   * Load the google client api, future returns
   * map results.
   */
  static Future<Map> load() {
    var completer = new Completer();
    _createScopedCallbacks(completer);
    _loadScript();
    return completer.future;
  }
}

The calling of load() returns a future with our authentication results. Eases the process of having the google drive scope available to the client application.

A simple dart Drive class created gives access to the list call from the javascript apis. The interesting point for me to learn is knowing that client.js has to load the drive api. In the load() future we call js.context.gapi.client.load which will take the api and version to be loaded for the client application.

dart_drive_files_list.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
 * Sample google drive class.
 */
class Drive {
  js.Proxy _drive;
  bool get _isLoaded => _drive != null;

  /**
   * Load the gapi.client.drive api.
   */
  Future<bool> load() {
    var completer = new Completer();
    js.scoped(() {
      js.context.gapi.client.load('drive', 'v2', new js.Callback.once(() {
        _drive = js.context.gapi.client.drive;
        js.retain(_drive);
        completer.complete(true);
      }));
    });
    return completer.future;
  }

  /**
   * Check if gapi.client.drive is loaded, if not
   * load before executing.
   */
  void _loadederExecute(Function function) {
    if (_isLoaded) {
      function();
    } else {
      load().then((s) {
        if (s == true) {
          function();
        } else {
          throw "loadedExecute failed";
        }
      });
    }
  }

  /**
   * List files with gapi.drive.files.list()
   */
  Future<Map> list() {
    var completer = new Completer();
    _loadederExecute(() {
      js.scoped(() {
        var request = _drive.files.list();
        request.execute(new js.Callback.once((js.Proxy jsonResp, var rawResp) {
          Map m = JSON.parse(js.context.JSON.stringify(jsonResp));
          completer.complete(m);
        }));
      });
    });
    return completer.future;
  }
}

With the following sugar classes it was easy to just list files from my google drive account.

dart_drive_files_list.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
  Drive drive = new Drive();
  GoogleApiClientLoader.load().then((result) {
    drive.list().then((Map files) {
      // https://developers.google.com/drive/v2/reference/files/list
      files['items'].forEach((i) {
        var li = new LIElement();
        AnchorElement a = new AnchorElement();
        a.href = i['alternateLink'];
        a.target = '_blank';
        a.text = i['title'];
        li.children.add(a);
        UListElement ul = query('#listmenu');
        ul.children.add(li);
      });
    });
  });
}

listing-files-drive

Hopefully this helps with developers interested in google drive & dart! Sample can be found on github dart_drive_files_list