share_counts 0.0.2 → 0.0.3
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/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/lib/share_counts/caching.rb +104 -102
- data/lib/share_counts/common.rb +93 -91
- data/lib/share_counts.rb +2 -2
- data/share_counts.gemspec +1 -1
- metadata +3 -3
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/lib/share_counts/caching.rb
CHANGED
@@ -1,110 +1,112 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# Returns true if the Redis
|
6
|
-
# cache store has been initialised
|
7
|
-
#
|
8
|
-
#
|
9
|
-
def cache_enabled?
|
10
|
-
!$share_counts_cache.nil?
|
11
|
-
end
|
1
|
+
module ShareCounts
|
2
|
+
module Caching
|
12
3
|
|
4
|
+
#
|
5
|
+
#
|
6
|
+
# Returns true if the Redis
|
7
|
+
# cache store has been initialised
|
8
|
+
#
|
9
|
+
#
|
10
|
+
def cache_enabled?
|
11
|
+
!$share_counts_cache.nil?
|
12
|
+
end
|
13
13
|
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
$share_counts_cache.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# Returns the cached share counts available for each URL, in the format
|
30
|
-
#
|
31
|
-
# {
|
32
|
-
# "URL 1": {
|
33
|
-
# :reddit => N,
|
34
|
-
# :digg => N,
|
35
|
-
# :twitter => N,
|
36
|
-
# :facebook => N,
|
37
|
-
# :fblike => N,
|
38
|
-
# :linkedin => N,
|
39
|
-
# :googlebuzz => N,
|
40
|
-
# :stumbleupon => N
|
41
|
-
# },
|
42
|
-
#
|
43
|
-
# "URL 2": {
|
44
|
-
# ...
|
45
|
-
# }
|
46
|
-
# }
|
47
|
-
#
|
48
|
-
#
|
49
|
-
def cached
|
50
|
-
urls = ($share_counts_cache || {}).keys.select{|k| k =~ /^ShareCounts/ }.inject({}) do |result, key|
|
51
|
-
data = key.split("||"); network = data[1]; url = data[2];
|
52
|
-
count = from_redis("ShareCounts||#{network}||#{url}")
|
53
|
-
(result[url] ||= {})[network.to_sym] = count unless ["all", "fball"].include? network
|
54
|
-
result
|
15
|
+
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# Removes from Redis cache store all the keys
|
19
|
+
# used by ShareCounts.
|
20
|
+
#
|
21
|
+
#
|
22
|
+
def clear_cache
|
23
|
+
($share_counts_cache || {}).keys.select{|cache_key| cache_key =~ /^ShareCounts/ }.each{|cache_key|
|
24
|
+
$share_counts_cache.del cache_key}
|
55
25
|
end
|
56
|
-
urls
|
57
|
-
end
|
58
|
-
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# Enables caching with Redis.
|
62
|
-
#
|
63
|
-
# By default, it connects to 127.0.0.1:6379, but it is also
|
64
|
-
# possible to specify in the arguments :host, :port the
|
65
|
-
# connection details.
|
66
|
-
#
|
67
|
-
# If the application using this gem is already using Redis too,
|
68
|
-
# with the "redis" gem, it is possible to use the existing
|
69
|
-
# instance of Redis by either setting the :redist_store argument
|
70
|
-
# or by setting the global variable $share_counts_cache first.
|
71
|
-
#
|
72
|
-
#
|
73
|
-
def use_cache *args
|
74
|
-
arguments = args.inject({}) { |r, c| r.merge(c) }
|
75
|
-
$share_counts_cache ||= arguments[:redis_store] ||
|
76
|
-
Redis.new(:host => arguments[:host] || "127.0.0.1", :port => arguments[:port] || "6379")
|
77
|
-
end
|
78
26
|
|
79
27
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
28
|
+
#
|
29
|
+
#
|
30
|
+
# Returns the cached share counts available for each URL, in the format
|
31
|
+
#
|
32
|
+
# {
|
33
|
+
# "URL 1": {
|
34
|
+
# :reddit => N,
|
35
|
+
# :digg => N,
|
36
|
+
# :twitter => N,
|
37
|
+
# :facebook => N,
|
38
|
+
# :fblike => N,
|
39
|
+
# :linkedin => N,
|
40
|
+
# :googlebuzz => N,
|
41
|
+
# :stumbleupon => N
|
42
|
+
# },
|
43
|
+
#
|
44
|
+
# "URL 2": {
|
45
|
+
# ...
|
46
|
+
# }
|
47
|
+
# }
|
48
|
+
#
|
49
|
+
#
|
50
|
+
def cached
|
51
|
+
urls = ($share_counts_cache || {}).keys.select{|k| k =~ /^ShareCounts/ }.inject({}) do |result, key|
|
52
|
+
data = key.split("||"); network = data[1]; url = data[2];
|
53
|
+
count = from_redis("ShareCounts||#{network}||#{url}")
|
54
|
+
(result[url] ||= {})[network.to_sym] = count unless ["all", "fball"].include? network
|
55
|
+
result
|
56
|
+
end
|
57
|
+
urls
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# Enables caching with Redis.
|
63
|
+
#
|
64
|
+
# By default, it connects to 127.0.0.1:6379, but it is also
|
65
|
+
# possible to specify in the arguments :host, :port the
|
66
|
+
# connection details.
|
67
|
+
#
|
68
|
+
# If the application using this gem is already using Redis too,
|
69
|
+
# with the "redis" gem, it is possible to use the existing
|
70
|
+
# instance of Redis by either setting the :redist_store argument
|
71
|
+
# or by setting the global variable $share_counts_cache first.
|
72
|
+
#
|
73
|
+
#
|
74
|
+
def use_cache *args
|
75
|
+
arguments = args.inject({}) { |r, c| r.merge(c) }
|
76
|
+
$share_counts_cache ||= arguments[:redis_store] ||
|
77
|
+
Redis.new(:host => arguments[:host] || "127.0.0.1", :port => arguments[:port] || "6379")
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# Caches the given value in Redis under the key specified.
|
86
|
+
# By default the value is cached for two minutes, but it
|
87
|
+
# is also possible to override this expiration time by
|
88
|
+
# setting the global variable $share_counts_cache_expire
|
89
|
+
# to a number of seconds.
|
90
|
+
#
|
91
|
+
#
|
92
|
+
def to_redis(cache_key, value)
|
93
|
+
$share_counts_cache.set cache_key, Marshal.dump(value)
|
94
|
+
$share_counts_cache.expire cache_key, $share_counts_cache_expire || 120
|
95
|
+
value
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
#
|
100
|
+
#
|
101
|
+
# Retrieves the value stores in Redis under
|
102
|
+
# the given key.
|
103
|
+
#
|
104
|
+
#
|
105
|
+
def from_redis(cache_key)
|
106
|
+
value = $share_counts_cache.get(cache_key)
|
107
|
+
return if value.nil?
|
108
|
+
Marshal.load value
|
109
|
+
end
|
96
110
|
|
97
|
-
|
98
|
-
#
|
99
|
-
#
|
100
|
-
# Retrieves the value stores in Redis under
|
101
|
-
# the given key.
|
102
|
-
#
|
103
|
-
#
|
104
|
-
def from_redis(cache_key)
|
105
|
-
value = $share_counts_cache.get(cache_key)
|
106
|
-
return if value.nil?
|
107
|
-
Marshal.load value
|
108
111
|
end
|
109
|
-
|
110
|
-
end
|
112
|
+
end
|
data/lib/share_counts/common.rb
CHANGED
@@ -1,106 +1,108 @@
|
|
1
|
-
module
|
1
|
+
module ShareCounts
|
2
|
+
module Common
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
4
|
+
private
|
5
|
+
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# Given the name of one of the supported social networks and a URL,
|
9
|
+
# attempts the execution of the given block to fetch the relevant share count.
|
10
|
+
#
|
11
|
+
# If caching with Redis is enabled, it will first try to
|
12
|
+
# fetch the share count from cache instead, if there is a valid
|
13
|
+
# cached value for the combination of network/URL. When a share count is
|
14
|
+
# instead retrieved with an HTTP request to the network's API and
|
15
|
+
# the caching with Redis is enabled, the value fetched is also cached.
|
16
|
+
#
|
17
|
+
# NOTE: caching will be skipped if the block fails.
|
18
|
+
#
|
19
|
+
#
|
20
|
+
def try service, url, &block
|
21
|
+
cache_key = "ShareCounts||#{service}||#{url}"
|
22
|
+
if cache_enabled?
|
23
|
+
if result = from_redis(cache_key)
|
24
|
+
puts "Loaded #{service} count from cache"
|
25
|
+
result
|
26
|
+
else
|
27
|
+
puts "Making request to #{service}..."
|
28
|
+
to_redis(cache_key, yield)
|
29
|
+
end
|
25
30
|
else
|
26
|
-
puts "Making request to #{service}..."
|
27
|
-
|
31
|
+
puts "Redis caching is disabled - Making request to #{service}..."
|
32
|
+
yield
|
28
33
|
end
|
29
|
-
|
30
|
-
puts "
|
31
|
-
yield
|
34
|
+
rescue Exception => e
|
35
|
+
puts "Something went wrong with #{service}: #{e}"
|
32
36
|
end
|
33
|
-
rescue Exception => e
|
34
|
-
puts "Something went wrong with #{service}: #{e}"
|
35
|
-
end
|
36
37
|
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
39
|
+
#
|
40
|
+
#
|
41
|
+
# Performs an HTTP request to the given API URL with the specified params
|
42
|
+
# and within 2 seconds, and max 3 attempts
|
43
|
+
#
|
44
|
+
# If a :callback param is also specified, then it is assumed that the API
|
45
|
+
# returns a JSON text wrapped in a call to a method by that callback name,
|
46
|
+
# therefore in this case it manipulates the response to extract only
|
47
|
+
# the JSON data required.
|
48
|
+
#
|
49
|
+
def make_request *args
|
50
|
+
result = nil
|
51
|
+
attempts = 1
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
begin
|
54
|
+
timeout(2) do
|
55
|
+
url = args.shift
|
56
|
+
params = args.inject({}) { |r, c| r.merge! c }
|
57
|
+
response = RestClient.get url, { :params => params }
|
57
58
|
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
# if a callback is specified, the expected response is in the format "callback_name(JSON data)";
|
61
|
+
# with the response ending with ";" and, in some cases, "\n"
|
62
|
+
result = params.keys.include?(:callback) \
|
63
|
+
? response.gsub(/^(.*);+\n*$/, "\\1").gsub(/^#{params[:callback]}\((.*)\)$/, "\\1") \
|
64
|
+
: response
|
65
|
+
end
|
66
|
+
|
67
|
+
rescue Exception => e
|
68
|
+
puts "Failed #{attempts} attempt(s)"
|
69
|
+
attempts += 1
|
70
|
+
retry if attempts <= 3
|
64
71
|
end
|
65
|
-
|
66
|
-
|
67
|
-
puts "Failed #{attempts} attempt(s)"
|
68
|
-
attempts += 1
|
69
|
-
retry if attempts <= 3
|
72
|
+
|
73
|
+
result
|
70
74
|
end
|
71
|
-
|
72
|
-
result
|
73
|
-
end
|
74
75
|
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
77
|
+
#
|
78
|
+
#
|
79
|
+
# Makes an HTTP request with the given URL and params, and assumes
|
80
|
+
# that the response is in JSON format, therefore it returns
|
81
|
+
# the parsed JSON.
|
82
|
+
#
|
83
|
+
#
|
84
|
+
def from_json *args
|
85
|
+
JSON.parse make_request *args
|
86
|
+
end
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
88
|
+
#
|
89
|
+
#
|
90
|
+
# Most social networks' APIs returns normal JSON data;
|
91
|
+
# this method simply extracts directly the share count from
|
92
|
+
# the given JSON data, by following a pattern common to the
|
93
|
+
# structure of most JSON responses.
|
94
|
+
#
|
95
|
+
# It also requires a :selector argument that determines how
|
96
|
+
# to "query" the JSON data in a way that emulates XPATH,
|
97
|
+
# so to extract the share count.
|
98
|
+
#
|
99
|
+
#
|
100
|
+
def extract_count *args
|
101
|
+
json = args.shift
|
102
|
+
result = args.first.flatten.last.split("/").inject( json.is_a?(Array) ? json.first : json ) {
|
103
|
+
|r, c| r[c].is_a?(Array) ? r[c].first : r[c]
|
104
|
+
}
|
105
|
+
end
|
105
106
|
|
106
|
-
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/share_counts.rb
CHANGED
@@ -5,8 +5,8 @@ require File.expand_path(File.dirname(__FILE__) + "/share_counts/caching")
|
|
5
5
|
|
6
6
|
module ShareCounts
|
7
7
|
|
8
|
-
extend
|
9
|
-
extend
|
8
|
+
extend Common
|
9
|
+
extend Caching
|
10
10
|
|
11
11
|
def self.supported_networks
|
12
12
|
%w(reddit digg twitter facebook fblike linkedin googlebuzz stumbleupon)
|
data/share_counts.gemspec
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 3
|
9
|
+
version: 0.0.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Vito Botta
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-01-
|
17
|
+
date: 2011-01-31 00:00:00 +00:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|