octopus 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +41 -0
  5. data/Rakefile +62 -0
  6. data/VERSION +1 -0
  7. data/bin/octopus +16 -0
  8. data/doc/octopus.zargo +0 -0
  9. data/lib/ext/array_ext.rb +8 -0
  10. data/lib/ext/partials.rb +27 -0
  11. data/lib/ext/reloader.rb +11 -0
  12. data/lib/init.rb +68 -0
  13. data/lib/octopus/config.ru +11 -0
  14. data/lib/octopus/grabbers/generic_http.rb +75 -0
  15. data/lib/octopus/models/net_resource.rb +68 -0
  16. data/lib/octopus/models/subscription.rb +35 -0
  17. data/lib/octopus/public/default.css +233 -0
  18. data/lib/octopus/public/images/bg01.jpg +0 -0
  19. data/lib/octopus/public/images/bg02.jpg +0 -0
  20. data/lib/octopus/public/images/bg03.jpg +0 -0
  21. data/lib/octopus/public/images/bg04.jpg +0 -0
  22. data/lib/octopus/public/images/img01.jpg +0 -0
  23. data/lib/octopus/public/images/img02.gif +0 -0
  24. data/lib/octopus/public/images/img03.gif +0 -0
  25. data/lib/octopus/public/images/img04.gif +0 -0
  26. data/lib/octopus/public/images/img05.gif +0 -0
  27. data/lib/octopus/public/images/img06.jpg +0 -0
  28. data/lib/octopus/public/images/spacer.gif +0 -0
  29. data/lib/octopus/views/edit.erb +29 -0
  30. data/lib/octopus/views/index.erb +21 -0
  31. data/lib/octopus/views/layout.erb +65 -0
  32. data/lib/octopus/views/new.erb +42 -0
  33. data/lib/octopus/views/resource_list_item.erb +9 -0
  34. data/lib/octopus.rb +81 -0
  35. data/octopus.gemspec +113 -0
  36. data/test/helper.rb +50 -0
  37. data/test/support/blueprints.rb +20 -0
  38. data/test/test_net_resource.rb +79 -0
  39. data/test/test_octopus.rb +153 -0
  40. data/test/test_subscription.rb +45 -0
  41. metadata +231 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ *.sqlite3
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 dave@boomer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,41 @@
1
+ = octopus
2
+
3
+ This thing is an experimental octopus implementation.
4
+
5
+ == Installation
6
+
7
+
8
+ sudo apt-get install libsqlite3-ruby libsqlite3-dev ruby rubygems
9
+ sudo gem install octopus
10
+
11
+ *Grab the code:*
12
+
13
+ Anonymous checkout:
14
+
15
+ foo: put github stuff here
16
+
17
+ Commit access:
18
+
19
+ git clone git@escapegoat.org:octopus.git
20
+
21
+ Once you've got the code, just run the octopus like so:
22
+
23
+ ruby octopus.rb
24
+
25
+ Then visit http://localhost:4567 and you can start adding resources for your octopus to grab. There is a resource creation form at http://localhost:4567/new
26
+
27
+
28
+ == Note on Patches/Pull Requests
29
+
30
+ * Fork the project.
31
+ * Make your feature addition or bug fix.
32
+ * Add tests for it. This is important so I don't break it in a
33
+ future version unintentionally.
34
+ * Commit, do not mess with rakefile, version, or history.
35
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
36
+ * Send me a pull request. Bonus points for topic branches.
37
+
38
+ == Copyright
39
+
40
+ Copyright (c) 2009 dave@boomer See LICENSE for details.
41
+
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "octopus"
8
+ gem.summary = %Q{An experimental octopus implementation.}
9
+ gem.description = %Q{Grabs stuff off the net and notifies interested subscribers.}
10
+ gem.email = "dave.hrycyszyn@headlondon.com"
11
+ gem.homepage = "http://github.com/futurechimp/octopus"
12
+ gem.authors = ["dave@boomer"]
13
+ gem.add_dependency "datamapper", ">= 0.10.1"
14
+ gem.add_dependency "do_sqlite3", ">= 0.10.0"
15
+ gem.add_dependency "sinatra", ">= 0.9.4"
16
+ gem.add_dependency "thin", ">= 1.2.5"
17
+ gem.add_dependency "rack-flash", ">= 0.1.1"
18
+ gem.add_dependency "rack", ">= 1.0.1"
19
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 2.10.2"
20
+ gem.add_development_dependency "rack-test", ">= 0.5.2"
21
+ gem.add_development_dependency "notahat-machinist", ">= 1.0.3"
22
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
23
+ end
24
+ Jeweler::GemcutterTasks.new
25
+ rescue LoadError
26
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
27
+ end
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ Rcov::RcovTask.new do |test|
39
+ test.libs << 'test'
40
+ test.pattern = 'test/**/test_*.rb'
41
+ test.verbose = true
42
+ end
43
+ rescue LoadError
44
+ task :rcov do
45
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
46
+ end
47
+ end
48
+
49
+ task :test => :check_dependencies
50
+
51
+ task :default => :test
52
+
53
+ require 'rake/rdoctask'
54
+ Rake::RDocTask.new do |rdoc|
55
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
56
+
57
+ rdoc.rdoc_dir = 'rdoc'
58
+ rdoc.title = "octopus #{version}"
59
+ rdoc.rdoc_files.include('README*')
60
+ rdoc.rdoc_files.include('lib/**/*.rb')
61
+ end
62
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/octopus ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Octopus command line interface script.
4
+ # Run <tt>octopus -h</tt> to get more usage.
5
+ require File.dirname(__FILE__) + '/../lib/octopus'
6
+ require 'thin'
7
+
8
+ rackup_file = "#{File.dirname(__FILE__)}/../lib/octopus/config.ru"
9
+
10
+ argv = ARGV
11
+ argv << ["-R", rackup_file] unless ARGV.include?("-R")
12
+ argv << ["-p", "2001"] unless ARGV.include?("-p")
13
+ argv << ["-l", "/home/dave"] unless ARGV.include?("-l")
14
+ argv << ["-e", "production"] unless ARGV.include?("-e")
15
+ Thin::Runner.new(argv.flatten).run!
16
+
data/doc/octopus.zargo ADDED
Binary file
@@ -0,0 +1,8 @@
1
+
2
+ # Monkey patches
3
+ class Array
4
+ def extract_options!
5
+ last.is_a?(::Hash) ? pop : {}
6
+ end
7
+ end
8
+
@@ -0,0 +1,27 @@
1
+ # An implementation of partials for Sinatra.
2
+ #
3
+ # Can be used like: <%= partial(:foo) %>
4
+ #
5
+ # Unlike Rails partials, the .erb files for these partials do not start with a
6
+ # leading underscore, i.e. the file name should be foo.rb, not _foo.rb.
7
+ #
8
+ # Liberated from:
9
+ # http://github.com/cschneid/irclogger/blob/master/lib/partials.rb
10
+ #
11
+ module Sinatra
12
+ module Partials
13
+ def partial(template, *args)
14
+ options = args.extract_options!
15
+ options.merge!(:layout => false)
16
+ if collection = options.delete(:collection) then
17
+ collection.inject([]) do |buffer, member|
18
+ buffer << erb(template, options.merge(:layout =>
19
+ false, :locals => {template.to_sym => member}))
20
+ end.join("\n")
21
+ else
22
+ erb(template, options)
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,11 @@
1
+ # Reload scripts and reset routes on change
2
+ class Sinatra::Reloader < Rack::Reloader
3
+ def safe_load(file, mtime, stderr = $stderr)
4
+ if file == __FILE__
5
+ ::Sinatra::Application.reset!
6
+ stderr.puts "#{self.class}: resetting routes"
7
+ end
8
+ super
9
+ end
10
+ end
11
+
data/lib/init.rb ADDED
@@ -0,0 +1,68 @@
1
+ # Gems
2
+ #
3
+ require 'rubygems'
4
+ require 'sinatra'
5
+ require 'datamapper'
6
+ require 'ruby-debug'
7
+ require 'eventmachine'
8
+ require 'rack-flash'
9
+ require 'dm-validations'
10
+
11
+
12
+ # Extensions to Sinatra
13
+ #
14
+ require File.dirname(__FILE__) + '/ext/partials'
15
+ require File.dirname(__FILE__) + '/ext/array_ext'
16
+ require File.dirname(__FILE__) + '/ext/reloader'
17
+
18
+ # Octopus code
19
+ #
20
+ require File.dirname(__FILE__) + '/octopus'
21
+ require File.dirname(__FILE__) + '/octopus/models/subscription'
22
+ require File.dirname(__FILE__) + '/octopus/models/net_resource'
23
+ require File.dirname(__FILE__) + '/octopus/grabbers/generic_http'
24
+
25
+ configure :production do
26
+ db = "sqlite3:///#{Dir.pwd}/octopus.sqlite3"
27
+ DataMapper.setup(:default, db)
28
+ end
29
+
30
+ configure :development do
31
+ db = "sqlite3:///#{Dir.pwd}/octopus.sqlite3"
32
+ DataMapper.setup(:default, db)
33
+ end
34
+
35
+ configure :test do
36
+ db = "sqlite3::memory:"
37
+ DataMapper.setup(:default, db)
38
+ end
39
+
40
+ configure :production, :test, :development do
41
+ NetResource.auto_migrate! unless NetResource.storage_exists?
42
+ Subscription.auto_migrate! unless Subscription.storage_exists?
43
+ DataMapper.auto_upgrade!
44
+ end
45
+
46
+ # Set the views to the proper path inside the gem
47
+ #
48
+ set :views, File.dirname(__FILE__) + '/octopus/views'
49
+ set :public, File.dirname(__FILE__) + '/octopus/public'
50
+
51
+ # Register helpers
52
+ #
53
+ helpers do
54
+ include Sinatra::Partials
55
+ alias_method :h, :escape_html
56
+ end
57
+
58
+ # Set up Rack authentication
59
+ #
60
+ use Rack::Auth::Basic do |username, password|
61
+ [username, password] == ['admin', 'admin']
62
+ end
63
+
64
+ # Include flash notices
65
+ #
66
+ use Rack::Session::Cookie
67
+ use Rack::Flash
68
+
@@ -0,0 +1,11 @@
1
+ require 'sinatra'
2
+
3
+ set :run => false
4
+ set :environment => :production
5
+
6
+ disable :reload
7
+
8
+ run Sinatra::Application
9
+
10
+ require File.dirname(__FILE__) + '/init'
11
+
@@ -0,0 +1,75 @@
1
+ module Grabbers
2
+ class GenericHttp
3
+
4
+ include EM::Protocols
5
+
6
+ # Adds a periodic timer to the Eventmachine reactor loop and immediately
7
+ # starts grabbing expired resources and checking them.
8
+ #
9
+ def initialize
10
+ @@currently_encoding = false
11
+ puts "Initializing generic http grabber..."
12
+ EM.add_periodic_timer(5) {
13
+ check_expired_resources
14
+ }
15
+ check_expired_resources
16
+ end
17
+
18
+ # Gets all of the expired NetResources from the database and sends an HTTP
19
+ # GET requests for each one. Subscribers to a NetResource will be notified
20
+ # if it has changed since the last time it was grabbed.
21
+ #
22
+ def check_expired_resources
23
+ net_resources = ::NetResource.expired
24
+ net_resources.each do |resource|
25
+ uri = URI.parse(resource.url)
26
+ conn = HttpClient2.connect uri.host, uri.port
27
+
28
+ req = conn.get(uri.path)
29
+ req.callback{ |response|
30
+ resource.set_next_update
31
+ if resource_changed?(resource, response)
32
+ notify_subscribers(resource)
33
+ update_changed_resource(resource, response)
34
+ end
35
+ }
36
+ end
37
+ end
38
+
39
+ # Notifies each of a NetResource's subscribers that the resource has changed
40
+ # by doing an HTTP GET request to the subscriber's callback url.
41
+ #
42
+ def notify_subscribers(resource)
43
+ resource.subscriptions.each do |subscription|
44
+ uri = URI.parse(subscription.url)
45
+ conn = HttpClient2.connect uri.host, uri.port
46
+ req = conn.get(uri.path)
47
+ end
48
+ end
49
+
50
+ # Determines whether a resource has changed by comparing its saved hash
51
+ # value with the hash value of the response content.
52
+ #
53
+ def resource_changed?(resource, response)
54
+ changed = false
55
+ if response.content.hash != resource.last_modified_hash
56
+ changed = true
57
+ end
58
+ end
59
+
60
+ # Updates the resource's fields when the resource has changed. The
61
+ # last_modified_hash is set to the hash value of the response body, the
62
+ # response body itself is saved so that it can be sent to consumers during
63
+ # notifications, and the resource's last_updated time is set to the current
64
+ # time.
65
+ #
66
+ def update_changed_resource(resource, response)
67
+ resource.last_modified_hash = response.content.hash
68
+ resource.last_updated = Time.now
69
+ resource.body = response.content
70
+ resource.save
71
+ end
72
+
73
+ end
74
+ end
75
+
@@ -0,0 +1,68 @@
1
+ # Some kind of resource which we're watching for changes out on the interweb.
2
+ # Could be an RSS feed, a web page, etc.
3
+ #
4
+ class NetResource
5
+ include DataMapper::Resource
6
+
7
+ # Properties
8
+ #
9
+ property :id, Serial
10
+ property :url, String, :required => true, :length => (1..254)
11
+ property :last_modified_hash, Integer
12
+ property :update_period, Integer, :required => true, :default => 1200
13
+ property :next_update, DateTime, :required => true, :default => Time.now
14
+ property :last_updated, DateTime
15
+ property :body, Text
16
+ property :created_at, DateTime
17
+ property :updated_at, DateTime
18
+
19
+ # Associations
20
+ #
21
+ has n, :subscriptions
22
+
23
+ # Validations
24
+ #
25
+ validates_with_method :url, :method => :validate_url
26
+ validates_with_method :update_period, :method => :validate_update_period
27
+
28
+ # Checks that the url property is formatted correctly.
29
+ #
30
+ def validate_url
31
+ begin
32
+ uri = ::URI.parse(self.url)
33
+ if uri && uri.scheme == "http" || uri.scheme == "https"
34
+ return true
35
+ else
36
+ return [false, "Url must be properly formatted"]
37
+ end
38
+ end
39
+ rescue ::URI::InvalidURIError
40
+ end
41
+
42
+ # Returns an array of all resources which have a next_update time which is
43
+ # less than or equal to Time.now.
44
+ #
45
+ def self.expired
46
+ all(:next_update.lte => Time.now)
47
+ end
48
+
49
+ # Sets the next_update time equal to the current time plus the update_period
50
+ # for this resource.
51
+ #
52
+ def set_next_update
53
+ self.next_update = Time.now + update_period
54
+ self.save
55
+ end
56
+
57
+ # Ensures that the update_period is not less than 20 seconds.
58
+ #
59
+ def validate_update_period
60
+ unless self.update_period.nil? || self.update_period < 20
61
+ return true
62
+ else
63
+ [false, "Update period must be more than 20 seconds"]
64
+ end
65
+ end
66
+
67
+ end
68
+
@@ -0,0 +1,35 @@
1
+ # A url callback pointing to a web application or messaging system which wants
2
+ # to be notified about changes to a NetResource.
3
+ #
4
+ class Subscription
5
+ include DataMapper::Resource
6
+
7
+ # Properties
8
+ property :id, Serial
9
+ property :url, String, :required => true, :length => (1..254)
10
+ property :created_at, DateTime
11
+ property :updated_at, DateTime
12
+
13
+ # Associations
14
+ belongs_to :net_resource
15
+
16
+ # Validations
17
+ validates_with_method :validate_url
18
+ validates_present :net_resource
19
+
20
+ # Checks that the url property is formatted correctly.
21
+ #
22
+ def validate_url
23
+ begin
24
+ uri = ::URI.parse(self.url)
25
+ if uri && uri.scheme == "http" || uri.scheme == "https"
26
+ return true
27
+ else
28
+ return [false, "Url must be properly formatted"]
29
+ end
30
+ end
31
+ rescue ::URI::InvalidURIError
32
+ end
33
+
34
+ end
35
+