Saturday, July 24, 2010
Ruby on Mirah on App Engine
             Run your apps on Google Servers,
             with access to first-class Java API...
Google App Engine
               3



Saturday, July 24, 2010
Key Features
             • No need to install or maintain your own stack
             • Use Google scalable services via ...
Key Limitations
             • No native code
             • No threads or sockets
             • No writing to the filesy...
Dev AppServer
             • Local implementation of services
               – LRU memcache
               – Disk-backed d...
Deployment
             • Your app lives at
               – <app_id>.appspot.com, or
               – Custom domain with ...
Quotas and Billing
                      Resource       Provided Free      Additional Cost
                      CPU time ...
App Engine Product Roadmap
             • SSL for third-party domains
             • Background servers capable of running...
JRuby on App Engine
              10



Saturday, July 24, 2010
Benefits of JRuby
             • Outperforms MRI in many cases... 2x to 10x
             • Gem extensions written in Java ...
App Engine JRuby Milestones
             • 2009-04-08   @olabini publishes blog post on YARBL
             • 2009-04-09   ...
Current Issues with JRuby on App Engine
             • Several seconds to “spin-up” a new JRuby instance
             • So...
Install it Now

                     sudo gem install google-appengine



                          Everything you need in...
App Engine Gems
            • Development Gems
            – appengine-sdk
            – appengine-tools . . . dev_appserv...
App Engine JRuby APIs
             • AppEngine::Users
             • AppEngine::Datastore
             • AppEngine::Memcac...
Mirah
                          Charles Nutter




              17



Saturday, July 24, 2010
Me
                     • Charles Oliver Nutter
                     • JRuby co-lead
                     • Ex-Java EE dev...
My Day Job

                     • JRuby and JRuby-related extensions
                     • JVM-based utilities and libra...
My Problem

                     • I love Ruby
                     • I write Java all day for JRuby
                     ...
