octopus 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+