url_tracker 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/ut ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/url_tracker'
4
+
5
+ UrlTracker::Client.new.run(ARGV)
data/bin/utd ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/url_tracker'
4
+
5
+ UrlTracker::Server.new.run(ARGV)
@@ -0,0 +1,70 @@
1
+ require 'notifier'
2
+
3
+ module UrlTracker
4
+ require_relative 'url_tracker/version'
5
+ require_relative 'url_tracker/page'
6
+ require_relative 'url_tracker/periodic'
7
+ require_relative 'url_tracker/socket_communication'
8
+ require_relative 'url_tracker/client'
9
+ require_relative 'url_tracker/server'
10
+
11
+ extend self
12
+
13
+ require 'set'
14
+
15
+ # Tracks +url+ fetching its content every +time+ seconds (defaults
16
+ # to 5*60 - 5 minutes).
17
+ def track_uri(uri, time = 5*60)
18
+ init_ivars
19
+
20
+ p = Page.new(URI(uri))
21
+
22
+ if @pages.add?(p)
23
+ @scheduler.task(uri).every(:minute) { check_change(p) }
24
+ 'ok'
25
+ else
26
+ 'error'
27
+ end
28
+ end
29
+
30
+ # Returns an array with all URIs currently being tracked
31
+ def list_all
32
+ init_ivars
33
+ uri_list = @pages.map { |page| page.uri }
34
+
35
+ uri_list.empty? ? ',' : uri_list.join(',')
36
+ end
37
+
38
+ # Stops tracking given URI
39
+ def release_uri(uri)
40
+ init_ivars
41
+
42
+ p = Page.new(URI(uri))
43
+ if @pages.include?(p)
44
+ @pages.delete(p)
45
+ @scheduler.remove_task(uri)
46
+ 'ok'
47
+ else
48
+ 'error'
49
+ end
50
+ end
51
+
52
+ # Forget about current tracked URIs
53
+ def restart
54
+ init_ivars
55
+ @pages.clear
56
+ @scheduler.restart
57
+ end
58
+
59
+ private
60
+
61
+ def check_change(page)
62
+ Notifier.notify(title: 'Change!', message: "Page #{page.uri} changed!") if page.changed?
63
+ end
64
+
65
+ def init_ivars
66
+ @pages ||= Set.new
67
+ @scheduler ||= Periodic.new
68
+ end
69
+
70
+ end
@@ -0,0 +1,88 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestClient < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ remove_socket
7
+ @server = Object.new.extend(UrlTracker::SocketCommunication)
8
+ @server.bind(socket_file)
9
+
10
+ async do
11
+ @server.wait_for_connection
12
+ end
13
+
14
+ @client = UrlTracker::Client.new(socket_file)
15
+
16
+ wait_sync
17
+ end
18
+
19
+ def teardown
20
+ @client.close_connection
21
+ @server.close_connection
22
+ end
23
+
24
+ def test_successful_track_new_url
25
+ async { @success = @client.track(fake_url) }
26
+ @server.write('ok')
27
+ wait_sync
28
+
29
+ assert_equal "track #{fake_url}", @server.next_message
30
+ assert @success
31
+ end
32
+
33
+ def test_server_could_not_track_url
34
+ async { @success = @client.track(fake_url) }
35
+ @server.write('error')
36
+ wait_sync
37
+
38
+ assert !@success
39
+ end
40
+
41
+ def test_list_urls_empty
42
+ async { @urls = @client.list }
43
+ @server.write(',')
44
+ wait_sync
45
+
46
+ assert_empty @urls
47
+ end
48
+
49
+ def test_list_single_url
50
+ async { @urls = @client.list }
51
+ @server.write(fake_url)
52
+ wait_sync
53
+
54
+ assert_equal [fake_url], @urls
55
+ end
56
+
57
+ def test_list_multiple_urls
58
+ async { @urls = @client.list }
59
+ @server.write(fake_url + ',http://foo.bar,http://google.com')
60
+ wait_sync
61
+
62
+ assert_equal [fake_url, 'http://foo.bar', 'http://google.com'], @urls
63
+ end
64
+
65
+ def test_release_tracked_url
66
+ async { @client.track(fake_url) }
67
+ @server.write('ok')
68
+ wait_sync
69
+
70
+ @server.next_message # so that we don't get the tracking request on the assertion
71
+
72
+ async { @success = @client.release(fake_url) }
73
+ @server.write('ok')
74
+ wait_sync
75
+
76
+ assert_equal "release #{fake_url}", @server.next_message
77
+ assert @success
78
+ end
79
+
80
+ def test_release_url_not_tracked
81
+ async { @success = @client.release(fake_url) }
82
+ @server.write('error')
83
+ wait_sync
84
+
85
+ assert !@success
86
+ end
87
+
88
+ end
@@ -0,0 +1,42 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+ require_relative '../lib/url_tracker'
4
+
5
+ # messages sent via sockets in the tests cannot be longer than 100 bytes
6
+ MAX_TEST_SOCK_MESSAGE = 100
7
+
8
+ puts "Url Tracker: running on Ruby version #{RUBY_VERSION}"
9
+
10
+ def assert_socket_received(message, socket)
11
+ assert_block("Expected #{socket.inspect} to receive #{message.inspect}") do
12
+ socket.recvfrom(MAX_TEST_SOCK_MESSAGE).first == message
13
+ end
14
+ end
15
+
16
+ def async(stop = false, &block)
17
+ @t = Thread.new(&block)
18
+ @t.abort_on_exception = true
19
+
20
+ while !@t.stop?; end if stop
21
+ end
22
+
23
+ def wait_sync
24
+ @t.join
25
+ end
26
+
27
+ def finish_async
28
+ @t.terminate
29
+ @t.join
30
+ end
31
+
32
+ def remove_socket
33
+ File.unlink(socket_file) if File.exists?(socket_file)
34
+ end
35
+
36
+ def socket_file
37
+ '/tmp/.dk29ei39kd3.sock'
38
+ end
39
+
40
+ def fake_url
41
+ 'http://fake.com'
42
+ end
data/test/test_page.rb ADDED
@@ -0,0 +1,52 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestPage < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @page_fetcher = MiniTest::Mock.new
7
+ @content = 'web page'
8
+
9
+ # should receive method get with argument fake_url and return @content
10
+ @page_fetcher.expect(:get, @content, [fake_url])
11
+
12
+ @page = UrlTracker::Page.new(fake_url, @page_fetcher)
13
+ end
14
+
15
+ def test_page_content
16
+ assert_equal @content, @page.content
17
+ end
18
+
19
+ def test_page_content_bang
20
+ assert_equal @content, @page.content
21
+
22
+ change_page_content('new content')
23
+ assert_equal 'new content', @page.content!
24
+ end
25
+
26
+ def test_page_changed_before_cached_content
27
+ assert !@page.changed?, 'Expected no changes when there is no cache of the page'
28
+ end
29
+
30
+ def test_page_changed_when_no_changes_happened
31
+ @page.content # we now have a cache of the page
32
+
33
+ assert !@page.changed?, 'Expected a false result when no changes occur to a page'
34
+ end
35
+
36
+ def test_page_changed_when_changes_really_happened
37
+ @page.content
38
+ change_page_content('new content')
39
+
40
+ assert @page.changed?, 'Expected Page to indicate change when its content was changed'
41
+ end
42
+
43
+ def change_page_content(new_content)
44
+ @page_fetcher.expect(:get, new_content, [fake_url])
45
+ end
46
+
47
+ def test_page_equality
48
+ other_page = UrlTracker::Page.new(fake_url)
49
+ assert_equal @page, other_page
50
+ end
51
+
52
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestPeriodic < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @p = UrlTracker::Periodic.new
7
+ end
8
+
9
+ def teardown
10
+ @p.stop
11
+ end
12
+
13
+ def test_every_with_symbol_argument
14
+ assert_equal 60, @p.every(:minute) {}
15
+ end
16
+
17
+ def test_every_with_hour_symbol
18
+ assert_equal 60*60, @p.every(:hour) {}
19
+ end
20
+
21
+ def test_two_minutes
22
+ assert_equal 2*60, @p.every(2, :minutes) {}
23
+ end
24
+
25
+ def test_two_hours
26
+ assert_equal 2*60*60, @p.every(2, :hours) {}
27
+ end
28
+
29
+ def test_invalid_first_argument
30
+ assert_raises(RuntimeError) do
31
+ @p.every(:invalid) {}
32
+ end
33
+ end
34
+
35
+ def test_invalid_time_unit
36
+ assert_raises(RuntimeError) do
37
+ @p.every(2, :invalid) {}
38
+ end
39
+ end
40
+
41
+ def test_add_named_task
42
+ @p.task("dummy").every(:minute) {}
43
+ assert_equal ["dummy"], @p.named_tasks
44
+ end
45
+
46
+ def test_remove_named_task
47
+ @p.task("dummy").every(:minute) {}
48
+ @p.remove_task("dummy")
49
+
50
+ assert_empty @p.named_tasks
51
+ end
52
+
53
+ def test_remove_inexistent_task
54
+ assert_raises(RuntimeError) do
55
+ @p.remove_task("dummy")
56
+ end
57
+ end
58
+
59
+ def test_restart
60
+ @p.stop
61
+ assert !@p.running?
62
+
63
+ @p.restart
64
+ assert @p.running?
65
+ end
66
+
67
+ def test_running
68
+ assert @p.running?, 'Expected Periodic to be running when `stop` was not called.'
69
+ end
70
+
71
+ def test_running_when_asked_to_stop
72
+ @p.stop
73
+
74
+ assert !@p.running?, 'Expected Periodic to stop'
75
+ @p.restart
76
+ end
77
+
78
+ end
@@ -0,0 +1,71 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestServer < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ remove_socket
7
+ @server = UrlTracker::Server.new(Logger.new('/dev/null'))
8
+
9
+ async(true) { @server.run(socket_file: socket_file) }
10
+
11
+ @client = UrlTracker::Client.new(socket_file) # connected!
12
+ end
13
+
14
+ def teardown
15
+ @client.close_connection
16
+ finish_async
17
+ @server.close_connection
18
+ UrlTracker.restart
19
+ end
20
+
21
+ def test_successfully_tracks_url
22
+ assert @client.track(fake_url), 'Expected to be able to track new URI'
23
+ end
24
+
25
+ def test_track_same_url
26
+ @client.track(fake_url)
27
+ restart_client
28
+
29
+ assert !@client.track(fake_url), 'Expected to return false when tracking same URI'
30
+ end
31
+
32
+ def test_list_empty
33
+ assert_equal [], @client.list
34
+ end
35
+
36
+ def test_list_with_single_uri
37
+ @client.track(fake_url)
38
+ restart_client
39
+
40
+ assert_equal [fake_url], @client.list
41
+ end
42
+
43
+ def test_list_two_uris
44
+ @client.track(fake_url)
45
+ restart_client
46
+ @client.track(fake_url + '/fake')
47
+ restart_client
48
+
49
+ assert_equal [fake_url, fake_url + '/fake'], @client.list
50
+ end
51
+
52
+ def test_release_when_tracking_nothing
53
+ assert !@client.release(fake_url), 'Expected to return false when releasing not tracked URI'
54
+ end
55
+
56
+ def test_release_with_tracked_uri
57
+ @client.track(fake_url)
58
+ restart_client
59
+ assert @client.release(fake_url), 'Expected to release tracked URI'
60
+
61
+ restart_client
62
+ assert_equal [], @client.list
63
+ end
64
+
65
+ def restart_client
66
+ @client.close_connection
67
+
68
+ @client = UrlTracker::Client.new(socket_file)
69
+ end
70
+
71
+ end
@@ -0,0 +1,89 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TestSocketCommunication < MiniTest::Unit::TestCase
4
+
5
+ def setup
6
+ @client = Object.new.extend(UrlTracker::SocketCommunication)
7
+ @server = Object.new.extend(UrlTracker::SocketCommunication)
8
+ @path = '/tmp/.dk29ei39kd3.sock'
9
+ remove_socket
10
+ end
11
+
12
+ def teardown
13
+ [@client, @server].each { |o| o.close_connection }
14
+ end
15
+
16
+ def test_connect_to_valid_socket
17
+ @server.bind(@path)
18
+ assert @client.connect(@path), 'Should connect when given a valid socket file'
19
+ end
20
+
21
+ def test_raises_invalid_socket_with_bind_and_connect
22
+ @server.bind(@path)
23
+
24
+ assert_raises(UrlTracker::SocketCommunication::InvalidSocketError) do
25
+ @server.connect(@path)
26
+ end
27
+ end
28
+
29
+ def test_raises_invalid_socket_with_connect_and_bind
30
+ @server.bind(@path)
31
+ @client.connect(@path)
32
+
33
+ assert_raises(UrlTracker::SocketCommunication::InvalidSocketError) do
34
+ @client.bind(@path)
35
+ end
36
+ end
37
+
38
+ def test_communication_client_to_server
39
+ connect_server_and_client
40
+
41
+ @client.write('message')
42
+
43
+ assert_equal 'message', @server.next_message
44
+ end
45
+
46
+ def test_communication_server_to_client
47
+ connect_server_and_client
48
+
49
+ @server.write('message')
50
+
51
+ assert_equal 'message', @client.next_message
52
+ end
53
+
54
+ def test_communication_bidirectional
55
+ connect_server_and_client
56
+
57
+ @server.write('server mesg')
58
+ @client.write('client mesg')
59
+
60
+ assert_equal 'client mesg', @server.next_message
61
+ assert_equal 'server mesg', @client.next_message
62
+ end
63
+
64
+ def test_closing_client_connection_doesnt_remove_socket_file
65
+ connect_server_and_client
66
+ @client.close_connection
67
+
68
+ assert File.exists?(@path), 'Expected socket file to exist when closing client connection'
69
+ end
70
+
71
+ def test_closing_server_connection_removes_socket_file
72
+ connect_server_and_client
73
+ @server.close_connection
74
+
75
+ assert !File.exists?(@path), 'Expected socket file to be removed when server closes connection'
76
+ end
77
+
78
+ def remove_socket
79
+ File.unlink @path if File.exists? @path
80
+ end
81
+
82
+ def connect_server_and_client
83
+ @server.bind(@path)
84
+ t = Thread.new { @server.wait_for_connection }
85
+ @client.connect(@path)
86
+ t.join # server is connected to client
87
+ end
88
+
89
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: url_tracker
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Renato Mascarenhas
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: libnotify
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: notifier
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: A simple tool for tracking URLs and getting informed when they are changed.
63
+ email: renato.mascosta@gmail.com
64
+ executables:
65
+ - ut
66
+ - utd
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - lib/url_tracker.rb
71
+ - bin/ut
72
+ - bin/utd
73
+ - test/test_server.rb
74
+ - test/test_periodic.rb
75
+ - test/test_socket_communication.rb
76
+ - test/test_client.rb
77
+ - test/test_page.rb
78
+ - test/test_helper.rb
79
+ homepage:
80
+ licenses:
81
+ - MIT
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ segments:
93
+ - 0
94
+ hash: -2248500158094718750
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ segments:
102
+ - 0
103
+ hash: -2248500158094718750
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 1.8.23
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: A simple tool for tracking URLs and getting informed when they are changed.
110
+ test_files: []