delta_cache 1.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/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Keep track of deltas and tombstones for an array of data
2
+
3
+ A cache that keeps track of deltas and tombstones for an array of data. Deltas and tombstones can be retrieved from the cache using a last-modified timestamp.
4
+
5
+ ## Configuration
6
+
7
+ ### Redis
8
+
9
+ Connection
10
+
11
+ DeltaCache.connection = Redis.new(:host => "your-host-ip")
12
+
13
+ ### Cassandra
14
+
15
+ Setup Cassandra Keyspaces and Column Families
16
+
17
+ create keyspace DeltaCache;
18
+ use DeltaCache;
19
+ create column family Cache;
20
+ create column family Deltas;
21
+
22
+ create keyspace DeltaCacheTest;
23
+ use DeltaCacheTest;
24
+ create column family Cache;
25
+ create column family Deltas;
26
+
27
+ Connection
28
+
29
+ DeltaCache.connection = Cassandra.new('DeltaCache')
30
+
31
+ ### Namespace
32
+
33
+ DeltaCache.cache_name = "your-cache-namespace"
34
+
35
+ ### Logger
36
+
37
+ DeltaCache.logger = Logger.new(STDOUT, Logger::DEBUG)
38
+
39
+ See emamples directory for more details.
@@ -0,0 +1,39 @@
1
+
2
+ # -*- encoding: utf-8 -*-
3
+ $:.push('lib')
4
+ require "delta_cache/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "delta_cache"
8
+ s.version = DeltaCache::VERSION.dup
9
+ s.date = "2012-02-01"
10
+ s.summary = "A cache that keeps track of deltas and tombstones for an array of data."
11
+ s.email = "john.critz@gmail.com"
12
+ s.homepage = ""
13
+ s.authors = ['John Critz']
14
+
15
+ s.description = <<-EOF
16
+ A cache that keeps track of deltas and tombstones for an array of data. Deltas and tombstones can be retrieved from the cache using a last-modified timestamp.
17
+ EOF
18
+
19
+ dependencies = []
20
+
21
+ s.files = Dir['**/*']
22
+ s.test_files = Dir['test/**/*'] + Dir['spec/**/*']
23
+ s.executables = Dir['bin/*'].map { |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+
26
+
27
+ ## Make sure you can build the gem on older versions of RubyGems too:
28
+ s.rubygems_version = "1.8.10"
29
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
30
+ s.specification_version = 3 if s.respond_to? :specification_version
31
+
32
+ dependencies.each do |type, name, version|
33
+ if s.respond_to?("add_#{type}_dependency")
34
+ s.send("add_#{type}_dependency", name, version)
35
+ else
36
+ s.add_dependency(name, version)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,76 @@
1
+ require 'rack'
2
+ require './facebook'
3
+
4
+ class Callback
5
+
6
+ attr_accessor :last_modified
7
+
8
+ def call(env)
9
+ init(env)
10
+ puts env.inspect
11
+ puts env["rack.input"].input.inspect
12
+ puts @req.inspect
13
+ puts @req.form_data?
14
+ puts @req.params.inspect
15
+
16
+ if is_valid_request?
17
+
18
+ if is_challenge_request?
19
+ [200, {'Content-Type' => 'text/plain'}, [@params["hub.challenge"]]]
20
+
21
+ else
22
+ changes = @params["entry"]
23
+ changes.each do |entry|
24
+
25
+ if is_valid_change?(entry)
26
+ fb_id = Integer(entry["uid"])
27
+
28
+ # update cache
29
+ friends_info = Facebook.get_friends(fb_id)
30
+ DeltaCache.new(:cache_prefix => "facebook", :key_id => fb_id).update(friends_info, fb_id)
31
+
32
+ # show changes
33
+ puts DeltaCache.new(
34
+ fb_id, :last_modified => self.last_modified
35
+ ).get_info(self.last_modified)
36
+
37
+ # store last modified timestamp
38
+ self.last_modified = DeltaCache.new(fb_id).get_last_modified
39
+ end
40
+ end
41
+
42
+ [200, {'Content-Type' => 'text/plain'}, []]
43
+ end
44
+
45
+ else
46
+ [404, {'Content-Type' => 'text/plain'}, []]
47
+
48
+ end
49
+ end
50
+
51
+ def init(env)
52
+ @req = Rack::Request.new(env)
53
+ @params = @req.params if @req
54
+ end
55
+
56
+ def is_valid_request?
57
+ @req && @params
58
+ end
59
+
60
+ def is_challenge_request?
61
+ return false if @params["hub.mode"].nil? || @params["hub.mode"].empty?
62
+ return false if @params["hub.challenge"].nil? || @params["hub.challenge"].empty?
63
+ return false if @params["hub.verify_token"].nil? || (token = @params["hub.verify_token"]).empty?
64
+ return FACEBOOK_CONFIG[:subscription_token] == token
65
+ end
66
+
67
+ def is_valid_change?(entry)
68
+ fb_id = Integer(entry["uid"])
69
+ fields = Array(entry["changed_fields"])
70
+
71
+ return false if fb_id.empty?
72
+ return false if fields.empty?
73
+ return fields.include?("friends")
74
+ end
75
+
76
+ end
@@ -0,0 +1,76 @@
1
+ require 'rack'
2
+ require './facebook'
3
+
4
+ class Callback
5
+
6
+ attr_accessor :last_modified
7
+
8
+ def call(env)
9
+ init(env)
10
+ # puts env.inspect
11
+ # puts env["rack.input"].input.inspect
12
+ # puts @req.inspect
13
+ # puts @req.form_data?
14
+ # puts @req.params.inspect
15
+
16
+ if is_valid_request?
17
+
18
+ if is_challenge_request?
19
+ [200, {'Content-Type' => 'text/plain'}, [@params["hub.challenge"]]]
20
+
21
+ else
22
+ changes = @params["entry"]
23
+ changes.each do |entry|
24
+
25
+ if is_valid_change?(entry)
26
+ fb_id = Integer(entry["uid"])
27
+
28
+ # update cache
29
+ friends_info = Facebook.get_friends(fb_id)
30
+ DeltaCache::Cache.new(fb_id).update(friends_info)
31
+
32
+ # show changes
33
+ puts DeltaCache::Cache.new(fb_id).get_info(self.last_modified)
34
+
35
+ # store last modified timestamp
36
+ self.last_modified = DeltaCache::Cache.new(fb_id).get_last_modified
37
+ end
38
+ end
39
+
40
+ [200, {'Content-Type' => 'text/plain'}, []]
41
+ end
42
+
43
+ else
44
+ [404, {'Content-Type' => 'text/plain'}, []]
45
+
46
+ end
47
+ end
48
+
49
+ def init(env)
50
+ @req = Rack::Request.new(env)
51
+ @params = @req.params if @req
52
+ DeltaCache.connection = Redis.new(:host => "127.0.0.1")
53
+ DeltaCache.cache_name = "facebook"
54
+ end
55
+
56
+ def is_valid_request?
57
+ @req && @params
58
+ end
59
+
60
+ def is_challenge_request?
61
+ return false if @params["hub.mode"].nil? || @params["hub.mode"].empty?
62
+ return false if @params["hub.challenge"].nil? || @params["hub.challenge"].empty?
63
+ return false if @params["hub.verify_token"].nil? || (token = @params["hub.verify_token"]).empty?
64
+ return FACEBOOK_CONFIG[:subscription_token] == token
65
+ end
66
+
67
+ def is_valid_change?(entry)
68
+ fb_id = Integer(entry["uid"])
69
+ fields = Array(entry["changed_fields"])
70
+
71
+ return false if fb_id.empty?
72
+ return false if fields.empty?
73
+ return fields.include?("friends")
74
+ end
75
+
76
+ end
@@ -0,0 +1,6 @@
1
+ require './callback'
2
+
3
+ use Rack::CommonLogger, STDOUT
4
+ use Rack::ShowExceptions
5
+
6
+ run Callback.new
@@ -0,0 +1,96 @@
1
+ ####
2
+ # Enable/disable Facebook subscriptions for a Facebook application
3
+ # so you will be notified of any changes to the users who have
4
+ # authorized the given Facebook app.
5
+ #
6
+ # Usage:
7
+ # Configure the FACEBOOK_CONFIG hash with the app information
8
+ # and then execute the ruby script.
9
+ #
10
+ ####
11
+
12
+ require 'net/http'
13
+ require 'uri'
14
+
15
+ require 'rubygems'
16
+ require 'fb_graph'
17
+ require 'redis'
18
+
19
+ require '../../lib/delta_cache'
20
+
21
+ FACEBOOK_CONFIG = {
22
+ :app_id => "your-app-id",
23
+ :access_token => "your-fb-access-token",
24
+ :subscription_callback_url => "http://your-public-ip/",
25
+ :subscription_token => "any-token-to-validate-the-fb-request"
26
+ }
27
+
28
+ class Facebook
29
+
30
+ def self.subscribe!
31
+ uri = URI.parse("https://graph.facebook.com/#{FACEBOOK_CONFIG[:app_id]}/subscriptions")
32
+ form_data = {
33
+ :object => "user",
34
+ :fields => "name,friends",
35
+ :access_token => FACEBOOK_CONFIG[:access_token],
36
+ :callback_url => FACEBOOK_CONFIG[:subscription_callback_url],
37
+ :verify_token => FACEBOOK_CONFIG[:subscription_token]
38
+ }
39
+
40
+ HTTP.new(uri).post(form_data)
41
+ end
42
+
43
+ def self.unsubscribe!
44
+ uri = URI.parse("https://graph.facebook.com/#{FACEBOOK_CONFIG[:app_id]}/subscriptions")
45
+ form_data = {
46
+ :object => "user",
47
+ :access_token => FACEBOOK_CONFIG[:access_token]
48
+ }
49
+
50
+ HTTP.new(uri).delete(form_data)
51
+ end
52
+
53
+ def self.get_friends(fb_id)
54
+ FbGraph::User.fetch(fb_id, :access_token => FACEBOOK_CONFIG[:access_token])
55
+ end
56
+
57
+ class HTTP
58
+
59
+ attr_accessor :uri, :http
60
+
61
+ def initialize(uri)
62
+ http = Net::HTTP.new(uri.host, uri.port)
63
+ http.use_ssl = true
64
+ http.read_timeout = 120
65
+
66
+ self.uri = uri
67
+ self.http = http
68
+ end
69
+
70
+ def post(form_data)
71
+ request = Net::HTTP::Post.new(self.uri.request_uri)
72
+ request.set_form_data(form_data)
73
+ response = self.http.request(request)
74
+
75
+ puts "\nForm Data"
76
+ puts form_data.inspect
77
+ puts "--"
78
+
79
+ puts "\nResponse"
80
+ puts response
81
+ puts "--"
82
+ end
83
+
84
+ def delete(form_data)
85
+ request = Net::HTTP::Delete.new(self.uri.request_uri)
86
+ request.set_form_data(form_data)
87
+ response = self.http.request(request)
88
+
89
+ puts "\nResponse"
90
+ puts response
91
+ puts "--"
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+ require 'json'
4
+
5
+ require File.expand_path('../../lib/delta_cache.rb')
6
+
7
+ DeltaCache.connection = Redis.new(:host => "127.0.0.1")
8
+ DeltaCache.cache_name = "notifications"
9
+
10
+ class Notifications
11
+
12
+ attr_accessor :cache
13
+
14
+ def initialize(cache_id)
15
+ self.cache = DeltaCache::Cache.new(cache_id)
16
+
17
+ ## flush and initialize cache
18
+ cache.flush!
19
+
20
+ puts "\nInitializing cache..."
21
+ notifications = [
22
+ {:name => "John", :message => "Be my friend."},
23
+ ]
24
+ update_and_print(notifications)
25
+ end
26
+
27
+ def add_to_cache
28
+ puts "\nAdding Steve and Bill to cache..."
29
+
30
+ lm = cache.get_last_modified
31
+ puts "\n Deltas since: #{lm}"
32
+ notifications = [
33
+ {:name => "John", :message => "Be my friend."},
34
+ {:name => "Steve", :message => "Be my friend."},
35
+ {:name => "Bill", :message => "Be my friend."}
36
+ ]
37
+
38
+ update_and_print(notifications)
39
+ end
40
+
41
+ def delete_from_cache
42
+ puts "\nDeleting John from cache..."
43
+
44
+ lm = cache.get_last_modified
45
+ puts "\n Tombstones since: #{lm}"
46
+ notifications = [
47
+ {:name => "Steve", :message => "Be my friend."},
48
+ {:name => "Bill", :message => "Be my friend."}
49
+ ]
50
+
51
+ update_and_print(notifications)
52
+ end
53
+
54
+ def update_and_print(data)
55
+ lm = cache.get_last_modified
56
+ cache.update(data)
57
+ puts " " + cache.get_info(lm).inspect
58
+ end
59
+
60
+ end
61
+
62
+ n = Notifications.new(1)
63
+ sleep 2
64
+
65
+ n.add_to_cache
66
+ sleep 2
67
+
68
+ n.delete_from_cache
@@ -0,0 +1,139 @@
1
+ # Stores friend cache data in a Cassandra keyspace. The advantage here is
2
+ # the cache can grow larger than the memory on one machine. The disadvantage
3
+ # is that you have to set up multiple machines and make some decisions about
4
+ # acceptable thresholds around consistency and atomicity.
5
+ #
6
+ # This module requires initialization, like so:
7
+ #
8
+ # DeltaCache.connection = Cassandra.new('DeltaCache')
9
+ # DeltaCache.logger = Logger.new(STDOUT, Logger::DEBUG)
10
+ #
11
+ # You only need to set the logger if you need to debug.
12
+ #
13
+ # ## Storage
14
+ #
15
+ # This adapter uses two column families: `:Cache` and `:Deltas`.
16
+ #
17
+ # The `:Cache` CF stores each cached object as a JSON blob. Each row is keyed
18
+ # by the SHA1 hash of the blob. The blob itself is stored in a column named
19
+ # `blob`. These rows won't ever get too wide, so this CF is a good candidate
20
+ # for row caching by Cassandra.
21
+ #
22
+ # The `:Deltas` CF stores changes to objects cached for each user. There are
23
+ # two rows per user; one tracks newly cached objects and the other tracks
24
+ # cached objects that have been removed. Each row contains a pair of columns
25
+ # per cached object. One maps a timestamp to a cached object and is used for
26
+ # caching the most recent changes to a user's cached objects. The other maps
27
+ # cached objects back to a timestamp and is used to find delta entries to
28
+ # remove if a cached object is marked as removed.
29
+
30
+ class DeltaCache::CassandraDB
31
+
32
+ MAX_DELTAS = 10_000
33
+
34
+ attr_accessor :cache_id
35
+
36
+ def initialize(cache_id)
37
+ self.cache_id = cache_id
38
+ end
39
+
40
+ def connection
41
+ DeltaCache.connection
42
+ end
43
+
44
+ def logger
45
+ DeltaCache.logger
46
+ end
47
+
48
+ def cache_name
49
+ DeltaCache.cache_name
50
+ end
51
+
52
+ # Given a key ID, generate a key into `:Deltas` for cached objects.
53
+ def info_key
54
+ ["cache", cache_name, cache_id].join(":")
55
+ end
56
+
57
+ # Given a key ID, generate a key into `:Deltas` for deleted cache objects.
58
+ def deleted_info_key
59
+ ["deleted", cache_name, cache_id].join(":")
60
+ end
61
+
62
+ # Store a cached object.
63
+ def set(data)
64
+ data = data.to_json
65
+ cache_key = Digest::SHA1.hexdigest(data)
66
+
67
+ log("insert :Cache, #{cache_key} -> {'blob' => #{data.inspect}}")
68
+ connection.insert(:Cache, cache_key, { "blob" => data })
69
+ cache_key
70
+ end
71
+
72
+ # Add a cached object to a delta timeline.
73
+ def add(key, timestamp, value)
74
+ columns = { timestamp_name(timestamp) => value, cache_name(value) => timestamp.to_f.to_s }
75
+
76
+ log("insert :Deltas, #{key} -> #{columns.inspect}")
77
+ connection.insert(:Deltas, key, columns)
78
+ end
79
+
80
+ # Remove a cached object from a delta timeline.
81
+ def rem(key, value)
82
+ log("get :Deltas, #{key}, #{cache_name(value)}")
83
+ timestamp = connection.get(:Deltas, key, cache_name(value))
84
+
85
+ log("remove :Deltas, #{key} -> #{timestamp_name(timestamp)}")
86
+ connection.remove(:Deltas, key, timestamp_name(timestamp))
87
+
88
+ log("remove :Deltas, #{key} -> #{cache_name(value)}")
89
+ connection.remove(:Deltas, key, cache_name(value))
90
+ end
91
+
92
+ # Fetch multiple cached objects.
93
+ def get(keys)
94
+ log("get :Cache, #{keys.inspect}")
95
+ connection.multi_get(:Cache, Array(keys)).values.map { |v| v['blob'] }
96
+ end
97
+
98
+ # Find all the cached objects referenced by a delta timeline.
99
+ def get_rev_range(key, most_recent, last_modified)
100
+ log("get :Deltas, #{key}, :reversed => true, :start => #{timestamp_name(most_recent)}, :finish => #{timestamp_name(last_modified)}")
101
+ connection.get(:Deltas, key, :reversed => true, :start => timestamp_name(most_recent), :finish => timestamp_name(last_modified), :count => MAX_DELTAS).values
102
+ end
103
+
104
+ # Get a mapping of cached objects to delta timestamps for a given user.
105
+ def get_timestamp(key, value)
106
+ log("get :Deltas, #{key}, #{cache_name(value)}")
107
+ score = connection.get(:Deltas, key, cache_name(value))
108
+ Time.at(score.to_f).utc
109
+ end
110
+
111
+ # Remove an object from the cache.
112
+ def del(key)
113
+ log("remove :Cache, #{key}")
114
+ connection.remove(:Cache, key)
115
+ end
116
+
117
+ # Check if a delta cache exists for a user.
118
+ def exists(key)
119
+ log("exists? :Deltas, #{key}")
120
+ connection.exists?(:Deltas, key)
121
+ end
122
+
123
+ # Given a timestamp, generate a column name for use in range queries on `:Deltas`.
124
+ def timestamp_name(timestamp)
125
+ ["ts", timestamp.to_f].join(":")
126
+ end
127
+
128
+ # Given the SHA1 for a cached object, generate a key into `:Cache`.
129
+ def cache_name(value)
130
+ ["cache", value].join(":")
131
+ end
132
+
133
+ # Private: Log a message, if a logger is set.
134
+ def log(msg)
135
+ return if logger.nil?
136
+ logger.debug(msg)
137
+ end
138
+
139
+ end
@@ -0,0 +1,79 @@
1
+ class DeltaCache::RedisDB
2
+
3
+ attr_accessor :cache_id
4
+
5
+ def initialize(cache_id)
6
+ self.cache_id = cache_id
7
+ end
8
+
9
+ def connection
10
+ DeltaCache.connection
11
+ end
12
+
13
+ def cache_name
14
+ DeltaCache.cache_name
15
+ end
16
+
17
+ def info_key
18
+ [
19
+ "delta_cache",
20
+ "info",
21
+ cache_name,
22
+ cache_id
23
+ ].join(":")
24
+ end
25
+
26
+ def deleted_info_key
27
+ [
28
+ "delta_cache",
29
+ "deleted_info",
30
+ cache_name,
31
+ cache_id
32
+ ].join(":")
33
+ end
34
+
35
+ # set parent key that holds the data
36
+ def set(data)
37
+ data = data.to_json
38
+ cache_key = Digest::SHA1.hexdigest(data)
39
+ connection.set(cache_key, data)
40
+ cache_key
41
+ end
42
+
43
+ # add key to a set
44
+ def add(key, timestamp, value)
45
+ connection.zadd(key, timestamp.to_i, value)
46
+ end
47
+
48
+ # remove key from a set
49
+ def rem(key, value)
50
+ connection.zrem(key, value)
51
+ end
52
+
53
+ # get a list of keys
54
+ def get(keys)
55
+ connection.mget(*keys)
56
+ end
57
+
58
+ # get members from a set in reverse order
59
+ def get_rev_range(key, end_pos, start_pos)
60
+ connection.zrevrangebyscore(key, end_pos.to_i, start_pos.to_i)
61
+ end
62
+
63
+ # get the 'score' of the member in a set
64
+ def get_timestamp(key, value)
65
+ score = connection.zscore(key, value)
66
+ Time.at(score.to_i).utc
67
+ end
68
+
69
+ # delete a key
70
+ def del(key)
71
+ connection.del(key)
72
+ end
73
+
74
+ # does a key exist
75
+ def exists(key)
76
+ connection.exists(key)
77
+ end
78
+
79
+ end
@@ -0,0 +1,3 @@
1
+ module DeltaCache
2
+ VERSION = '1.0'
3
+ end
@@ -0,0 +1,136 @@
1
+ module DeltaCache
2
+
3
+ require 'time'
4
+ require 'digest'
5
+
6
+ require 'delta_cache/db/redis'
7
+ require 'delta_cache/db/cassandra'
8
+
9
+ class << self
10
+ attr_accessor :connection, :logger, :cache_name
11
+ end
12
+
13
+ class Cache
14
+
15
+ attr_accessor :db, :cache_id
16
+
17
+ def initialize(cache_id)
18
+ self.cache_id = cache_id
19
+
20
+ raise "DeltaCache.connection is not defined." unless DeltaCache.connection
21
+ raise "DeltaCache.cache_name is not defined." unless DeltaCache.cache_name
22
+
23
+ class_name = DeltaCache.connection.class.name
24
+ if class_name =~ /redis/i
25
+ self.db = DeltaCache::RedisDB.new(cache_id)
26
+ elsif class_name =~ /cassandra/i
27
+ self.db = DeltaCache::CassandraDB.new(cache_id)
28
+ end
29
+ end
30
+
31
+ def info_key
32
+ db.info_key
33
+ end
34
+
35
+ def deleted_info_key
36
+ db.deleted_info_key
37
+ end
38
+
39
+ def exists?
40
+ db.exists(info_key)
41
+ end
42
+
43
+ # this will remove all cache for the given id
44
+ def flush!
45
+ db.del(info_key)
46
+ db.del(deleted_info_key)
47
+ end
48
+
49
+ def update(info)
50
+ cache_new(info)
51
+ # stores tombstones for deleted records
52
+ cache_deleted(info)
53
+
54
+ return true
55
+ end
56
+
57
+ def cache_new(info)
58
+ new_info = (info - get_info(nil, false))
59
+ new_info.each do |info|
60
+ info_id = set_info(info)
61
+ db.rem(deleted_info_key, info_id)
62
+ db.add(info_key, timestamp, info_id)
63
+ end
64
+ end
65
+
66
+ def cache_deleted(info)
67
+ deleted_info = (get_info(nil, false) - info)
68
+ deleted_info.each do |info|
69
+ info_id = set_info(info)
70
+ db.rem(info_key, info_id)
71
+ db.add(deleted_info_key, timestamp, info_id)
72
+ end
73
+ end
74
+
75
+ def timestamp(time=nil)
76
+ Time.parse((time || Time.now).to_s).utc
77
+ end
78
+
79
+ def set_info(info)
80
+ db.set(info)
81
+ end
82
+
83
+ def get_info(last_modified=nil, show_deleted_flag=true)
84
+ return [] unless exists?
85
+
86
+ last_modified_time = if last_modified.nil?
87
+ Time.at(0)
88
+ else
89
+ timestamp(last_modified)
90
+ end
91
+
92
+ info = []
93
+
94
+ info_ids = db.get_rev_range(info_key, timestamp, last_modified_time + 1)
95
+ if info_ids.any?
96
+ info += db.get(info_ids).compact.map do |f|
97
+ hash = JSON.parse(f)
98
+ hash.merge!(:deleted => false) if show_deleted_flag
99
+ hash
100
+ end
101
+ end
102
+
103
+ if last_modified_time.to_i > 0
104
+ info_ids = db.get_rev_range(deleted_info_key, timestamp, last_modified_time + 1)
105
+ if info_ids.any?
106
+ info += db.get(info_ids).compact.map do |f|
107
+ hash = JSON.parse(f)
108
+ hash.merge!(:deleted => true) if show_deleted_flag
109
+ hash
110
+ end
111
+ end
112
+ end
113
+
114
+ info.map do |hash|
115
+ new_hash = Hash.new
116
+ hash.each do |k, v|
117
+ new_hash[k.to_sym] = v
118
+ end
119
+ new_hash
120
+ end
121
+ end
122
+
123
+ def get_last_modified
124
+ info_ids = db.get_rev_range(info_key, timestamp, Time.at(0))
125
+ deleted_info_ids = db.get_rev_range(deleted_info_key, timestamp, Time.at(0))
126
+
127
+ last_modified = [ db.get_timestamp(info_key, info_ids.first),
128
+ db.get_timestamp(deleted_info_key, deleted_info_ids.first) ].compact
129
+ return Time.now.utc.httpdate unless last_modified.any?
130
+
131
+ Time.at(last_modified.max.to_i).utc.httpdate
132
+ end
133
+
134
+ end
135
+
136
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delta_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Critz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-01 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: ! 'A cache that keeps track of deltas and tombstones for an array of
15
+ data. Deltas and tombstones can be retrieved from the cache using a last-modified
16
+ timestamp.
17
+
18
+ '
19
+ email: john.critz@gmail.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - delta_cache-1.0.gem
25
+ - delta_cache.gemspec
26
+ - examples/callback.rb
27
+ - examples/facebook/callback.rb
28
+ - examples/facebook/config.ru
29
+ - examples/facebook/facebook.rb
30
+ - examples/notifications/notifications.rb
31
+ - lib/delta_cache/db/cassandra.rb
32
+ - lib/delta_cache/db/redis.rb
33
+ - lib/delta_cache/version.rb
34
+ - lib/delta_cache.rb
35
+ - README.md
36
+ homepage: ''
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.10
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: A cache that keeps track of deltas and tombstones for an array of data.
60
+ test_files: []