railsquest 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ require 'dnssd'
2
+
3
+ class Railsquest::Bonjour::Advertiser
4
+ def initialize
5
+ @services = []
6
+ end
7
+
8
+ def go!
9
+ register_app
10
+ register_quests
11
+ end
12
+
13
+ private
14
+ def register_app
15
+ STDOUT.puts "Registering #{Railsquest.web_uri}"
16
+ tr = DNSSD::TextRecord.new
17
+ tr["name"] = Railsquest.config.name
18
+ tr["email"] = Railsquest.config.email
19
+ tr["uri"] = Railsquest.web_uri
20
+ tr["gravatar"] = Railsquest.gravatar
21
+ tr["version"] = Railsquest::VERSION
22
+ DNSSD.register("#{Railsquest.config.name}'s railsquest", "_http._tcp,_railsquest", nil, Railsquest.web_port, tr)
23
+ end
24
+
25
+ def register_quests
26
+ loop do
27
+ stop_old_services
28
+ register_new_quests
29
+ sleep(1)
30
+ end
31
+ end
32
+
33
+ def stop_old_services
34
+ old_services.each do |old_service|
35
+ STDOUT.puts "Unregistering #{old_service.quest.uri}"
36
+ old_service.stop
37
+ @services.delete(old_service)
38
+ end
39
+ end
40
+
41
+ def old_services
42
+ @services.reject {|s| Railsquest.quests.include?(s.quest)}
43
+ end
44
+
45
+ def register_new_quests
46
+ new_quests.each do |new_quest|
47
+ STDOUT.puts "Registering #{new_quest.uri}"
48
+ tr = DNSSD::TextRecord.new
49
+ tr["name"] = new_quest.name
50
+ tr["uri"] = new_quest.uri
51
+ tr["bjour-name"] = Railsquest.config.name
52
+ tr["bjour-email"] = Railsquest.config.email
53
+ tr["bjour-uri"] = Railsquest.web_uri
54
+ tr["bjour-gravatar"] = Railsquest.gravatar
55
+ tr["bjour-version"] = Railsquest::VERSION
56
+ service = DNSSD.register(new_quest.name, "_git._tcp,_railsquest", nil, 9877, tr)
57
+ service.class.instance_eval { attr_accessor(:quest) }
58
+ service.quest = new_quest
59
+ @services << service
60
+ end
61
+ end
62
+
63
+ def new_quests
64
+ Railsquest.quests.select {|quest| !@services.any? {|s| s.quest == quest } }
65
+ end
66
+ end
@@ -0,0 +1,58 @@
1
+ require 'dnssd'
2
+
3
+ require 'thread'
4
+ require 'timeout'
5
+
6
+ Thread.abort_on_exception = true
7
+
8
+ # Generic bonjour browser
9
+ #
10
+ # Example use:
11
+ #
12
+ # browser = BonjourBrowser.new("_git._tcp,_railsquest")
13
+ # loop do
14
+ # sleep(1)
15
+ # pp browser.replies.map {|r| r.name}
16
+ # end
17
+ #
18
+ # Probably gem-worthy
19
+ class Railsquest::Bonjour::Browser
20
+ def initialize(service)
21
+ @service = service
22
+ @mutex = Mutex.new
23
+ @replies = {}
24
+ watch!
25
+ end
26
+
27
+ def replies
28
+ @mutex.synchronize { @replies.values }
29
+ end
30
+
31
+ private
32
+ def watch!
33
+ Thread.new(@service, @mutex, @replies) do |service, mutex, replies|
34
+ begin
35
+ DNSSD.browse!(service) do |reply|
36
+ Thread.new(reply, replies, mutex) do |reply, replies, mutex|
37
+ begin
38
+ DNSSD.resolve!(reply.name, reply.type, reply.domain) do |resolve_reply|
39
+ mutex.synchronize do
40
+ if reply.flags.add?
41
+ replies[reply.fullname] = resolve_reply
42
+ else
43
+ replies.delete(reply.fullname)
44
+ end
45
+ end
46
+ resolve_reply.service.stop unless resolve_reply.service.stopped?
47
+ end
48
+ rescue DNSSD::BadParamError
49
+ # Ignore em
50
+ end
51
+ end
52
+ end
53
+ rescue DNSSD::BadParamError
54
+ # Ignore em
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,25 @@
1
+ class Railsquest::Bonjour::Person
2
+
3
+ attr_accessor :name, :email, :uri , :gravatar
4
+
5
+ def initialize(name, email, uri, gravatar)
6
+ @name, @email, @uri, @gravatar = name, email, uri, gravatar
7
+ end
8
+
9
+ def ==(other)
10
+ self.uri == other.uri
11
+ end
12
+
13
+ def hash
14
+ to_hash.hash
15
+ end
16
+
17
+ def to_hash
18
+ {"name" => name, "email" => email, "uri" => uri, "gravatar" => gravatar}
19
+ end
20
+
21
+ def host_name
22
+ uri.gsub(/http:\/\//, '').gsub(/:9876/, '')
23
+ end
24
+
25
+ end
@@ -0,0 +1,39 @@
1
+ class Railsquest::Bonjour::Quest
2
+
3
+ attr_accessor :name, :uri, :person
4
+
5
+ def initialize(name, uri, person)
6
+ @name, @uri, @person = name, uri, person
7
+ end
8
+
9
+ def html_id
10
+ Railsquest::Quest.html_id(name)
11
+ end
12
+
13
+ def ==(other)
14
+ self.uri == other.uri
15
+ end
16
+
17
+ def hash
18
+ to_hash.hash
19
+ end
20
+
21
+ def json_uri
22
+ "#{person.uri}#{name}.json"
23
+ end
24
+
25
+ def web_uri
26
+ "#{person.uri}##{html_id}"
27
+ end
28
+
29
+ def to_hash
30
+ {
31
+ "name" => name,
32
+ "uri" => uri,
33
+ "json_uri" => json_uri,
34
+ "web_uri" => web_uri,
35
+ "person" => person.to_hash
36
+ }
37
+ end
38
+
39
+ end
@@ -0,0 +1,36 @@
1
+ module Railsquest::Bonjour
2
+ class QuestBrowser
3
+
4
+ def initialize
5
+ @browser = Browser.new('_git._tcp,_railsquest')
6
+ end
7
+
8
+ def quests
9
+ @browser.replies.map do |reply|
10
+ Quest.new(
11
+ reply.text_record["name"],
12
+ reply.text_record["uri"],
13
+ Person.new(
14
+ reply.text_record["bjour-name"],
15
+ reply.text_record["bjour-email"],
16
+ reply.text_record["bjour-uri"],
17
+ reply.text_record["bjour-gravatar"]
18
+ )
19
+ )
20
+ end
21
+ end
22
+
23
+ def other_quests
24
+ quests.reject {|r| Railsquest.quests.any? {|my_quest| my_quest.name == r.name}}
25
+ end
26
+
27
+ def quests_similar_to(quest)
28
+ quests.select {|r| r.name == quest.name}
29
+ end
30
+
31
+ def quests_for(person)
32
+ quests.select {|r| r.person == person}
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ module Railsquest::Bonjour
2
+ class RailsquestBrowser
3
+
4
+ def initialize
5
+ @browser = Browser.new('_http._tcp,_railsquest')
6
+ end
7
+
8
+ def railsquests
9
+ @browser.replies.map do |reply|
10
+ Person.new(
11
+ reply.text_record["name"],
12
+ reply.text_record["email"],
13
+ reply.text_record["uri"],
14
+ reply.text_record["gravatar"]
15
+ )
16
+ end
17
+ end
18
+
19
+ def other_railsquests
20
+ railsquests.reject {|b| b.uri == Railsquest.web_uri}
21
+ end
22
+
23
+ def all_railsquests
24
+ railsquests
25
+ end
26
+
27
+ def find_person(hostname)
28
+ railsquests.find { |u| u.uri =~ /hostname/}
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,53 @@
1
+ require 'rainbow'
2
+ require 'pathname'
3
+
4
+ module Railsquest::Commands
5
+
6
+ # Start sinatra app.
7
+ def serve_web!
8
+ puts "* Starting " + web_uri.foreground(:yellow)
9
+ fork do
10
+ ENV["RACK_ENV"] ||= "production"
11
+ require "railsquest/../../sinatra/app"
12
+ Sinatra::Application.set :port, web_port
13
+ Sinatra::Application.set :server, "thin"
14
+ Sinatra::Application.run!
15
+ end
16
+ end
17
+
18
+ def advertise!
19
+ fork { Railsquest::Bonjour::Advertiser.new.go! }
20
+ end
21
+
22
+ def add!(port, name = nil)
23
+
24
+ if name.nil?
25
+ default_name = "My Awesome Quest"
26
+ print "Quest Name?".foreground(:yellow) + " [#{default_name}] "
27
+ name = (STDIN.gets || "").strip
28
+ name = default_name if name.empty?
29
+ end
30
+
31
+ quest = Railsquest::Quest.for_name(name)
32
+ quest.port = port
33
+
34
+ if quest.exist?
35
+ abort "You've already a quest #{quest}."
36
+ end
37
+
38
+ quest.init!
39
+
40
+ puts init_success_message(quest.dirname)
41
+
42
+ quest
43
+ end
44
+
45
+ def init_success_message(quest_dirname)
46
+ plain_init_success_message(quest_dirname)
47
+ end
48
+
49
+ def plain_init_success_message(quest_dirname)
50
+ "Railsquest quest #{quest_dirname} created."
51
+ end
52
+
53
+ end
@@ -0,0 +1,11 @@
1
+ Grit::Commit.class_eval do
2
+ def ==(other)
3
+ self.id == other.id
4
+ end
5
+ end
6
+
7
+ Grit::Actor.class_eval do
8
+ def gravatar_uri
9
+ Railsquest.gravatar_uri(email)
10
+ end
11
+ end
@@ -0,0 +1,100 @@
1
+ require 'digest/md5'
2
+
3
+ module Railsquest
4
+ module GravatarHelpers
5
+ def gravatar
6
+ gravatar_uri(self.config.email)
7
+ end
8
+ def gravatar_uri(email)
9
+ "http://gravatar.com/avatar/#{Digest::MD5.hexdigest(email)}.png"
10
+ end
11
+ end
12
+
13
+ # Lifted from Rails
14
+ module DateHelpers
15
+ # Reports the approximate distance in time between two Time or Date objects or integers as seconds.
16
+ # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
17
+ # Distances are questrted based on the following table:
18
+ #
19
+ # 0 <-> 29 secs # => less than a minute
20
+ # 30 secs <-> 1 min, 29 secs # => 1 minute
21
+ # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
22
+ # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
23
+ # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
24
+ # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
25
+ # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
26
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
27
+ # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
28
+ # 1 yr <-> 2 yrs minus 1 secs # => about 1 year
29
+ # 2 yrs <-> max time or date # => over [2..X] years
30
+ #
31
+ # With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds:
32
+ # 0-4 secs # => less than 5 seconds
33
+ # 5-9 secs # => less than 10 seconds
34
+ # 10-19 secs # => less than 20 seconds
35
+ # 20-39 secs # => half a minute
36
+ # 40-59 secs # => less than a minute
37
+ # 60-89 secs # => 1 minute
38
+ #
39
+ # ==== Examples
40
+ # from_time = Time.now
41
+ # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
42
+ # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
43
+ # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
44
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
45
+ # distance_of_time_in_words(from_time, 3.years.from_now) # => over 3 years
46
+ # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
47
+ # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
48
+ # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
49
+ # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
50
+ # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
51
+ # distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
52
+ #
53
+ # to_time = Time.now + 6.years + 19.days
54
+ # distance_of_time_in_words(from_time, to_time, true) # => over 6 years
55
+ # distance_of_time_in_words(to_time, from_time, true) # => over 6 years
56
+ # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
57
+ #
58
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
59
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
60
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
61
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
62
+ distance_in_seconds = ((to_time - from_time).abs).round
63
+
64
+ case distance_in_minutes
65
+ when 0..1
66
+ return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
67
+ case distance_in_seconds
68
+ when 0..4 then 'less than 5 seconds'
69
+ when 5..9 then 'less than 10 seconds'
70
+ when 10..19 then 'less than 20 seconds'
71
+ when 20..39 then 'half a minute'
72
+ when 40..59 then 'less than a minute'
73
+ else '1 minute'
74
+ end
75
+
76
+ when 2..44 then "#{distance_in_minutes} minutes"
77
+ when 45..89 then 'about 1 hour'
78
+ when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
79
+ when 1440..2879 then '1 day'
80
+ when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
81
+ when 43200..86399 then 'about 1 month'
82
+ when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
83
+ when 525600..1051199 then 'about 1 year'
84
+ else "over #{(distance_in_minutes / 525600).round} years"
85
+ end
86
+ end
87
+
88
+ # Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
89
+ #
90
+ # ==== Examples
91
+ # time_ago_in_words(3.minutes.from_now) # => 3 minutes
92
+ # time_ago_in_words(Time.now - 15.hours) # => 15 hours
93
+ # time_ago_in_words(Time.now) # => less than a minute
94
+ #
95
+ # from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days
96
+ def time_ago_in_words(from_time, include_seconds = false)
97
+ distance_of_time_in_words(from_time, Time.now, include_seconds)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,86 @@
1
+ require 'grit'
2
+ require 'pathname'
3
+ require 'json'
4
+
5
+ module Railsquest
6
+ class Quest
7
+
8
+ attr_accessor :name, :port
9
+ attr_reader :secret
10
+
11
+ def self.for_name(name)
12
+ n = name.gsub(/[^A-Za-z-]+/, '_')
13
+ q = new(Railsquest.quests_path.join(n + ".quest"))
14
+ q.name = n
15
+ q
16
+ end
17
+
18
+ def self.html_id(name)
19
+ name.gsub(/[^A-Za-z-]+/, '').downcase
20
+ end
21
+
22
+ def initialize(path)
23
+ @path = Pathname(path)
24
+ end
25
+
26
+ def ==(other)
27
+ other.respond_to?(:path) && self.path == other.path
28
+ end
29
+
30
+ attr_reader :path
31
+
32
+ def exist?
33
+ path.exist?
34
+ end
35
+
36
+ def init!
37
+ secret = `uuidgen`.strip
38
+ contents = '{\"secret\" : \"' + secret + '\", \"port\" : ' + port + '}'
39
+ Dir.chdir(Railsquest.quests_path) { `echo "#{contents}" >> #{path}` }
40
+ end
41
+
42
+ def uri
43
+ Railsquest.quest_uri.gsub(/\/$/, '') + ':' + File.open(path) { |f| JSON.parse(f.gets)["port"].to_s }
44
+ end
45
+
46
+ def secret
47
+ File.open(path) { |f| JSON.parse(f.gets)["secret"] }
48
+ end
49
+
50
+ def name
51
+ dirname.sub(".quest",'')
52
+ end
53
+
54
+ def html_id
55
+ self.class.html_id(name)
56
+ end
57
+
58
+ def dirname
59
+ path.split.last.to_s
60
+ end
61
+
62
+ def to_s
63
+ name
64
+ end
65
+
66
+ def web_uri
67
+ Railsquest.web_uri + "#" + html_id
68
+ end
69
+
70
+ def attempts
71
+
72
+ end
73
+
74
+ def remove!
75
+ path.rmtree
76
+ end
77
+
78
+ def to_hash
79
+ {
80
+ "name" => name,
81
+ "uri" => uri,
82
+ "host_name" => Railsquest.host_name
83
+ }
84
+ end
85
+ end
86
+ end