guillotine 1.1.0 → 1.2.0
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.
- 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
|