rattlecache 0.1
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 +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
|
+
|