What If This...
                          public class Foo {
                            private int a;

                 ...
...Could Be This
                          class Foo
                            def initialize(a)
                       ...
Mirah
                          class Foo
                            def initialize(a:int)
                              ...
Mirah

                     • A nicer way to write Java
                     • Ruby syntax with modifications
             ...
Features From Ruby
                     • Optional arguments ✓
                     • Internal iteration ✓
               ...
Ruby


                          puts “Hello, world!”




Saturday, July 24, 2010
Mirah


                          puts “Hello, world!”




Saturday, July 24, 2010
Ruby
    public static __file__(Lruby/__dash_e__;Lorg/jruby/runtime/ThreadContext;Lorg/jruby/
  runtime/builtin/IRubyObjec...
Mirah

  public static void main(java.lang.String[]);
    Code:
     0:
       getstatic
 #12; //Field java/lang/System.ou...
Mirah

      // Generated from DashE
      public class DashE extends java.lang.Object {
        public static void main(j...
Ruby
                          def fib(a)
                            if a < 2
                              a
           ...
Mirah
                          def fib(a:int)
                            if a < 2
                              a
      ...
Ruby
                            public static method__0$RUBY$fib(Lruby/__dash_e__;Lorg/jruby/runtime/ThreadContext;Lorg/j...
Mirah
                          public static int fib(int);
                            Code:
                            ...
Mirah
        // Generated from DashE
        public class DashE extends java.lang.Object {
          public static void m...
Ruby


                          def foo(a = 1, b = 2)
                            puts a + b
                          en...
Mirah


                          def foo(a:int = 1, b:int = 2)
                            puts a + b
                   ...
Mirah
              public static java.io.PrintStream foo(int a, int b) {
                java.io.PrintStream temp$1 =
   ...
Ruby

                          a = [5,4,3,2,1]
                          a.each do |x|
                            puts x...
Mirah

                          a = [5,4,3,2,1]
                          a.each do |x|
                            puts ...
Mirah
           // Generated from DashE
           public class DashE extends java.lang.Object {
             public stat...
Ruby


                          t = Thread.new do
                            puts “in thread”
                          ...
Mirah


                          t = Thread.new do
                            puts “in thread”
                         ...
// Generated from DashE
                                    Mirah
  public class DashE extends java.lang.Object {
    publ...
// Generated from DashE
                                    Mirah
  public class DashE extends java.lang.Object {
    publ...
// Generated from DashE
                                    Mirah
  public class DashE extends java.lang.Object {
    publ...
It’s Not Ruby

                     • Using Java’s libraries and type system
                     • No “eval”
            ...
But It Feels Like Ruby!

                     • Clean, lightweight syntax
                     • Iteration, closures
     ...
mirah.org



Saturday, July 24, 2010
Rails & Mirah Demos
                           John Woodell




              50



Saturday, July 24, 2010
Saturday, July 24, 2010
Saturday, July 24, 2010
Benefits of Mirah on App Engine
             • New instances always spin-up in about a second
             • Dubious frame...
Mirah apps should look familiar to Rubyists




Saturday, July 24, 2010
Mirah apps can use ERb templates




Saturday, July 24, 2010
The generated Java source can be inspected




Saturday, July 24, 2010
The ERb is transformed into method calls




Saturday, July 24, 2010
Your model definition is very concise




Saturday, July 24, 2010
Code is generated based on properties you define




Saturday, July 24, 2010
All the basic methods you need are generated




Saturday, July 24, 2010
Resources
             • Blog
               – http://jruby-appengine.blogspot.com
             • Code Site
              ...
Saturday, July 24, 2010
Upcoming SlideShare
Loading in...5
×

Oscon 2010

3,065

Published on

0 Comments
10 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,065
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
29
Comments
0
Likes
10
Embeds 0
No embeds

No notes for slide

Oscon 2010

  1. 1. Saturday, July 24, 2010
  2. 2. Ruby on Mirah on App Engine Run your apps on Google Servers, with access to first-class Java APIs John Woodell Charles Nutter July 22, 2010 2 Saturday, July 24, 2010
  3. 3. Google App Engine 3 Saturday, July 24, 2010
  4. 4. Key Features • No need to install or maintain your own stack • Use Google scalable services via standard APIs • Built-in application management console • Pay-as-you-go, with free quota to get started 4 Saturday, July 24, 2010
  5. 5. Key Limitations • No native code • No threads or sockets • No writing to the filesystem • No more than 30 seconds per request 5 Saturday, July 24, 2010
  6. 6. Dev AppServer • Local implementation of services – LRU memcache – Disk-backed datastore – HttpClient-backed URLFetch • Emulates the production environment – Sandbox restrictions may be inconsistent, so run tests on production servers as well 6 Saturday, July 24, 2010
  7. 7. Deployment • Your app lives at – <app_id>.appspot.com, or – Custom domain with Google Apps • Deploying uploads – Static files – Resource files – Other metadata (datastore indexes, cron jobs) • Admin Console – dashboards – manage multiple versions – view logs 7 Saturday, July 24, 2010
  8. 8. Quotas and Billing Resource Provided Free Additional Cost CPU time 6.5 hours/day $0.10/hour Bandwidth In 1GByte/day $0.10/GByte Bandwidth Out 1GByte/day $0.12/GByte Stored Data 1 GB $0.005/GB-day Emails sent 2000/day to users $0.0001/email 50000/day to admins 8 Saturday, July 24, 2010
  9. 9. App Engine Product Roadmap • SSL for third-party domains • Background servers capable of running for longer than 30s • Ability to reserve instances to reduce application loading overhead • Ability to select different availability vs. latency options for Datastore • Support for mapping operations across datasets • Datastore dump and restore facility • Raise request/response size limits for some APIs • Improved monitoring and alerting of application serving • Support for Browser Push (Comet) communication • Built-in support for OAuth & OpenID 9 Saturday, July 24, 2010
  10. 10. JRuby on App Engine 10 Saturday, July 24, 2010
  11. 11. Benefits of JRuby • Outperforms MRI in many cases... 2x to 10x • Gem extensions written in Java (no more segfaults) • A wealth of integration options and first-class Java APIs • Spin-up new instances quickly using Mirah/Java servlets 11 Saturday, July 24, 2010
  12. 12. App Engine JRuby Milestones • 2009-04-08 @olabini publishes blog post on YARBL • 2009-04-09 @nicksieger publishes warbler demo • 2009-05-06 RailsConf (sinatra & merb) • 2009-11-02 0.0.5 bundler, precompilation & Duby preview • 2009-11-20 RubyConf (Rails 3.0.pre & Duby App) • 2009-12-27 @urekat publishes Rails 2.3.5 patches • 2010-01-11 @codingforrent published rails/dm gem • 2010-01-21 0.0.8 Rails 2.3.5 Primer for DM & TinyDS • 2010-01-26 0.0.9 Mechanize and Hpricot demos • 2010-02-27 0.0.10 ActionMailer, ImageService, jruby-openssl • 2010-04-08 @azazeal blog post on taxster.gr (OpenID) • 2010-06-09 0.0.14 JRuby 1.5.1 & app.yaml preview 12 Saturday, July 24, 2010
  13. 13. Current Issues with JRuby on App Engine • Several seconds to “spin-up” a new JRuby instance • Some gems may need their extensions ported to Java • Not officially supported , but you have all that you need + 13 Saturday, July 24, 2010
  14. 14. Install it Now sudo gem install google-appengine Everything you need installs as gems 14 Saturday, July 24, 2010
  15. 15. App Engine Gems • Development Gems – appengine-sdk – appengine-tools . . . dev_appserver.rb & appcfg.rb • Runtime Gems – appengine-rack . . . . jruby-jars & jruby-rack – appengine-apis • Related Gems – dm-appengine – rails_appengine – rails_dm_datastore – rails_tiny_ds 15 Saturday, July 24, 2010
  16. 16. App Engine JRuby APIs • AppEngine::Users • AppEngine::Datastore • AppEngine::Memcache • AppEngine::Mail • AppEngine::URLFetch • AppEngine::Images • AppEngine::Logger • AppEngine::XMPP • AppEngine::Labs::TaskQueue • AppEngine::OAuth • AppEngine::Blobstore 16 Saturday, July 24, 2010
  17. 17. Mirah Charles Nutter 17 Saturday, July 24, 2010
  18. 18. Me • Charles Oliver Nutter • JRuby co-lead • Ex-Java EE developer • @headius • headius@headius.com • blog.headius.com Saturday, July 24, 2010
  19. 19. My Day Job • JRuby and JRuby-related extensions • JVM-based utilities and libraries (for JRuby?) • Bytecode generation • Mobile and embedded interests • Narrow, “system”-level scope Saturday, July 24, 2010
  20. 20. My Problem • I love Ruby • I write Java all day for JRuby • How can I write Ruby all day? Saturday, July 24, 2010
  21. 21. What If This... public class Foo { private int a; public Foo(int a) { this.a = a; } public void show() { System.out.println(a); } } Saturday, July 24, 2010
  22. 22. ...Could Be This class Foo def initialize(a) @a = a end def show puts @a end end Saturday, July 24, 2010
  23. 23. Mirah class Foo def initialize(a:int) @a = a end def show puts @a end end Saturday, July 24, 2010
  24. 24. Mirah • A nicer way to write Java • Ruby syntax with modifications • Feels like Ruby • Compiles to Java/JVM • No runtime library Saturday, July 24, 2010
  25. 25. Features From Ruby • Optional arguments ✓ • Internal iteration ✓ • Closures ✓ • Literals ✓ • String interpolation ✓ • Mixins, “open” classes (soon) Saturday, July 24, 2010
  26. 26. Ruby puts “Hello, world!” Saturday, July 24, 2010
  27. 27. Mirah puts “Hello, world!” Saturday, July 24, 2010
  28. 28. Ruby public static __file__(Lruby/__dash_e__;Lorg/jruby/runtime/ThreadContext;Lorg/jruby/ runtime/builtin/IRubyObject;[Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/ Block;)Lorg/jruby/runtime/builtin/IRubyObject; @Lorg/jruby/anno/JRubyMethod;(name="__file__", frame=true, required=0, optional=0, rest=-2) L0 LINENUMBER 1 L0 ALOAD 1 ICONST_0 INVOKESTATIC ruby/__dash_e__.setPosition (Lorg/jruby/runtime/ThreadContext;I)V ALOAD 0 INVOKEVIRTUAL ruby/__dash_e__.getCallSite0 ()Lorg/jruby/runtime/CallSite; ALOAD 1 ALOAD 2 ALOAD 2 ALOAD 0 ALOAD 1 GETFIELD org/jruby/runtime/ThreadContext.runtime : Lorg/jruby/Ruby; INVOKEVIRTUAL ruby/__dash_e__.getString0 (Lorg/jruby/Ruby;)Lorg/jruby/RubyString; INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/ jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/ runtime/builtin/IRubyObject;)Lorg/jruby/runtime/builtin/IRubyObject; ARETURN Saturday, July 24, 2010
  29. 29. Mirah public static void main(java.lang.String[]); Code: 0: getstatic #12; //Field java/lang/System.out:Ljava/ io/PrintStream; 3: ldc #14; //String Hello, world! 5: invokevirtual #20; //Method java/io/ PrintStream.println:(Ljava/lang/String;)V 8: return Saturday, July 24, 2010
  30. 30. Mirah // Generated from DashE public class DashE extends java.lang.Object { public static void main(java.lang.String[] argv) { java.io.PrintStream temp$1 = java.lang.System.out; temp$1.println("Hello, world!"); } } Saturday, July 24, 2010
  31. 31. Ruby def fib(a) if a < 2 a else fib(a - 1) + fib(a - 2) end end Saturday, July 24, 2010
  32. 32. Mirah def fib(a:int) if a < 2 a else fib(a - 1) + fib(a - 2) end end Saturday, July 24, 2010
  33. 33. Ruby public static method__0$RUBY$fib(Lruby/__dash_e__;Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/ IRubyObject;Lorg/jruby/runtime/Block;)Lorg/jruby/runtime/builtin/IRubyObject; @Lorg/jruby/anno/JRubyMethod;(name="fib", frame=true, required=1, optional=0, rest=-1) ALOAD 3 ASTORE 9 L0 LINENUMBER 1 L0 ALOAD 1 ICONST_0 INVOKESTATIC ruby/__dash_e__.setPosition (Lorg/jruby/runtime/ThreadContext;I)V ALOAD 0 INVOKEVIRTUAL ruby/__dash_e__.getCallSite0 ()Lorg/jruby/runtime/CallSite; ALOAD 1 ALOAD 2 ALOAD 9 LDC 2 INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/ IRubyObject;J)Lorg/jruby/runtime/builtin/IRubyObject; INVOKEINTERFACE org/jruby/runtime/builtin/IRubyObject.isTrue ()Z IFEQ L1 ALOAD 9 GOTO L2 L1 FRAME FULL [ruby/__dash_e__ org/jruby/runtime/ThreadContext org/jruby/runtime/builtin/IRubyObject org/jruby/runtime/builtin/IRubyObject org/jruby/runtime/Block T T T T org/jruby/runtime/builtin/IRubyObject] [] ALOAD 0 INVOKEVIRTUAL ruby/__dash_e__.getCallSite1 ()Lorg/jruby/runtime/CallSite; ALOAD 1 ALOAD 2 ALOAD 0 INVOKEVIRTUAL ruby/__dash_e__.getCallSite2 ()Lorg/jruby/runtime/CallSite; ALOAD 1 ALOAD 2 ALOAD 2 ALOAD 0 INVOKEVIRTUAL ruby/__dash_e__.getCallSite3 ()Lorg/jruby/runtime/CallSite; ALOAD 1 ALOAD 2 ALOAD 9 LDC 1 INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/ IRubyObject;J)Lorg/jruby/runtime/builtin/IRubyObject; INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/ IRubyObject;Lorg/jruby/runtime/builtin/IRubyObject;)Lorg/jruby/runtime/builtin/IRubyObject; ALOAD 0 INVOKEVIRTUAL ruby/__dash_e__.getCallSite4 ()Lorg/jruby/runtime/CallSite; ALOAD 1 ALOAD 2 ALOAD 2 ALOAD 0 INVOKEVIRTUAL ruby/__dash_e__.getCallSite5 ()Lorg/jruby/runtime/CallSite; ALOAD 1 ALOAD 2 ALOAD 9 LDC 2 INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/ IRubyObject;J)Lorg/jruby/runtime/builtin/IRubyObject; INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/ IRubyObject;Lorg/jruby/runtime/builtin/IRubyObject;)Lorg/jruby/runtime/builtin/IRubyObject; INVOKEVIRTUAL org/jruby/runtime/CallSite.call (Lorg/jruby/runtime/ThreadContext;Lorg/jruby/runtime/builtin/IRubyObject;Lorg/jruby/runtime/builtin/ IRubyObject;Lorg/jruby/runtime/builtin/IRubyObject;)Lorg/jruby/runtime/builtin/IRubyObject; L2 FRAME SAME1 org/jruby/runtime/builtin/IRubyObject ARETURN Saturday, July 24, 2010
  34. 34. Mirah public static int fib(int); Code: 0: iload_0 1: iconst_2 2: if_icmplt 9 5: iconst_0 6: goto 10 9: iconst_1 10: ifeq 17 13: iload_0 14: goto 30 17: iload_0 18: iconst_1 19: isub 20: invokestatic #10; //Method fib:(I)I 23: iload_0 24: iconst_2 25: isub 26: invokestatic #10; //Method fib:(I)I 29: iadd 30: ireturn Saturday, July 24, 2010
  35. 35. Mirah // Generated from DashE public class DashE extends java.lang.Object { public static void main(java.lang.String[] argv) { } public static int fib(int a) { return (a < 2) ? (a) : ((DashE.fib((a - 1)) + DashE.fib((a - 2)))); } } Saturday, July 24, 2010
  36. 36. Ruby def foo(a = 1, b = 2) puts a + b end Saturday, July 24, 2010
  37. 37. Mirah def foo(a:int = 1, b:int = 2) puts a + b end Saturday, July 24, 2010
  38. 38. Mirah public static java.io.PrintStream foo(int a, int b) { java.io.PrintStream temp$1 = java.lang.System.out; temp$1.println((a + b)); return temp$1; } public static java.io.PrintStream foo() { return foo(1); } public static java.io.PrintStream foo(int a) { return foo(a, 2); } Saturday, July 24, 2010
  39. 39. Ruby a = [5,4,3,2,1] a.each do |x| puts x end Saturday, July 24, 2010
  40. 40. Mirah a = [5,4,3,2,1] a.each do |x| puts x end Saturday, July 24, 2010
  41. 41. Mirah // Generated from DashE public class DashE extends java.lang.Object { public static void main(java.lang.String[] argv) { java.util.List a = java.util.Collections.unmodifiableList( java.util.Arrays.asList(1, 2, 3, 4, 5)); java.util.Iterator __xform_tmp_1 = a.iterator(); label1: while (__xform_tmp_1.hasNext()) { java.lang.Object x = __xform_tmp_1.next(); label2: { java.io.PrintStream temp$3 = java.lang.System.out; temp$3.println(x); } } } } Saturday, July 24, 2010
  42. 42. Ruby t = Thread.new do puts “in thread” end Saturday, July 24, 2010
  43. 43. Mirah t = Thread.new do puts “in thread” end Saturday, July 24, 2010
  44. 44. // Generated from DashE Mirah public class DashE extends java.lang.Object { public static void main(java.lang.String[] argv) { DashE.__xform_tmp_1 $binding = new DashE.__xform_tmp_1(); $binding.x = "in thread"; java.lang.Thread t = new java.lang.Thread(new DashE.__xform_tmp_2($binding)); } public static class __xform_tmp_1 extends java.lang.Object { java.lang.String x; } public static class __xform_tmp_2 extends java.lang.Object implements java.lang.Runnable { private DashE.__xform_tmp_1 binding; public __xform_tmp_2(DashE.__xform_tmp_1 binding) { this.binding = binding; } public void run() { DashE.__xform_tmp_1 $binding = this.binding; java.io.PrintStream temp$1 = java.lang.System.out; temp$1.println($binding.x); } } } Saturday, July 24, 2010
  45. 45. // Generated from DashE Mirah public class DashE extends java.lang.Object { public static void main(java.lang.String[] argv) { DashE.__xform_tmp_1 $binding = new DashE.__xform_tmp_1(); $binding.x = "in thread"; java.lang.Thread t = new java.lang.Thread(new DashE.__xform_tmp_2($binding)); } public static class __xform_tmp_1 extends java.lang.Object { java.lang.String x; } public static class __xform_tmp_2 extends java.lang.Object implements java.lang.Runnable { private DashE.__xform_tmp_1 binding; public __xform_tmp_2(DashE.__xform_tmp_1 binding) { this.binding = binding; } public void run() { DashE.__xform_tmp_1 $binding = this.binding; java.io.PrintStream temp$1 = java.lang.System.out; temp$1.println($binding.x); } } } Saturday, July 24, 2010
  46. 46. // Generated from DashE Mirah public class DashE extends java.lang.Object { public static void main(java.lang.String[] argv) { DashE.__xform_tmp_1 $binding = new DashE.__xform_tmp_1(); $binding.x = "in thread"; java.lang.Thread t = new java.lang.Thread(new DashE.__xform_tmp_2($binding)); } public static class __xform_tmp_1 extends java.lang.Object { java.lang.String x; } public static class __xform_tmp_2 extends java.lang.Object implements java.lang.Runnable { private DashE.__xform_tmp_1 binding; public __xform_tmp_2(DashE.__xform_tmp_1 binding) { this.binding = binding; } public void run() { DashE.__xform_tmp_1 $binding = this.binding; java.io.PrintStream temp$1 = java.lang.System.out; temp$1.println($binding.x); } } } Saturday, July 24, 2010
  47. 47. It’s Not Ruby • Using Java’s libraries and type system • No “eval” • No runtime-mutable classes • Ruby libraries will not work Saturday, July 24, 2010
  48. 48. But It Feels Like Ruby! • Clean, lightweight syntax • Iteration, closures • Far less “ceremony” than Java • Performance equivalent to Java Saturday, July 24, 2010
  49. 49. mirah.org Saturday, July 24, 2010
  50. 50. Rails & Mirah Demos John Woodell 50 Saturday, July 24, 2010
  51. 51. Saturday, July 24, 2010
  52. 52. Saturday, July 24, 2010
  53. 53. Benefits of Mirah on App Engine • New instances always spin-up in about a second • Dubious framework uses familiar Rails conventions • Code in Java or Ruby when Mirah lacks features you require • The generated Java source can be inspected at any time 53 Saturday, July 24, 2010
  54. 54. Mirah apps should look familiar to Rubyists Saturday, July 24, 2010
  55. 55. Mirah apps can use ERb templates Saturday, July 24, 2010
  56. 56. The generated Java source can be inspected Saturday, July 24, 2010
  57. 57. The ERb is transformed into method calls Saturday, July 24, 2010
  58. 58. Your model definition is very concise Saturday, July 24, 2010
  59. 59. Code is generated based on properties you define Saturday, July 24, 2010
  60. 60. All the basic methods you need are generated Saturday, July 24, 2010
  61. 61. Resources • Blog – http://jruby-appengine.blogspot.com • Code Site – http://code.google.com/p/appengine-jruby • Examples Apps – http://rails-primer.appspot.com – http://dubious-demo.appspot.com • Mirah Projects – http://github.com/headius/mirah – http://github.com/mirah/dubious 61 Saturday, July 24, 2010
  62. 62. Saturday, July 24, 2010
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×