guillotine 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +12 -0
- data/README.md +51 -7
- data/guillotine.gemspec +5 -2
- data/lib/guillotine.rb +55 -40
- data/lib/guillotine/adapters/active_record_adapter.rb +62 -64
- data/lib/guillotine/adapters/memory_adapter.rb +53 -51
- data/lib/guillotine/adapters/mongo_adapter.rb +56 -58
- data/lib/guillotine/adapters/redis_adapter.rb +58 -52
- data/lib/guillotine/adapters/riak_adapter.rb +107 -109
- data/lib/guillotine/adapters/sequel_adapter.rb +61 -63
- data/lib/guillotine/host_checkers.rb +87 -0
- data/lib/guillotine/service.rb +6 -51
- data/test/active_record_adapter_test.rb +1 -1
- data/test/app_test.rb +1 -1
- data/test/host_checker_test.rb +114 -0
- data/test/memory_adapter_test.rb +1 -1
- data/test/mongo_adapter_test.rb +1 -1
- data/test/redis_adapter_test.rb +1 -1
- data/test/riak_adapter_test.rb +3 -3
- data/test/sequel_adapter_test.rb +1 -1
- data/test/service_test.rb +1 -1
- metadata +12 -11
@@ -1,70 +1,68 @@
|
|
1
1
|
require 'mongo'
|
2
2
|
|
3
3
|
module Guillotine
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@collection.ensure_index([['url', Mongo::ASCENDING]])
|
4
|
+
class MongoAdapter < Adapter
|
5
|
+
def initialize(collection)
|
6
|
+
@collection = collection
|
7
|
+
@collection.ensure_index([['url', Mongo::ASCENDING]])
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# Public: Stores the shortened version of a URL.
|
18
|
-
#
|
19
|
-
# url - The String URL to shorten and store.
|
20
|
-
# code - Optional String code for the URL.
|
21
|
-
#
|
22
|
-
# Returns the unique String code for the URL. If the URL is added
|
23
|
-
# multiple times, this should return the same code.
|
24
|
-
def add(url, code = nil)
|
25
|
-
code_for(url) || insert(url, code || shorten(url))
|
26
|
-
end
|
9
|
+
# \m/
|
10
|
+
@transformers = {
|
11
|
+
:url => lambda { |doc| doc['url'] },
|
12
|
+
:code => lambda { |doc| doc['_id'] }
|
13
|
+
}
|
14
|
+
end
|
27
15
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
16
|
+
# Public: Stores the shortened version of a URL.
|
17
|
+
#
|
18
|
+
# url - The String URL to shorten and store.
|
19
|
+
# code - Optional String code for the URL.
|
20
|
+
#
|
21
|
+
# Returns the unique String code for the URL. If the URL is added
|
22
|
+
# multiple times, this should return the same code.
|
23
|
+
def add(url, code = nil)
|
24
|
+
code_for(url) || insert(url, code || shorten(url))
|
25
|
+
end
|
37
26
|
|
38
|
-
# Public: Retrieves the code for a given URL.
|
39
|
-
#
|
40
|
-
# url - The String URL to lookup.
|
41
|
-
#
|
42
|
-
# Returns the String code, or nil if none is found.
|
43
|
-
def code_for(url)
|
44
|
-
select :code, :url => url
|
45
|
-
end
|
46
27
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
28
|
+
# Public: Retrieves a URL from the code.
|
29
|
+
#
|
30
|
+
# code - The String code to lookup the URL.
|
31
|
+
#
|
32
|
+
# Returns the String URL, or nil if none is found.
|
33
|
+
def find(code)
|
34
|
+
select :url, :_id => code
|
35
|
+
end
|
55
36
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
37
|
+
# Public: Retrieves the code for a given URL.
|
38
|
+
#
|
39
|
+
# url - The String URL to lookup.
|
40
|
+
#
|
41
|
+
# Returns the String code, or nil if none is found.
|
42
|
+
def code_for(url)
|
43
|
+
select :code, :url => url
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Removes the assigned short code for a URL.
|
47
|
+
#
|
48
|
+
# url - The String URL to remove.
|
49
|
+
#
|
50
|
+
# Returns nothing.
|
51
|
+
def clear(url)
|
52
|
+
@collection.remove(:url => url)
|
53
|
+
end
|
54
|
+
|
55
|
+
def select(field, query)
|
56
|
+
@collection.find_one(query, {:transformer => @transformers[field]})
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def insert(url, code)
|
61
|
+
if existing_url = find(code)
|
62
|
+
raise DuplicateCodeError.new(existing_url, url, code) if url != existing_url
|
67
63
|
end
|
64
|
+
@collection.insert(:_id => code, :url => url)
|
65
|
+
code
|
68
66
|
end
|
69
67
|
end
|
70
68
|
end
|
@@ -1,63 +1,69 @@
|
|
1
1
|
module Guillotine
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
2
|
+
class RedisAdapter < Adapter
|
3
|
+
# Public: Initialise the adapter with a Redis instance.
|
4
|
+
#
|
5
|
+
# redis - A Redis instance to persist urls and codes to.
|
6
|
+
def initialize(redis)
|
7
|
+
@redis = redis
|
8
|
+
end
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
@redis.set "guillotine:hash:#{code}", url
|
27
|
-
@redis.set "guillotine:urls:#{url}", code
|
28
|
-
code
|
10
|
+
# Public: Stores the shortened version of a URL.
|
11
|
+
#
|
12
|
+
# url - The String URL to shorten and store.
|
13
|
+
# code - Optional String code for the URL.
|
14
|
+
#
|
15
|
+
# Returns the unique String code for the URL. If the URL is added
|
16
|
+
# multiple times, this should return the same code.
|
17
|
+
def add(url, code = nil)
|
18
|
+
if existing_code = @redis.get(url_key(url))
|
19
|
+
existing_code
|
20
|
+
else
|
21
|
+
code ||= shorten(url)
|
22
|
+
if existing_url = @redis.get(code_key(code))
|
23
|
+
raise DuplicateCodeError.new(existing_url, url, code) if url != existing_url
|
29
24
|
end
|
25
|
+
@redis.set code_key(code), url
|
26
|
+
@redis.set url_key(url), code
|
27
|
+
code
|
30
28
|
end
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
31
|
+
# Public: Retrieves a URL from the code.
|
32
|
+
#
|
33
|
+
# code - The String code to lookup the URL.
|
34
|
+
#
|
35
|
+
# Returns the String URL, or nil if none is found.
|
36
|
+
def find(code)
|
37
|
+
@redis.get code_key(code)
|
38
|
+
end
|
40
39
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
40
|
+
# Public: Retrieves the code for a given URL.
|
41
|
+
#
|
42
|
+
# url - The String URL to lookup.
|
43
|
+
#
|
44
|
+
# Returns the String code, or nil if none is found.
|
45
|
+
def code_for(url)
|
46
|
+
@redis.get url_key(url)
|
47
|
+
end
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
49
|
+
# Public: Removes the assigned short code for a URL.
|
50
|
+
#
|
51
|
+
# url - The String URL to remove.
|
52
|
+
#
|
53
|
+
# Returns nothing.
|
54
|
+
def clear(url)
|
55
|
+
if code = @redis.get(url_key(url))
|
56
|
+
@redis.del url_key(url)
|
57
|
+
@redis.del code_key(code)
|
60
58
|
end
|
61
59
|
end
|
60
|
+
|
61
|
+
def code_key(code)
|
62
|
+
"guillotine:hash:#{code}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def url_key(url)
|
66
|
+
"guillotine:urls:#{url}"
|
67
|
+
end
|
62
68
|
end
|
63
69
|
end
|
@@ -1,134 +1,132 @@
|
|
1
1
|
require 'digest/sha1'
|
2
2
|
|
3
3
|
module Guillotine
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader :code_bucket, :url_bucket
|
4
|
+
# Stores shortened URLs in Riak. Totally scales.
|
5
|
+
class RiakAdapter < Adapter
|
6
|
+
PLAIN = 'text/plain'.freeze
|
7
|
+
attr_reader :code_bucket, :url_bucket
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
# Initializes the adapter.
|
10
|
+
#
|
11
|
+
# code_bucket - The Riak::Bucket for all code keys.
|
12
|
+
# url_bucket - The Riak::Bucket for all url keys. If this is not
|
13
|
+
# given, the code bucket is used for all keys.
|
14
|
+
def initialize(code_bucket, url_bucket = nil)
|
15
|
+
@code_bucket = code_bucket
|
16
|
+
@url_bucket = url_bucket || @code_bucket
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Stores the shortened version of a URL.
|
20
|
+
#
|
21
|
+
# url - The String URL to shorten and store.
|
22
|
+
# code - Optional String code for the URL.
|
23
|
+
#
|
24
|
+
# Returns the unique String code for the URL. If the URL is added
|
25
|
+
# multiple times, this should return the same code.
|
26
|
+
def add(url, code = nil)
|
27
|
+
sha = url_key url
|
28
|
+
url_obj = @url_bucket.get_or_new sha, :r => 1
|
29
|
+
if url_obj.raw_data
|
30
|
+
fix_url_object(url_obj)
|
31
|
+
code = url_obj.data
|
18
32
|
end
|
19
33
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# code - Optional String code for the URL.
|
24
|
-
#
|
25
|
-
# Returns the unique String code for the URL. If the URL is added
|
26
|
-
# multiple times, this should return the same code.
|
27
|
-
def add(url, code = nil)
|
28
|
-
sha = url_key url
|
29
|
-
url_obj = @url_bucket.get_or_new sha, :r => 1
|
30
|
-
if url_obj.raw_data
|
31
|
-
fix_url_object(url_obj)
|
32
|
-
code = url_obj.data
|
33
|
-
end
|
34
|
+
code ||= shorten url
|
35
|
+
code_obj = @code_bucket.get_or_new code
|
36
|
+
code_obj.content_type = url_obj.content_type = PLAIN
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
+
if existing_url = code_obj.data # key exists
|
39
|
+
raise DuplicateCodeError.new(existing_url, url, code) if url_key(existing_url) != sha
|
40
|
+
end
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
+
if !url_obj.data # unsaved
|
43
|
+
url_obj.data = code
|
44
|
+
url_obj.store
|
45
|
+
end
|
42
46
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
code_obj.data = url
|
48
|
+
code_obj.store
|
49
|
+
code
|
50
|
+
end
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
# Public: Retrieves a URL from the code.
|
53
|
+
#
|
54
|
+
# code - The String code to lookup the URL.
|
55
|
+
#
|
56
|
+
# Returns the String URL.
|
57
|
+
def find(code)
|
58
|
+
if obj = url_object(code)
|
59
|
+
obj.data
|
51
60
|
end
|
61
|
+
end
|
52
62
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
63
|
+
# Public: Retrieves the code for a given URL.
|
64
|
+
#
|
65
|
+
# url - The String URL to lookup.
|
66
|
+
#
|
67
|
+
# Returns the String code, or nil if none is found.
|
68
|
+
def code_for(url)
|
69
|
+
if obj = code_object(url)
|
70
|
+
obj.data
|
62
71
|
end
|
72
|
+
end
|
63
73
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
74
|
+
# Public: Removes the assigned short code for a URL.
|
75
|
+
#
|
76
|
+
# url - The String URL to remove.
|
77
|
+
#
|
78
|
+
# Returns nothing.
|
79
|
+
def clear(url)
|
80
|
+
if code_obj = code_object(url)
|
81
|
+
@url_bucket.delete code_obj.key
|
82
|
+
@code_bucket.delete code_obj.data
|
73
83
|
end
|
84
|
+
end
|
74
85
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
86
|
+
# Retrieves a URL riak value from the code.
|
87
|
+
#
|
88
|
+
# code - The String code to lookup the URL.
|
89
|
+
#
|
90
|
+
# Returns a Riak::RObject, or nil if none is found.
|
91
|
+
def url_object(code)
|
92
|
+
@code_bucket.get(code, :r => 1)
|
93
|
+
rescue Riak::FailedRequest => err
|
94
|
+
raise unless err.not_found?
|
95
|
+
end
|
86
96
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
# Retrieves the code riak value for a given URL.
|
98
|
+
#
|
99
|
+
# url - The String URL to lookup.
|
100
|
+
#
|
101
|
+
# Returns a Riak::RObject, or nil if none is found.
|
102
|
+
def code_object(url)
|
103
|
+
sha = url_key url
|
104
|
+
if o = @url_bucket.get(sha, :r => 1)
|
105
|
+
fix_url_object(o)
|
96
106
|
end
|
107
|
+
rescue Riak::FailedRequest => err
|
108
|
+
raise unless err.not_found?
|
109
|
+
end
|
97
110
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
108
|
-
rescue Riak::FailedRequest => err
|
109
|
-
raise unless err.not_found?
|
111
|
+
# Fixes a bug in Guillotine 1.0.2 where the content type on url objects
|
112
|
+
# was not being set. The ruby Riak::Client defaults to JSON, so
|
113
|
+
# strings were being saved as "somecode", which is unparseable by JSON.
|
114
|
+
def fix_url_object(obj, data = nil)
|
115
|
+
if data
|
116
|
+
obj.content_type = PLAIN
|
117
|
+
obj.data = data
|
118
|
+
obj.store
|
119
|
+
return obj
|
110
120
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def fix_url_object(obj, data = nil)
|
116
|
-
if data
|
117
|
-
obj.content_type = PLAIN
|
118
|
-
obj.data = data
|
119
|
-
obj.store
|
120
|
-
return obj
|
121
|
-
end
|
122
|
-
case obj.content_type
|
123
|
-
when /json/ then fix_url_object(obj, JSON.parse(%({"data":#{obj.raw_data}}))['data'])
|
124
|
-
when PLAIN then obj
|
125
|
-
else fix_url_object(obj, obj.data) # old values had the right data but the content type was application/x-www-form-urlencoded
|
126
|
-
end
|
121
|
+
case obj.content_type
|
122
|
+
when /json/ then fix_url_object(obj, JSON.parse(%({"data":#{obj.raw_data}}))['data'])
|
123
|
+
when PLAIN then obj
|
124
|
+
else fix_url_object(obj, obj.data) # old values had the right data but the content type was application/x-www-form-urlencoded
|
127
125
|
end
|
126
|
+
end
|
128
127
|
|
129
|
-
|
130
|
-
|
131
|
-
end
|
128
|
+
def url_key(url)
|
129
|
+
Digest::SHA1.hexdigest url.downcase
|
132
130
|
end
|
133
131
|
end
|
134
132
|
end
|