<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>app/controllers/apps_controller.rb</filename>
    </added>
    <added>
      <filename>app/helpers/apps_helper.rb</filename>
    </added>
    <added>
      <filename>app/models/upload.rb</filename>
    </added>
    <added>
      <filename>app/views/apps/_list.html.erb</filename>
    </added>
    <added>
      <filename>app/views/apps/index.html.erb</filename>
    </added>
    <added>
      <filename>app/views/apps/manage.html.erb</filename>
    </added>
    <added>
      <filename>app/views/apps/new.html.erb</filename>
    </added>
    <added>
      <filename>app/views/apps/show.html.erb</filename>
    </added>
    <added>
      <filename>db/migrate/20080413220608_create_uploads.rb</filename>
    </added>
    <added>
      <filename>db/migrate/20080413220918_add_upload_token_to_users.rb</filename>
    </added>
    <added>
      <filename>lib/appdrop_upload.rb</filename>
    </added>
    <added>
      <filename>log/mongrel.pid</filename>
    </added>
    <added>
      <filename>spec/controllers/apps_controller_spec.rb</filename>
    </added>
    <added>
      <filename>spec/fixtures/guestbook.tar.gz</filename>
    </added>
    <added>
      <filename>spec/fixtures/guestbook/app.yaml</filename>
    </added>
    <added>
      <filename>spec/fixtures/guestbook/guestbook.py</filename>
    </added>
    <added>
      <filename>spec/fixtures/invalid/app.yaml</filename>
    </added>
    <added>
      <filename>spec/fixtures/invalid/guestbook.py</filename>
    </added>
    <added>
      <filename>spec/fixtures/uploads.yml</filename>
    </added>
    <added>
      <filename>spec/helpers/apps_helper_spec.rb</filename>
    </added>
    <added>
      <filename>spec/models/appdrop_upload_spec.rb</filename>
    </added>
    <added>
      <filename>spec/models/upload_spec.rb</filename>
    </added>
    <added>
      <filename>uploads/0000/0001/guestbook.tar.gz</filename>
    </added>
    <added>
      <filename>vendor/plugins/attachment_fu</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -23,12 +23,12 @@ views
   control panel for your app
   maybe just mount the google dev console here?
 
-/apps
-  browse apps
-
-/apps/fug-this
-  public info page for app
-  link to app
+# /apps
+#   browse apps
+# 
+# /apps/fug-this
+#   public info page for app
+#   link to app
   
 
 ====</diff>
      <filename>README</filename>
    </modified>
    <modified>
      <diff>@@ -2,29 +2,90 @@ require 'ftools'
 
 class App &lt; ActiveRecord::Base
 
+  APP_ROOT = &quot;/var/apps&quot;
+  class FileError &lt; StandardError; end;
+
   validates_uniqueness_of :key
   validates_uniqueness_of :port
   validates_presence_of :key
-  validates_presence_of :port
+  # validates_presence_of :port
   validates_presence_of :user_id
   validates_format_of   :key, :with =&gt; /\A[\-a-z0-9]*\Z/
   validates_length_of   :key, :in =&gt; (1..32)
-  validates_inclusion_of :port, :in =&gt; (3000..65535)
+  validates_inclusion_of :port, :in =&gt; (3000..65535), :allow_nil =&gt; true
   
   belongs_to :user
   
+  after_create :do_setup
+  
+  def to_param
+    key
+  end
+  
+  def do_setup
+    set_port
+    initialize_configuration
+  end
+  
+  def set_port
+    return if self.port
+    transaction do
+      a = App.find :first, :order =&gt; 'port desc', :conditions =&gt; ['id != ?', self.id]
+      self.update_attribute :port, a.port + 1
+    end
+  end
+  
   def initialize_configuration
     make_app_dirs
     create_portfile
     nginx_config
   end
   
