rattlecache 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +2 -0
- data/Rakefile +9 -0
- data/lib/backend_manager.rb +49 -0
- data/lib/backends/filesystem.rb +55 -0
- data/lib/caches/Auctionscache.rb +79 -0
- data/lib/caches/Fieldsrequestcache.rb +73 -0
- data/lib/rattlecache.rb +144 -0
- data/rattlecache.gemspec +21 -0
- metadata +85 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Marv Cool
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
6
|
+
t.rspec_opts = ['--color', '-f progress', '-r ./spec/spec_helper.rb']
|
7
|
+
t.pattern = 'spec/**/*_spec.rb'
|
8
|
+
t.fail_on_error = false
|
9
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rattlecache
|
2
|
+
module Backend
|
3
|
+
|
4
|
+
class InvalidBackend < Exception; end
|
5
|
+
|
6
|
+
extend self
|
7
|
+
|
8
|
+
attr_reader :backends
|
9
|
+
|
10
|
+
@backends = {
|
11
|
+
:filesystem => "Filesystem"
|
12
|
+
#:redis => "Redis",
|
13
|
+
#:somOtherCoolBackend => "someOther"
|
14
|
+
}
|
15
|
+
|
16
|
+
def fetch(backend_name)
|
17
|
+
unless @backends.include?(backend_name)
|
18
|
+
raise InvalidBackend.new("#{backend_name.to_s} is not a valid backend!")
|
19
|
+
end
|
20
|
+
|
21
|
+
backend_class = @backends[backend_name]
|
22
|
+
return load_backend(backend_name, backend_class)
|
23
|
+
end
|
24
|
+
|
25
|
+
def register(identifier, klass)
|
26
|
+
@backends[identifier] = klass
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def load_backend(backend_name, klass_name)
|
32
|
+
begin
|
33
|
+
klass = Rattlecache::Backend.const_get("#{klass_name}", false)
|
34
|
+
rescue NameError
|
35
|
+
begin
|
36
|
+
backend_file = "backends/#{backend_name.to_s}"
|
37
|
+
require backend_file
|
38
|
+
klass = Rattlecache::Backend.const_get("#{klass_name}", false)
|
39
|
+
rescue LoadError
|
40
|
+
raise InvalidBackend.new("backend #{klass_name} does not exist, and file #{backend_file} does not exist")
|
41
|
+
rescue NameError
|
42
|
+
raise InvalidBackend.new("expected #{backend_file} to define Rattlecache::Backend::#{klass_name}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
return klass.new
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Rattlecache
|
5
|
+
module Backend
|
6
|
+
class Filesystem < Cache
|
7
|
+
|
8
|
+
def initialize(prefix = "/tmp/rattlecache/")
|
9
|
+
@prefix = prefix
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(objectKey)
|
13
|
+
#puts "Filesystem gets you: #{objectKey}"
|
14
|
+
# get the file from the filesystem
|
15
|
+
ret = {:status => 404, :lastModified => nil, :object => nil}
|
16
|
+
begin
|
17
|
+
f = open_file(objectKey)
|
18
|
+
firstline = f.first
|
19
|
+
object = f.read
|
20
|
+
mtime = f.mtime
|
21
|
+
f.close
|
22
|
+
ret = {:status => 200,
|
23
|
+
:object => object,
|
24
|
+
:lastModified => mtime,
|
25
|
+
:header => Base64.strict_decode64(firstline.chomp)
|
26
|
+
}
|
27
|
+
rescue Errno::ENOENT
|
28
|
+
end
|
29
|
+
return ret
|
30
|
+
end
|
31
|
+
|
32
|
+
def post(object)
|
33
|
+
# put the file to the filesysten
|
34
|
+
firstline = Base64.strict_encode64(object[:header].to_json)
|
35
|
+
#puts "Debug: putting headers as firstline: #{firstline}"
|
36
|
+
f = open_file(object[:key],"w")
|
37
|
+
f.puts(firstline)
|
38
|
+
f.puts(object[:data])
|
39
|
+
f.close
|
40
|
+
#puts "Debug: filesystem posted #{object[:key]}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def open_file(objectKey,how="r")
|
44
|
+
begin
|
45
|
+
Dir.mkdir(@prefix) unless File.directory?(@prefix)
|
46
|
+
File.open(@prefix+objectKey,how)
|
47
|
+
rescue
|
48
|
+
# raise this to the caller
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Rattlecache
|
2
|
+
class Auctionscache < Cache
|
3
|
+
|
4
|
+
|
5
|
+
# @param backend [Rattlecache::Backend]
|
6
|
+
# @param adapter [Battlenet::Adapter::AbstractAdapter]
|
7
|
+
def initialize(backend, adapter)
|
8
|
+
@adapter = adapter
|
9
|
+
@backend = backend
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param url [String]
|
13
|
+
# @return [String|nil]
|
14
|
+
def get(url,header)
|
15
|
+
|
16
|
+
cached_data = @backend.get(sanitize(url))
|
17
|
+
|
18
|
+
# Check if any fields are requested.
|
19
|
+
# If yes, request without fields and look into ['lastModified']
|
20
|
+
# if we need to fetch new or answer with our cached result.",
|
21
|
+
if cached_data[:status] == 200
|
22
|
+
puts "Debug: Auctionscache: cache HIT for #{url}"
|
23
|
+
unless is_lmt_and_url_request(url)
|
24
|
+
puts "Debug: Auctionscache: has fields. Timethings: #{cached_data[:lastModified]} < #{get_lmt(url,header)} #{cached_data[:lastModified] < get_lmt(url,header)}"
|
25
|
+
if cached_data[:lastModified] < get_lmt(url,header)
|
26
|
+
cached_data = {:status => 404}
|
27
|
+
end
|
28
|
+
else
|
29
|
+
# if a guild is requested without additional fields,
|
30
|
+
# simply check if this file is still valid
|
31
|
+
if generic_needs_request?(cached_data[:header],cached_data[:lastModified])
|
32
|
+
cached_data = {:status => 404}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
puts "Debug: Auctionscache: cache MISS for #{url}"
|
37
|
+
end
|
38
|
+
cached_data
|
39
|
+
end
|
40
|
+
|
41
|
+
# check if the supplied url is not pointing to a .json auctions file,
|
42
|
+
# but to a generic url for lmt and the .json url
|
43
|
+
def is_lmt_and_url_request(url)
|
44
|
+
not url.include?(".json")
|
45
|
+
end
|
46
|
+
|
47
|
+
# method to get the lastModified info for a specified url
|
48
|
+
# @param url [String] the url for which the LMT is wanted
|
49
|
+
# @param header [Hash] the Hash of headers for a api request
|
50
|
+
def get_lmt(url,header)
|
51
|
+
cached_data = @backend.get(sanitize(generic_auctions_url(url)))
|
52
|
+
puts "Debug: get_lmt() cache result for generic url: #{cached_data[:status]}"
|
53
|
+
# if the object was not in the cache or it is expired
|
54
|
+
if cached_data[:status] != 200 or generic_needs_request?(cached_data[:header],cached_data[:lastModified])
|
55
|
+
# request it from the api
|
56
|
+
request_generic(url,header)
|
57
|
+
# and load the new object
|
58
|
+
cached_data = @backend.get(sanitize(generic_auctions_url(url)))
|
59
|
+
end
|
60
|
+
# this a JS milliseconds timestamp, we only do seconds!
|
61
|
+
Time.at(JSON.parse(cached_data[:object])["lastModified"]/1000)
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_generic(url,header)
|
65
|
+
# need to request the generic guild response
|
66
|
+
url = generic_auctions_url(url)
|
67
|
+
# request it from the API:
|
68
|
+
got = request_raw(url,header)
|
69
|
+
@backend.post({:key => sanitize(url),:header => got.header.to_hash, :data => got.body}) # and put into cache
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def generic_auctions_url(url)
|
74
|
+
u = URI.parse(url)
|
75
|
+
u.path=(u.path.gsub("-data","/data").gsub(/\/auctions.*\.json$/,""))
|
76
|
+
u.to_s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Rattlecache
|
2
|
+
class Fieldsrequestcache < Cache
|
3
|
+
|
4
|
+
|
5
|
+
# @param backend [Rattlecache::Backend]
|
6
|
+
# @param adapter [Battlenet::Adapter::AbstractAdapter]
|
7
|
+
def initialize(backend, adapter)
|
8
|
+
@adapter = adapter
|
9
|
+
@backend = backend
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param url [String]
|
13
|
+
# @return [String|nil]
|
14
|
+
def get(url,header)
|
15
|
+
|
16
|
+
cached_data = @backend.get(sanitize(url))
|
17
|
+
|
18
|
+
# Check if any fields are requested.
|
19
|
+
# If yes, request without fields and look into ['lastModified']
|
20
|
+
# if we need to fetch new or answer with our cached result.",
|
21
|
+
if cached_data[:status] == 200
|
22
|
+
puts "Debug: guildcache: cache HIT for #{url}"
|
23
|
+
if has_fields?(url)
|
24
|
+
puts "Debug: guildcache: has fields. Timethings: #{cached_data[:lastModified]} < #{get_lmt(url,header)} #{cached_data[:lastModified] < get_lmt(url,header)}"
|
25
|
+
if cached_data[:lastModified] < get_lmt(url,header)
|
26
|
+
cached_data = {:status => 404}
|
27
|
+
end
|
28
|
+
else
|
29
|
+
# if a guild is requested without additional fields,
|
30
|
+
# simply check if this file is still valid
|
31
|
+
if generic_needs_request?(cached_data[:header],cached_data[:lastModified])
|
32
|
+
cached_data = {:status => 404}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
puts "Debug: guildcache: cache MISS for #{url}"
|
37
|
+
end
|
38
|
+
cached_data
|
39
|
+
end
|
40
|
+
|
41
|
+
# method to get the lastModified info for a specified url
|
42
|
+
# @param url [String] the url for which the LMT is wanted
|
43
|
+
# @param header [Hash] the Hash of headers for a api request
|
44
|
+
def get_lmt(url,header)
|
45
|
+
cached_data = @backend.get(sanitize(url_without_fields(url)))
|
46
|
+
puts "Debug: get_lmt() cache result for generic url: #{cached_data[:status]}"
|
47
|
+
# if the object was not in the cache or it is expired
|
48
|
+
if cached_data[:status] != 200 or generic_needs_request?(cached_data[:header],cached_data[:lastModified])
|
49
|
+
# request it from the api
|
50
|
+
request_without_fields(url,header)
|
51
|
+
# and load the new object
|
52
|
+
cached_data = @backend.get(sanitize(url_without_fields(url)))
|
53
|
+
end
|
54
|
+
# this a JS milliseconds timestamp, we only do seconds!
|
55
|
+
Time.at(JSON.parse(cached_data[:object])["lastModified"]/1000)
|
56
|
+
end
|
57
|
+
|
58
|
+
def request_without_fields(url,header)
|
59
|
+
# need to request the generic guild response
|
60
|
+
url = url_without_fields(url)
|
61
|
+
# request it from the API:
|
62
|
+
got = request_raw(url,header)
|
63
|
+
@backend.post({:key => sanitize(url),:header => got.header.to_hash, :data => got.body}) # and put into cache
|
64
|
+
end
|
65
|
+
|
66
|
+
def url_without_fields(url)
|
67
|
+
url_obj = URI.parse(url)
|
68
|
+
url_obj.query=(url_obj.query.gsub(/fields=.+&|$/,"")) # strip the fields
|
69
|
+
url_obj.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
data/lib/rattlecache.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'backend_manager'
|
2
|
+
require 'net/http'
|
3
|
+
require 'digest/sha2'
|
4
|
+
|
5
|
+
module Rattlecache
|
6
|
+
|
7
|
+
class Cache
|
8
|
+
|
9
|
+
# @param backend [Symbol]
|
10
|
+
# @param adapter [Battlenet::Adapter::AbstractAdapter]
|
11
|
+
def initialize(backend = :filesystem, adapter = nil)
|
12
|
+
puts "Debug: rattlecache adapter: #{adapter}"
|
13
|
+
@adapter = adapter
|
14
|
+
@backend = Rattlecache::Backend.fetch(backend)
|
15
|
+
end
|
16
|
+
|
17
|
+
@request_pragmas = {
|
18
|
+
"guild" => "Check if any fields are requested. If yes, request without fields and look into ['lastModified'] if we need to fetch new or answer with out cached result.",
|
19
|
+
"data" => "Everything data related should be considered stable. Cache it for one week.",
|
20
|
+
"auction" => "dont know yet...",
|
21
|
+
"else" => "cache it for as long as 'RETRY-AFTER'-responseheader told us. (600 sec)"
|
22
|
+
}
|
23
|
+
|
24
|
+
# @param url [String]
|
25
|
+
# @param header [Hash]
|
26
|
+
def get(url, header = nil)
|
27
|
+
@header = header
|
28
|
+
@header['User-Agent'] = @header['User-Agent'] << " (with rattlecache)"
|
29
|
+
#puts "Cache class gets you: #{objectKey}"
|
30
|
+
|
31
|
+
# what to do with this request?
|
32
|
+
case request_type(url)
|
33
|
+
when "guild"
|
34
|
+
puts "Debug: its a guild related request!"
|
35
|
+
require 'caches/Fieldsrequestcache'
|
36
|
+
Rattlecache::Fieldsrequestcache.new(@backend,@adapter).get(url,header)
|
37
|
+
when "character"
|
38
|
+
puts "Debug: its a character related request!"
|
39
|
+
require 'caches/Fieldsrequestcache'
|
40
|
+
Rattlecache::Fieldsrequestcache.new(@backend,@adapter).get(url,header)
|
41
|
+
when /auction.*/
|
42
|
+
puts "Debug: its a auchtion related request!"
|
43
|
+
require 'caches/Auctionscache'
|
44
|
+
Rattlecache::Auctionscache.new(@backend,@adapter).get(url,header)
|
45
|
+
when "item"
|
46
|
+
# for items it seems reasonable to cache them at least for a week
|
47
|
+
# a week in seconds: 60*60*24*7 = 604800
|
48
|
+
check_and_return(@backend.get(sanitize(url)),604800)
|
49
|
+
else
|
50
|
+
puts "Debug: its a boring request!"
|
51
|
+
check_and_return(@backend.get(sanitize(url)))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_and_return(backend_result,given_time = nil)
|
56
|
+
if given_time.nil?
|
57
|
+
if backend_result[:status] == 200 and generic_needs_request?(backend_result[:header],backend_result[:lastModified])
|
58
|
+
backend_result = {:status => 404}
|
59
|
+
end
|
60
|
+
else
|
61
|
+
if backend_result[:status] == 200 and needs_request_with_given_time?(given_time,backend_result[:lastModified])
|
62
|
+
backend_result = {:status => 404}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
backend_result
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param object [Hash]
|
69
|
+
def post(object)
|
70
|
+
#puts "Cache class puts: #{object[:key]}"
|
71
|
+
@backend.post({:key => sanitize(object[:key]), :header => object[:header], :data => object[:data]})
|
72
|
+
end
|
73
|
+
|
74
|
+
# @param headerline [Hash]
|
75
|
+
# @param mtime [Time]
|
76
|
+
# @return [TrueClass|FalseClass]
|
77
|
+
def generic_needs_request?(headerline,mtime)
|
78
|
+
header = JSON.parse(headerline)
|
79
|
+
#header["date"][0] is a String with CGI.rfc1123_date() encoded time,
|
80
|
+
# as there is no easy method to inverse this coding, I will keep using the files mtime
|
81
|
+
# to estimate when the data was last recieved.
|
82
|
+
unless header["cache-control"].nil?
|
83
|
+
mtime+header["cache-control"][0].split("=")[1].to_i < Time.now()
|
84
|
+
else
|
85
|
+
unless header["retry-after"].nil?
|
86
|
+
mtime+(header["retry-after"][0].to_i) < Time.now()
|
87
|
+
else
|
88
|
+
# if we dont find any hint, pull it again!
|
89
|
+
puts "Warning: Cache couldn't find any hint if this object is still valid!"
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def needs_request_with_given_time?(given_time,mtime)
|
96
|
+
mtime+given_time < Time.now
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param objectKey [String]
|
100
|
+
# @return [String]
|
101
|
+
def sanitize(objectKey)
|
102
|
+
# strip scheme, sort paramters and encode for safty
|
103
|
+
urlObj = URI.parse(objectKey)
|
104
|
+
key = urlObj.host
|
105
|
+
key << urlObj.path
|
106
|
+
key << sort_params(urlObj.query)
|
107
|
+
Digest::SHA256.hexdigest(key)
|
108
|
+
end
|
109
|
+
|
110
|
+
# @param query [String]
|
111
|
+
# @return [String]
|
112
|
+
def sort_params(query)
|
113
|
+
q = Hash.new
|
114
|
+
query.split("&").each do |parampair|
|
115
|
+
q[parampair.split("=")[0]] = parampair.split("=")[1]
|
116
|
+
end
|
117
|
+
s = Array.new
|
118
|
+
q.sort.each { |pair| s << pair.join("=")}
|
119
|
+
"?"+s.join("&")
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param objectKey [String]
|
123
|
+
# @return [String]
|
124
|
+
def request_type(objectKey)
|
125
|
+
#[0] = "". [1]= "api". [2]="wow", [3]= what we want
|
126
|
+
URI.parse(objectKey).path.split("/")[3]
|
127
|
+
end
|
128
|
+
|
129
|
+
# @param query [String]
|
130
|
+
# @return [TrueClass|FalseClass]
|
131
|
+
def has_fields?(query)
|
132
|
+
not query.scan(/fields=/).empty?
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param url [String]
|
136
|
+
# @param header [Hash]
|
137
|
+
# @return [Net::HTTPResponse]
|
138
|
+
def request_raw(url,header)
|
139
|
+
req = @adapter.get(url,header,true)
|
140
|
+
req.get(url,header)
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
data/rattlecache.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "rattlecache"
|
6
|
+
s.version = "0.1"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["Marv Cool"]
|
9
|
+
s.email = "marv@hostin.is"
|
10
|
+
s.homepage = "https://github.com/MrMarvin/rattlcache/wiki"
|
11
|
+
s.summary = %q{A smart caching system for battlenet API Requests.}
|
12
|
+
|
13
|
+
if s.respond_to?(:add_development_dependency)
|
14
|
+
s.add_development_dependency "rspec"
|
15
|
+
end
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rattlecache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Marv Cool
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2011-08-23 00:00:00 +02:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: rspec
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :development
|
31
|
+
version_requirements: *id001
|
32
|
+
description:
|
33
|
+
email: marv@hostin.is
|
34
|
+
executables: []
|
35
|
+
|
36
|
+
extensions: []
|
37
|
+
|
38
|
+
extra_rdoc_files: []
|
39
|
+
|
40
|
+
files:
|
41
|
+
- .gitignore
|
42
|
+
- Gemfile
|
43
|
+
- LICENSE
|
44
|
+
- README.md
|
45
|
+
- Rakefile
|
46
|
+
- lib/backend_manager.rb
|
47
|
+
- lib/backends/filesystem.rb
|
48
|
+
- lib/caches/Auctionscache.rb
|
49
|
+
- lib/caches/Fieldsrequestcache.rb
|
50
|
+
- lib/rattlecache.rb
|
51
|
+
- rattlecache.gemspec
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: https://github.com/MrMarvin/rattlcache/wiki
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.7
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: A smart caching system for battlenet API Requests.
|
84
|
+
test_files: []
|
85
|
+
|