summaryrefslogtreecommitdiff
path: root/lib/gitlab/avatar_cache.rb
blob: 30c8e0890614b7d58f99b368e7a77eea806685d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# frozen_string_literal: true

module Gitlab
  class AvatarCache
    class << self
      # Increment this if a breaking change requires
      # immediate cache expiry of all avatar caches.
      #
      # @return [Integer]
      VERSION = 1

      # @return [Symbol]
      BASE_KEY = :avatar_cache

      # @return [ActiveSupport::Duration]
      DEFAULT_EXPIRY = 7.days

      # Look up cached avatar data by email address.
      # This accepts a block to provide the value to be
      # cached in the event nothing is found.
      #
      # Multiple calls in the same request will be served from the
      # request store.
      #
      # @param email [String]
      # @param additional_keys [*Object] all must respond to `#to_s`
      # @param expires_in [ActiveSupport::Duration, Integer]
      # @yield [email, *additional_keys] yields the supplied params back to the block
      # @return [String]
      def by_email(email, *additional_keys, expires_in: DEFAULT_EXPIRY)
        key = email_key(email)
        subkey = additional_keys.join(":")

        Gitlab::SafeRequestStore.fetch([key, subkey]) do
          with do |redis|
            # Look for existing cache value
            cached = redis.hget(key, subkey)

            # Return the cached entry if set
            break cached unless cached.nil?

            # Otherwise, call the block to get the value
            to_cache = yield(email, *additional_keys).to_s

            # Set it in the cache
            redis.hset(key, subkey, to_cache)

            # Update the expiry time
            redis.expire(key, expires_in)

            # Return this new value
            break to_cache
          end
        end
      end

      # Remove one or more emails from the cache
      #
      # @param emails [String] one or more emails to delete
      # @return [Integer] the number of keys deleted
      def delete_by_email(*emails)
        return 0 if emails.empty?

        with do |redis|
          keys = emails.map { |email| email_key(email) }

          Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
            redis.unlink(*keys)
          end
        end
      end

      private

      # @param email [String]
      # @return [String]
      def email_key(email)
        "#{BASE_KEY}:v#{VERSION}:#{email}"
      end

      def with(&blk)
        Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
      end
    end
  end
end