+  def update_code(tarfile)
+    untar_to_tmpdir(tarfile)
+    fileappkey = validate_appfiles
+    raise FileError, &quot;Invalid application key in tarfile&quot; unless fileappkey == self.key
+    replace_app_with_new
+  end
+
+  def replace_app_with_new
+    tmpdir = &quot;#{APP_ROOT}/#{key}/tmpapp&quot;
+    appdir = &quot;#{APP_ROOT}/#{key}/app&quot;
+    # File.copy(tmpdir, appdir)
+    `rm -rf #{appdir}`
+    `mv #{tmpdir} #{appdir}`
+  end
+
+  def untar_to_tmpdir(tarfile)
+    tmpdir = &quot;#{APP_ROOT}/#{key}/tmpapp&quot;
+    `rm -rf #{tmpdir}`
+    File.makedirs tmpdir
+    tarc = &quot;tar --file #{tarfile} --force-local -C #{tmpdir} -zx&quot;
+    success = system(tarc)
+    raise  FileError, &quot;Filesystem error with #{tarc}&quot; unless success
+  end
+  
+  def validate_appfiles
+    tmpdir = &quot;#{APP_ROOT}/#{key}/tmpapp&quot;
+    yamls = [&quot;#{tmpdir}/app.yaml&quot;, &quot;#{tmpdir}/app.yml&quot;]
+    appid = nil
+    unless yamls.any? do |file|
+      if File.exists?(file)
+        parse = File.open(file) { |yf| YAML::load( yf ) }        
+        appid = parse['application'] if parse['application']
+      end
+    end
+      raise FileError, &quot;Directory #{tmpdir} does not contain a valid app.yaml or app.yml&quot;
+    end
+    return appid
+  end
+  
   def make_app_dirs
-    File.makedirs &quot;/var/apps/#{key}/app&quot;, &quot;/var/apps/#{key}/data&quot;, &quot;/var/apps/#{key}/log&quot;
+    File.makedirs &quot;#{APP_ROOT}/#{key}/app&quot;, &quot;#{APP_ROOT}/#{key}/data&quot;, &quot;#{APP_ROOT}/#{key}/log&quot;, &quot;#{APP_ROOT}/#{key}/tmpapp&quot;
   end
   
   def create_portfile
-    File.open(&quot;/var/apps/#{key}/portfile&quot;,'w') do |f|
+    File.open(&quot;#{APP_ROOT}/#{key}/portfile&quot;,'w') do |f|
       f.write port
     end
   end
@@ -34,7 +95,7 @@ class App &lt; ActiveRecord::Base
     server {
       listen   80;
       server_name  #{key}.appdrop.com;
-      access_log  /var/apps/#{key}/log/access.log;
+      access_log  #{APP_ROOT}/#{key}/log/access.log;
       location / {
         proxy_set_header  X-Real-IP  $remote_addr;
         proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;</diff>
      <filename>app/models/app.rb</filename>
    </modified>
    <modified>
      <diff>@@ -7,7 +7,7 @@ module AuthenticatedBase
     
     base.set_table_name base.name.tableize
 
-    base.validates_presence_of     :email
+    # base.validates_presence_of     :email
     base.validates_presence_of     :password,                   :if =&gt; :password_required?
     base.validates_presence_of     :password_confirmation,      :if =&gt; :password_required?
     base.validates_length_of       :password, :within =&gt; 4..40, :if =&gt; :password_required?</diff>
      <filename>app/models/authenticated_base.rb</filename>
    </modified>
    <modified>
      <diff>@@ -23,6 +23,7 @@ class User &lt; ActiveRecord::Base
   # has_many :images, :as =&gt; :attachable
 
   # composed_of :tz, :class_name =&gt; 'TZInfo::Timezone', :mapping =&gt; %w( time_zone time_zone )
+  validates_presence_of     :email
   validates_format_of :nickname, :with =&gt; /\A[\-a-z0-9A-Z\s]*\Z/
   validates_format_of :email, :with =&gt; /^[_\w\d+-]+(\.[_\w\d-]+)*@[^\.@][\w\d\.-]+$/
 
@@ -30,6 +31,12 @@ class User &lt; ActiveRecord::Base
 
   # Protect internal methods from mass-update with update_attributes
   attr_accessible :nickname, :email, :password, :password_confirmation, :time_zone
+  include TokenGenerator
+  before_create :set_upload_token
+  
+  def set_upload_token
+    set_token(24)
+  end
   
   def to_param
     email</diff>
      <filename>app/models/user.rb</filename>
    </modified>
    <modified>
      <diff>@@ -25,7 +25,7 @@ ActionController::Routing::Routes.draw do |map|
 
   # See how all your routes lay out with &quot;rake routes&quot;
 
-  map.resources :apps
+  map.resources :apps, :member =&gt; {:manage =&gt; :get, :upload =&gt; :post}
 
   map.root :controller =&gt; &quot;pages&quot;
 </diff>
      <filename>config/routes.rb</filename>
    </modified>
    <modified>
      <diff>@@ -9,7 +9,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version =&gt; 20080412232335) do
+ActiveRecord::Schema.define(:version =&gt; 20080413220918) do
 
   create_table &quot;apps&quot;, :force =&gt; true do |t|
     t.integer  &quot;user_id&quot;
@@ -32,6 +32,16 @@ ActiveRecord::Schema.define(:version =&gt; 20080412232335) do
     t.datetime &quot;updated_at&quot;
   end
 
+  create_table &quot;uploads&quot;, :force =&gt; true do |t|
+    t.integer  &quot;user_id&quot;
+    t.integer  &quot;app_id&quot;
+    t.string   &quot;content_type&quot;
+    t.string   &quot;filename&quot;
+    t.integer  &quot;size&quot;
+    t.datetime &quot;created_at&quot;
+    t.datetime &quot;updated_at&quot;
+  end
+
   create_table &quot;users&quot;, :force =&gt; true do |t|
     t.string   &quot;nickname&quot;
     t.string   &quot;email&quot;
@@ -44,6 +54,9 @@ ActiveRecord::Schema.define(:version =&gt; 20080412232335) do
     t.datetime &quot;remember_token_expires_at&quot;
     t.integer  &quot;visits_count&quot;,                            :default =&gt; 0
     t.string   &quot;permalink&quot;
+    t.string   &quot;token&quot;
   end
 
+  add_index &quot;users&quot;, [&quot;token&quot;], :name =&gt; &quot;index_users_on_token&quot;
+
 end</diff>
      <filename>db/schema.rb</filename>
    </modified>
    <modified>
      <diff>@@ -17,8 +17,7 @@
 &lt;p&gt;&lt;strong&gt;Confidence builder:&lt;/strong&gt; AppDrop is a proof-of-concept, made to show that the AppEngine platform released by Google is not closed, and could easily be supported by other hosts. Unlike Google&amp;apos;s project, we make no claims to be able to scale beyond a small amount of traffic. In fact, at AppDrop we reserve the right to shut the whole thing down any time we feel like it. We're running on EC2, so &lt;em&gt;our virtual machine could just disappear at any time&lt;/em&gt;. We hope you enjoy AppDrop. If we see enough demand, we might decided to make serious go at it. Only time will tell.&lt;/p&gt;
 &lt;h2&gt;&lt;em&gt;Lock in, we don&amp;apos;t need no stinking lock in!&lt;/em&gt;&lt;/h2&gt;
 &lt;p&gt;&lt;strong&gt;Use at your own risk!&lt;/strong&gt; Our current implementation makes no claims to be a finished product. For that reason, we&amp;apos;re doing the simplest thing that could possibly work on most fronts. This means that we&amp;apos;ve pretty much ignored security concerns, so for the time being please don&amp;apos;t upload any applications that deal in sensitive data. Also, we&amp;apos;ve yet to enable email support. This application and the EC2 Image that support it will be released for you to play with soon enough, so if email support, database scaling, or more secure logins are important to you, you&amp;apos;ll have the opportunity to implement them yourself.&lt;/p&gt;
-&lt;p&gt;Hacked together by &lt;a href=&quot;http://jchris.mfdz.com&quot;&gt;Chris Anderson&lt;/a&gt; with moral support from &lt;a href=&quot;http://bottlecaplabs.net/&quot;&gt;Bottlecap Labs&lt;/a&gt;. &lt;a href=&quot;https://github.com/jchris/portable-google-app-engine-sdk/tree/master&quot;&gt;Download the Modified App Angine SDK&lt;/a&gt; or &lt;a href=&quot;https://github.com/jchris/appdrop/tree/master&quot;&gt;get the source code for this Rails app management console.&lt;/a&gt;&lt;/p&gt;
-
+&lt;p&gt;Hacked together by &lt;a href=&quot;http://jchris.mfdz.com&quot;&gt;Chris Anderson&lt;/a&gt; with moral support from &lt;a href=&quot;http://bottlecaplabs.net/&quot;&gt;Bottlecap Labs&lt;/a&gt;. &lt;a href=&quot;https://github.com/jchris/portable-google-app-engine-sdk/tree/master&quot;&gt;Download the Modified App Engine SDK&lt;/a&gt; or &lt;a href=&quot;https://github.com/jchris/appdrop/tree/master&quot;&gt;get the AppDrop.com source code (the Rails app you&amp;apos;re looking at)&lt;/a&gt;.&lt;/p&gt;
     &lt;/div&gt;
   &lt;/body&gt;
 &lt;/html&gt;
\ No newline at end of file</diff>
      <filename>public/index.html</filename>
    </modified>
    <modified>
      <diff>@@ -1,7 +1,9 @@
 # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
 
-# one:
-#   column: value
+one:
+  user_id: 1
+  key: danger
+  port: 3000
 #
 # two:
 #   column: value</diff>
      <filename>spec/fixtures/apps.yml</filename>
    </modified>
    <modified>
      <diff>@@ -10,6 +10,57 @@ describe App do
   end
 end
 
+describe App, &quot;on create&quot; do
+  fixtures :apps
+  it &quot;should set the port to the next port&quot; do
+    a = App.create :key =&gt; 'thisapp', :user_id =&gt; 5
+    a.should be_valid
+    a.created_at.should_not be_nil
+    a.port.should == 3001
+  end
+end
+
+describe App, &quot;update_code with a matching tarfile&quot; do
+  fixtures :apps
+  before(:each) do
+    raise &quot;OMFG don't run me on the production server!&quot; if (/nginx/.match `ls /etc/nginx`)
+    `rm /etc/nginx/sites-enabled/*`
+    `rm -rf /var/apps/*`
+    @app = App.create! :key =&gt; 'guestbook', :user_id =&gt; 5
+    `echo 'junk' &gt; /var/apps/guestbook/app/app.yaml`
+    @tarfile = &quot;#{RAILS_ROOT}/spec/fixtures/guestbook.tar.gz&quot;
+  end
+  it &quot;should have setup&quot; do
+    ls = `ls /var/apps/guestbook`
+    ls.should match(/tmpapp/)
+    appf = `ls /var/apps/guestbook/app`
+    appf.should match(/app.yaml/)
+  end
+  it &quot;should untar the tarball to the tmpapp directory&quot; do
+    @app.stub!(:replace_app_with_new)
+    @app.update_code @tarfile
+    File.exists?(&quot;#{App::APP_ROOT}/guestbook/tmpapp/app.yaml&quot;).should be_true
+  end
+  it &quot;should copy the new dir over the old directory&quot; do
+    @app.update_code @tarfile
+    IO.readlines(&quot;#{App::APP_ROOT}/guestbook/app/app.yaml&quot;)[0].should match(/guestbook/)
+  end
+  it &quot;should ensure that it is running&quot;
+end
+
+describe App, &quot;update_code with a mismatched tarfile&quot; do
+  before(:each) do
+    raise &quot;OMFG don't run me on the production server!&quot; if (/nginx/.match `ls /etc/nginx`)
+    `rm /etc/nginx/sites-enabled/*`
+    `rm -rf /var/apps/*`
+    @app = App.create! :key =&gt; 'notguestbook', :user_id =&gt; 5
+    @tarfile = &quot;#{RAILS_ROOT}/spec/fixtures/guestbook.tar.gz&quot;
+  end
+  it &quot;should fail&quot; do
+    lambda{@app.update_code @tarfile}.should raise_error(App::FileError)
+  end
+end
+
 describe App, &quot;initializing configuration&quot; do
   before(:each) do
     raise &quot;OMFG don't run me on the production server!&quot; if (/nginx/.match `ls /etc/nginx`)
@@ -28,6 +79,7 @@ describe App, &quot;initializing configuration&quot; do
     ls.should match(/log/)
     ls.should match(/data/)
     ls.should match(/app/)
+    ls.should match(/tmpapp/)
   end
   it &quot;should set a portfile&quot; do
     @app.initialize_configuration</diff>
      <filename>spec/models/app_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,5 +18,9 @@ describe &quot;A User&quot; do
     @user.created_at.should == ca
   end
   
+  it &quot;should set an upload token&quot; do
+    @user.save
+    @user.token.should_not be_nil
+  end
 end
 </diff>
      <filename>spec/models/user_spec.rb</filename>
    </modified>
    <modified>
      <diff>@@ -9,7 +9,7 @@ module TokenGenerator
     token
   end
 
-  def set_token
-    self.token = generate_token { |token| self.class.find_by_token(token).nil? }
+  def set_token(size = 12)
+    self.token = generate_token(size) { |token| self.class.find_by_token(token).nil? }
   end
 end</diff>
      <filename>vendor/plugins/token_generator/lib/token_generator.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>3a27c76c66fa5542b619884720cf97baa58b40ac</id>
    </parent>
  </parents>
  <author>
    <name>Chris Anderson</name>
    <email>jchris@grabb.it</email>
  </author>
  <url>http://github.com/jchris/appdrop/commit/798e4cdbd41260cea777d85db0728a5184c8a709</url>
  <id>798e4cdbd41260cea777d85db0728a5184c8a709</id>
  <committed-date>2008-04-13T19:04:22-07:00</committed-date>
  <authored-date>2008-04-13T19:04:22-07:00</authored-date>
  <message>upload managment and script</message>
  <tree>c73f78b11c37f86a0a07582f0aa047961a1f8484</tree>
  <committer>
    <name>Chris Anderson</name>
    <email>jchris@grabb.it</email>
  </committer>
</commit>
