sidekiq-unique-jobs 6.0.6 → 6.0.25

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +768 -150
  3. data/README.md +123 -77
  4. data/bin/uniquejobs +2 -2
  5. data/lib/sidekiq-unique-jobs.rb +1 -1
  6. data/lib/sidekiq_unique_jobs/cli.rb +27 -12
  7. data/lib/sidekiq_unique_jobs/client/middleware.rb +8 -4
  8. data/lib/sidekiq_unique_jobs/constants.rb +29 -18
  9. data/lib/sidekiq_unique_jobs/digests.rb +34 -19
  10. data/lib/sidekiq_unique_jobs/job.rb +29 -0
  11. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +14 -18
  12. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +20 -4
  13. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +7 -3
  14. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +1 -1
  15. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +1 -0
  16. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +9 -3
  17. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +0 -8
  18. data/lib/sidekiq_unique_jobs/locksmith.rb +59 -32
  19. data/lib/sidekiq_unique_jobs/logging.rb +14 -0
  20. data/lib/sidekiq_unique_jobs/middleware.rb +26 -9
  21. data/lib/sidekiq_unique_jobs/normalizer.rb +1 -1
  22. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +1 -1
  23. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +3 -3
  24. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +7 -2
  25. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +1 -1
  26. data/lib/sidekiq_unique_jobs/on_conflict.rb +12 -7
  27. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +8 -8
  28. data/lib/sidekiq_unique_jobs/scripts.rb +38 -9
  29. data/lib/sidekiq_unique_jobs/server/middleware.rb +18 -6
  30. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +1 -1
  31. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +88 -0
  32. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +13 -4
  33. data/lib/sidekiq_unique_jobs/testing.rb +3 -3
  34. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +2 -1
  35. data/lib/sidekiq_unique_jobs/timeout.rb +1 -1
  36. data/lib/sidekiq_unique_jobs/unique_args.rb +7 -4
  37. data/lib/sidekiq_unique_jobs/util.rb +8 -4
  38. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  39. data/lib/sidekiq_unique_jobs/version_check.rb +95 -0
  40. data/lib/sidekiq_unique_jobs/web/helpers.rb +7 -6
  41. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +4 -0
  42. data/lib/sidekiq_unique_jobs/web.rb +19 -12
  43. data/lib/sidekiq_unique_jobs.rb +33 -107
  44. data/lib/tasks/changelog.rake +23 -0
  45. data/redis/convert_legacy_lock.lua +13 -0
  46. data/redis/delete_by_digest.lua +10 -11
  47. data/redis/delete_job_by_digest.lua +6 -4
  48. data/redis/lock.lua +14 -10
  49. data/redis/unlock.lua +13 -11
  50. metadata +88 -108
  51. data/.codeclimate.yml +0 -35
  52. data/.csslintrc +0 -2
  53. data/.dockerignore +0 -4
  54. data/.editorconfig +0 -14
  55. data/.eslintignore +0 -1
  56. data/.eslintrc +0 -213
  57. data/.fasterer.yml +0 -23
  58. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
  59. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  60. data/.gitignore +0 -26
  61. data/.reek.yml +0 -87
  62. data/.rspec +0 -2
  63. data/.rubocop.yml +0 -127
  64. data/.simplecov +0 -20
  65. data/.travis.yml +0 -49
  66. data/.yardopts +0 -7
  67. data/Appraisals +0 -25
  68. data/CODE_OF_CONDUCT.md +0 -74
  69. data/Gemfile +0 -24
  70. data/Guardfile +0 -55
  71. data/Rakefile +0 -12
  72. data/_config.yml +0 -1
  73. data/assets/unique_digests_1.png +0 -0
  74. data/assets/unique_digests_2.png +0 -0
  75. data/bin/bench +0 -20
  76. data/examples/another_unique_job.rb +0 -15
  77. data/examples/custom_queue_job.rb +0 -12
  78. data/examples/custom_queue_job_with_filter_method.rb +0 -13
  79. data/examples/custom_queue_job_with_filter_proc.rb +0 -16
  80. data/examples/expiring_job.rb +0 -12
  81. data/examples/inline_worker.rb +0 -12
  82. data/examples/just_a_worker.rb +0 -13
  83. data/examples/long_running_job.rb +0 -14
  84. data/examples/main_job.rb +0 -14
  85. data/examples/my_job.rb +0 -12
  86. data/examples/my_unique_job.rb +0 -15
  87. data/examples/my_unique_job_with_filter_method.rb +0 -21
  88. data/examples/my_unique_job_with_filter_proc.rb +0 -19
  89. data/examples/notify_worker.rb +0 -14
  90. data/examples/plain_class.rb +0 -13
  91. data/examples/simple_worker.rb +0 -15
  92. data/examples/spawn_simple_worker.rb +0 -12
  93. data/examples/test_class.rb +0 -9
  94. data/examples/unique_across_workers_job.rb +0 -20
  95. data/examples/unique_job_on_conflict_raise.rb +0 -14
  96. data/examples/unique_job_on_conflict_reject.rb +0 -14
  97. data/examples/unique_job_on_conflict_reschedule.rb +0 -14
  98. data/examples/unique_job_with_conditional_parameter.rb +0 -18
  99. data/examples/unique_job_with_filter_method.rb +0 -21
  100. data/examples/unique_job_with_nil_unique_args.rb +0 -20
  101. data/examples/unique_job_with_no_unique_args_method.rb +0 -16
  102. data/examples/unique_job_withthout_unique_args_parameter.rb +0 -18
  103. data/examples/unique_on_all_queues_job.rb +0 -16
  104. data/examples/until_and_while_executing_job.rb +0 -17
  105. data/examples/until_executed_2_job.rb +0 -24
  106. data/examples/until_executed_job.rb +0 -25
  107. data/examples/until_executing_job.rb +0 -11
  108. data/examples/until_expired_job.rb +0 -12
  109. data/examples/until_global_expired_job.rb +0 -12
  110. data/examples/while_executing_job.rb +0 -15
  111. data/examples/while_executing_reject_job.rb +0 -14
  112. data/examples/without_argument_job.rb +0 -13
  113. data/sidekiq-unique-jobs.gemspec +0 -42
@@ -1,29 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pathname'
4
- require 'digest/sha1'
5
- require 'concurrent/map'
3
+ require "pathname"
4
+ require "digest/sha1"
5
+ require "concurrent/map"
6
6
 
7
7
  module SidekiqUniqueJobs
8
8
  # Interface to dealing with .lua files
9
9
  #
10
10
  # @author Mikael Henriksson <mikael@zoolutions.se>
11
11
  module Scripts
12
- LUA_PATHNAME ||= Pathname.new(__FILE__).dirname.join('../../redis').freeze
12
+ LUA_PATHNAME ||= Pathname.new(__FILE__).dirname.join("../../redis").freeze
13
13
  SCRIPT_SHAS ||= Concurrent::Map.new
14
14
 
15
15
  include SidekiqUniqueJobs::Connection
16
16
 
17
17
  module_function
18
18
 
19
+ #
19
20
  # Call a lua script with the provided file_name
21
+ #
22
+ # @note this method is recursive if we need to load a lua script
23
+ # that wasn't previously loaded.
24
+ #
20
25
  # @param [Symbol] file_name the name of the lua script
21
26
  # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
22
27
  # @param [Hash] options arguments to pass to the script file
23
28
  # @option options [Array] :keys the array of keys to pass to the script
24
29
  # @option options [Array] :argv the array of arguments to pass to the script
25
- # @note this method is recursive if we need to load a lua script
26
- # that wasn't previously loaded.
30
+ #
31
+ # @return value from script
32
+ #
27
33
  def call(file_name, redis_pool, options = {})
28
34
  execute_script(file_name, redis_pool, options)
29
35
  rescue Redis::CommandError => ex
@@ -32,12 +38,17 @@ module SidekiqUniqueJobs
32
38
  end
33
39
  end
34
40
 
41
+ #
35
42
  # Execute the script file
43
+ #
36
44
  # @param [Symbol] file_name the name of the lua script
37
45
  # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
38
46
  # @param [Hash] options arguments to pass to the script file
39
47
  # @option options [Array] :keys the array of keys to pass to the script
40
48
  # @option options [Array] :argv the array of arguments to pass to the script
49
+ #
50
+ # @return value from script (evalsha)
51
+ #
41
52
  def execute_script(file_name, redis_pool, options = {})
42
53
  redis(redis_pool) do |conn|
43
54
  sha = script_sha(conn, file_name)
@@ -45,10 +56,15 @@ module SidekiqUniqueJobs
45
56
  end
46
57
  end
47
58
 
59
+ #
48
60
  # Return sha of already loaded lua script or load it and return the sha
61
+ #
49
62
  # @param [Sidekiq::RedisConnection] conn the redis connection
50
63
  # @param [Symbol] file_name the name of the lua script
51
64
  # @return [String] sha of the script file
65
+ #
66
+ # @return [String] the sha of the script
67
+ #
52
68
  def script_sha(conn, file_name)
53
69
  if (sha = SCRIPT_SHAS.get(file_name))
54
70
  return sha
@@ -59,12 +75,17 @@ module SidekiqUniqueJobs
59
75
  sha
60
76
  end
61
77
 
62
- # Return sha of already loaded lua script or load it and return the sha
78
+ #
79
+ # Handle errors to allow retrying errors that need retrying
80
+ #
63
81
  # @param [Redis::CommandError] ex exception to handle
64
82
  # @param [Symbol] file_name the name of the lua script
65
- # @raise [ScriptError] when the error isn't handled
83
+ #
84
+ # @return [void]
85
+ #
86
+ # @yieldreturn [void] yields back to the caller when NOSCRIPT is raised
66
87
  def handle_error(ex, file_name)
67
- if ex.message == 'NOSCRIPT No matching script. Please use EVAL.'
88
+ if ex.message == "NOSCRIPT No matching script. Please use EVAL."
68
89
  SCRIPT_SHAS.delete(file_name)
69
90
  return yield if block_given?
70
91
  end
@@ -72,16 +93,24 @@ module SidekiqUniqueJobs
72
93
  raise ScriptError, file_name: file_name, source_exception: ex
73
94
  end
74
95
 
96
+ #
75
97
  # Reads the lua file from disk
98
+ #
76
99
  # @param [Symbol] file_name the name of the lua script
100
+ #
77
101
  # @return [String] the content of the lua file
102
+ #
78
103
  def script_source(file_name)
79
104
  script_path(file_name).read
80
105
  end
81
106
 
107
+ #
82
108
  # Construct a Pathname to a lua script
109
+ #
83
110
  # @param [Symbol] file_name the name of the lua script
111
+ #
84
112
  # @return [Pathname] the full path to the gems lua script
113
+ #
85
114
  def script_path(file_name)
86
115
  LUA_PATHNAME.join("#{file_name}.lua")
87
116
  end
@@ -6,28 +6,40 @@ module SidekiqUniqueJobs
6
6
  #
7
7
  # @author Mikael Henriksson <mikael@zoolutions.se>
8
8
  class Middleware
9
+ include Logging
9
10
  include OptionsWithFallback
10
11
 
11
- # Runs the server middleware
12
- # Used from Sidekiq::Processor#process
12
+ #
13
+ #
14
+ # Runs the server middleware (used from Sidekiq::Processor#process)
15
+ #
13
16
  # @param [Sidekiq::Worker] worker_class
14
17
  # @param [Hash] item a sidekiq job hash
15
18
  # @param [String] queue name of the queue
19
+ #
20
+ # @see https://github.com/mperham/sidekiq/wiki/Job-Format
21
+ # @see https://github.com/mperham/sidekiq/wiki/Middleware
22
+ #
16
23
  # @yield when uniqueness is disabled
17
- # @yield when the lock class executes successfully
24
+ # @yield when the lock is acquired
18
25
  def call(worker_class, item, queue)
19
26
  @worker_class = worker_class
20
27
  @item = item
21
28
  @queue = queue
22
29
  return yield if unique_disabled?
23
30
 
24
- lock.execute do
25
- yield
31
+ SidekiqUniqueJobs::Job.add_uniqueness(item)
32
+ SidekiqUniqueJobs.with_context(logging_context(self.class, item)) do
33
+ lock.execute do
34
+ yield
35
+ end
26
36
  end
27
37
  end
28
38
 
29
- protected
39
+ private
30
40
 
41
+ # The sidekiq job hash
42
+ # @return [Hash] the Sidekiq job hash
31
43
  attr_reader :item
32
44
  end
33
45
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sidekiq/api'
3
+ require "sidekiq/api"
4
4
 
5
5
  module Sidekiq
6
6
  class SortedEntry
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Contains configuration and utility methods that belongs top level
5
+ #
6
+ # @author Mikael Henriksson <mikael@zoolutions.se>
7
+ module SidekiqUniqueJobs
8
+ include SidekiqUniqueJobs::Connection
9
+
10
+ module_function
11
+
12
+ Config = Concurrent::MutableStruct.new(
13
+ :default_lock_timeout,
14
+ :enabled,
15
+ :unique_prefix,
16
+ :logger,
17
+ )
18
+
19
+ # The current configuration (See: {.configure} on how to configure)
20
+ def config
21
+ # Arguments here need to match the definition of the new class (see above)
22
+ @config ||= Config.new(
23
+ 0,
24
+ true,
25
+ "uniquejobs",
26
+ Sidekiq.logger,
27
+ )
28
+ end
29
+
30
+ # The current logger
31
+ # @return [Logger] the configured logger
32
+ def logger
33
+ config.logger
34
+ end
35
+
36
+ # :reek:ManualDispatch
37
+ def with_context(context)
38
+ if logger.respond_to?(:with_context)
39
+ logger.with_context(context) { yield }
40
+ elsif defined?(Sidekiq::Context)
41
+ Sidekiq::Context.with(context) { yield }
42
+ elsif defined?(Sidekiq::Logging)
43
+ Sidekiq::Logging.with_context(context) { yield }
44
+ else
45
+ yield
46
+ end
47
+ end
48
+
49
+ # Set a new logger
50
+ # @param [Logger] other a new logger
51
+ def logger=(other)
52
+ config.logger = other
53
+ end
54
+
55
+ # Change global configuration while yielding
56
+ # @yield control to the caller
57
+ def use_config(tmp_config)
58
+ raise ::ArgumentError, "#{name}.#{__method__} needs a block" unless block_given?
59
+
60
+ old_config = config.to_h
61
+ configure(tmp_config)
62
+ yield
63
+ configure(old_config)
64
+ end
65
+
66
+ # Configure the gem
67
+ #
68
+ # This is usually called once at startup of an application
69
+ # @param [Hash] options global gem options
70
+ # @option options [Integer] :default_lock_timeout (default is 0)
71
+ # @option options [true,false] :enabled (default is true)
72
+ # @option options [String] :unique_prefix (default is 'uniquejobs')
73
+ # @option options [Logger] :logger (default is Sidekiq.logger)
74
+ # @yield control to the caller when given block
75
+ def configure(options = {})
76
+ if block_given?
77
+ yield config
78
+ else
79
+ options.each do |key, val|
80
+ config.send("#{key}=", val)
81
+ end
82
+ end
83
+ end
84
+
85
+ def redis_version
86
+ @redis_version ||= redis { |conn| conn.info("server")["redis_version"] }
87
+ end
88
+ end
@@ -15,6 +15,7 @@ module SidekiqUniqueJobs
15
15
  # @return [Hash] of the worker class sidekiq options
16
16
  def worker_options
17
17
  return {} unless sidekiq_worker_class?
18
+
18
19
  worker_class.get_sidekiq_options.stringify_keys
19
20
  end
20
21
 
@@ -28,21 +29,29 @@ module SidekiqUniqueJobs
28
29
  # The Sidekiq::Worker implementation
29
30
  # @return [Sidekiq::Worker]
30
31
  def worker_class
31
- @_worker_class ||= worker_class_constantize # rubocop:disable Naming/MemoizedInstanceVariableName
32
+ @_worker_class ||= worker_class_constantize(@worker_class) # rubocop:disable Naming/MemoizedInstanceVariableName
32
33
  end
33
34
 
34
35
  # The hook to call after a successful unlock
35
36
  # @return [Proc]
36
37
  def after_unlock_hook
37
- -> { worker_class.after_unlock if worker_method_defined?(:after_unlock) }
38
+ lambda do
39
+ if @worker_class.respond_to?(:after_unlock)
40
+ @worker_class.after_unlock # instance method in sidekiq v6
41
+ elsif worker_class.respond_to?(:after_unlock)
42
+ worker_class.after_unlock # class method regardless of sidekiq version
43
+ end
44
+ end
38
45
  end
39
46
 
40
47
  # Attempt to constantize a string worker_class argument, always
41
48
  # failing back to the original argument when the constant can't be found
42
49
  #
43
50
  # @return [Sidekiq::Worker]
44
- def worker_class_constantize(klazz = @worker_class)
45
- return klazz unless klazz.is_a?(String)
51
+ def worker_class_constantize(klazz)
52
+ return klazz.class if klazz.is_a?(Sidekiq::Worker) # sidekiq v6.x
53
+ return klazz unless klazz.is_a?(String)
54
+
46
55
  Object.const_get(klazz)
47
56
  rescue NameError => ex
48
57
  case ex.message
@@ -2,8 +2,8 @@
2
2
 
3
3
  # :nocov:
4
4
 
5
- require 'sidekiq'
6
- require 'sidekiq/testing'
5
+ require "sidekiq"
6
+ require "sidekiq/testing"
7
7
 
8
8
  module Sidekiq
9
9
  def self.use_options(tmp_config = {})
@@ -52,7 +52,7 @@ module Sidekiq
52
52
  module Testing
53
53
  def clear_all_ext
54
54
  clear_all_orig
55
- SidekiqUniqueJobs::Util.del('*', 1000)
55
+ SidekiqUniqueJobs::Util.del("*", 1000)
56
56
  end
57
57
  end
58
58
  end
@@ -25,6 +25,7 @@ module SidekiqUniqueJobs
25
25
  # @return [Integer] the number of seconds until job is scheduled
26
26
  def time_until_scheduled
27
27
  return 0 unless scheduled_at
28
+
28
29
  scheduled_at.to_i - Time.now.utc.to_i
29
30
  end
30
31
 
@@ -39,7 +40,7 @@ module SidekiqUniqueJobs
39
40
  @lock_expiration ||= begin
40
41
  expiration = item[LOCK_EXPIRATION_KEY]
41
42
  expiration ||= worker_options[LOCK_EXPIRATION_KEY]
42
- expiration && expiration + time_until_scheduled
43
+ expiration && expiration.to_i + time_until_scheduled
43
44
  end
44
45
  end
45
46
 
@@ -5,4 +5,4 @@ module SidekiqUniqueJobs
5
5
  end
6
6
  end
7
7
 
8
- require 'sidekiq_unique_jobs/timeout/calculator'
8
+ require "sidekiq_unique_jobs/timeout/calculator"
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
4
- require 'sidekiq_unique_jobs/normalizer'
3
+ require "digest"
4
+ require "openssl"
5
+ require "sidekiq_unique_jobs/normalizer"
5
6
 
6
7
  module SidekiqUniqueJobs
7
8
  # Handles uniqueness of sidekiq arguments
@@ -47,7 +48,7 @@ module SidekiqUniqueJobs
47
48
  # Creates a namespaced unique digest based on the {#digestable_hash} and the {#unique_prefix}
48
49
  # @return [String] a unique digest
49
50
  def create_digest
50
- digest = Digest::MD5.hexdigest(Sidekiq.dump_json(digestable_hash))
51
+ digest = OpenSSL::Digest::MD5.hexdigest(Sidekiq.dump_json(digestable_hash))
51
52
  "#{unique_prefix}:#{digest}"
52
53
  end
53
54
 
@@ -60,7 +61,7 @@ module SidekiqUniqueJobs
60
61
  # Filter a hash to use for digest
61
62
  # @return [Hash] to use for digest
62
63
  def digestable_hash
63
- @item.slice(CLASS_KEY, QUEUE_KEY, UNIQUE_ARGS_KEY).tap do |hash|
64
+ @item.slice(CLASS_KEY, QUEUE_KEY, UNIQUE_ARGS_KEY, APARTMENT).tap do |hash|
64
65
  hash.delete(QUEUE_KEY) if unique_across_queues?
65
66
  hash.delete(CLASS_KEY) if unique_across_workers?
66
67
  end
@@ -70,6 +71,7 @@ module SidekiqUniqueJobs
70
71
  # @return [Array] the arguments filters by the {#filtered_args} method if {#unique_args_enabled?}
71
72
  def unique_args(args)
72
73
  return filtered_args(args) if unique_args_enabled?
74
+
73
75
  args
74
76
  end
75
77
 
@@ -99,6 +101,7 @@ module SidekiqUniqueJobs
99
101
  # @return [Array] args unfiltered when neither of the above
100
102
  def filtered_args(args)
101
103
  return args if args.empty?
104
+
102
105
  json_args = Normalizer.jsonify(args)
103
106
 
104
107
  case unique_args_method
@@ -7,11 +7,11 @@ module SidekiqUniqueJobs
7
7
  # @author Mikael Henriksson <mikael@zoolutions.se>
8
8
  module Util
9
9
  DEFAULT_COUNT = 1_000
10
- SCAN_PATTERN = '*'
10
+ SCAN_PATTERN = "*"
11
11
 
12
12
  include SidekiqUniqueJobs::Logging
13
13
  include SidekiqUniqueJobs::Connection
14
- extend self # rubocop:disable Style/ModuleFunction
14
+ extend self
15
15
 
16
16
  # Find unique keys in redis
17
17
  #
@@ -20,6 +20,7 @@ module SidekiqUniqueJobs
20
20
  # @return [Array<String>] an array with active unique keys
21
21
  def keys(pattern = SCAN_PATTERN, count = DEFAULT_COUNT)
22
22
  return redis(&:keys) if pattern.nil?
23
+
23
24
  redis { |conn| conn.scan_each(match: prefix(pattern), count: count).to_a }
24
25
  end
25
26
 
@@ -43,7 +44,8 @@ module SidekiqUniqueJobs
43
44
  # @param [Integer] count the maximum number of keys to delete
44
45
  # @return [Integer] the number of keys deleted
45
46
  def del(pattern = SCAN_PATTERN, count = 0)
46
- raise ArgumentError, 'Please provide a number of keys to delete greater than zero' if count.zero?
47
+ raise ArgumentError, "Please provide a number of keys to delete greater than zero" if count.zero?
48
+
47
49
  pattern = suffix(pattern)
48
50
 
49
51
  log_debug { "Deleting keys by: #{pattern}" }
@@ -84,11 +86,13 @@ module SidekiqUniqueJobs
84
86
  def prefix(key)
85
87
  return key if unique_prefix.nil?
86
88
  return key if key.start_with?("#{unique_prefix}:")
89
+
87
90
  "#{unique_prefix}:#{key}"
88
91
  end
89
92
 
90
93
  def suffix(key)
91
- return "#{key}*" unless key.end_with?(':*')
94
+ return "#{key}*" unless key.end_with?(":*")
95
+
92
96
  key
93
97
  end
94
98
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SidekiqUniqueJobs
4
- VERSION = '6.0.6'
4
+ VERSION = "6.0.25"
5
5
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqUniqueJobs
4
+ #
5
+ # Handles checking if a version is compliant with given constraint
6
+ #
7
+ # @author Mikael Henriksson <mikael@zoolutions.se>
8
+ #
9
+ class VersionCheck
10
+ PATTERN = /(?<operator1>[<>=]+)?\s?(?<version1>(\d+.?)+)(\s+&&\s+)?(?<operator2>[<>=]+)?\s?(?<version2>(\d+.?)+)?/m.freeze # rubocop:disable Layout/LineLength
11
+
12
+ #
13
+ # Checks if a version is consrtaint is satisfied
14
+ #
15
+ # @example A satisfied constraint
16
+ # VersionCheck.satisfied?("5.0.0", ">= 4.0.0") #=> true
17
+ #
18
+ # @example An unsatisfied constraint
19
+ # VersionCheck.satisfied?("5.0.0", "<= 4.0.0") #=> false
20
+ #
21
+ #
22
+ # @param [String] version a version string `5.0.0`
23
+ # @param [String] constraint a version constraint `>= 5.0.0 <= 5.1.1`
24
+ #
25
+ # @return [<type>] <description>
26
+ #
27
+ def self.satisfied?(version, constraint)
28
+ new(version, constraint).satisfied?
29
+ end
30
+
31
+ #
32
+ # @!attribute [r] version
33
+ # @return [String] a version string `5.0.0`
34
+ attr_reader :version
35
+ #
36
+ # @!attribute [r] match
37
+ # @return [String] a version constraint `>= 5.0.0 <= 5.1.1`
38
+ attr_reader :match
39
+
40
+ #
41
+ # Initialize a new VersionCheck instance
42
+ #
43
+ # @param [String] version a version string `5.0.0`
44
+ # @param [String] constraint a version constraint `>= 5.0.0 <= 5.1.1`
45
+ #
46
+ def initialize(version, constraint)
47
+ @version = Gem::Version.new(version)
48
+ @match = PATTERN.match(constraint.to_s)
49
+
50
+ raise ArgumentError, "A version (eg. 5.0) is required to compare against" unless @version
51
+ raise ArgumentError, "At least one operator and version is required (eg. >= 5.1)" unless constraint
52
+ end
53
+
54
+ #
55
+ # Checks if all constraints were met
56
+ #
57
+ #
58
+ # @return [true,false]
59
+ #
60
+ def satisfied?
61
+ constraints.all? do |expected, operator|
62
+ compare(expected, operator)
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def compare(expected, operator)
69
+ Gem::Version.new(version).send(operator, Gem::Version.new(expected))
70
+ end
71
+
72
+ def constraints
73
+ result = { version_one => operator_one }
74
+ result[version_two] = operator_two if version_two
75
+
76
+ result
77
+ end
78
+
79
+ def version_one
80
+ match[:version1]
81
+ end
82
+
83
+ def operator_one
84
+ match[:operator1]
85
+ end
86
+
87
+ def version_two
88
+ match[:version2]
89
+ end
90
+
91
+ def operator_two
92
+ match[:operator2]
93
+ end
94
+ end
95
+ end
@@ -3,7 +3,7 @@
3
3
  module SidekiqUniqueJobs
4
4
  module Web
5
5
  module Helpers
6
- VIEW_PATH = File.expand_path('../web/views', __dir__)
6
+ VIEW_PATH = File.expand_path("../web/views", __dir__)
7
7
 
8
8
  def unique_template(name)
9
9
  File.open(File.join(VIEW_PATH, "#{name}.erb")).read
@@ -19,8 +19,9 @@ module SidekiqUniqueJobs
19
19
 
20
20
  params.merge(options).map do |key, value|
21
21
  next unless SAFE_CPARAMS.include?(key)
22
+
22
23
  "#{key}=#{CGI.escape(value.to_s)}"
23
- end.compact.join('&')
24
+ end.compact.join("&")
24
25
  end
25
26
 
26
27
  def redirect_to(subpath)
@@ -35,10 +36,10 @@ module SidekiqUniqueJobs
35
36
 
36
37
  def safe_relative_time(time)
37
38
  time = if time.is_a?(Numeric)
38
- Time.at(time)
39
- else
40
- Time.parse(time)
41
- end
39
+ Time.at(time)
40
+ else
41
+ Time.parse(time)
42
+ end
42
43
 
43
44
  relative_time(time)
44
45
  end
@@ -38,5 +38,9 @@
38
38
  </tr>
39
39
  <% end %>
40
40
  </table>
41
+
42
+ <form action="<%= root_path %>unique_digests/delete_all" method="get">
43
+ <input class="btn btn-danger btn-xs" type="submit" name="delete_all" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSure') %>" />
44
+ </form>
41
45
  </div>
42
46
  <% end %>
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require 'sidekiq/web'
5
- rescue LoadError # rubocop:disable Lint/HandleExceptions
4
+ require "delegate"
5
+ require "rack"
6
+ require "sidekiq/web"
7
+ rescue LoadError
6
8
  # client-only usage
7
9
  end
8
10
 
9
- require_relative 'web/helpers'
11
+ require_relative "web/helpers"
10
12
 
11
13
  module SidekiqUniqueJobs
12
14
  # Utility module to help manage unique keys in redis.
@@ -19,27 +21,32 @@ module SidekiqUniqueJobs
19
21
  include Web::Helpers
20
22
  end
21
23
 
22
- app.get '/unique_digests' do
23
- @filter = params[:filter] || '*'
24
- @filter = '*' if @filter == ''
24
+ app.get "/unique_digests" do
25
+ @filter = params[:filter] || "*"
26
+ @filter = "*" if @filter == ""
25
27
  @count = (params[:count] || 100).to_i
26
28
  @current_cursor = params[:cursor]
27
29
  @prev_cursor = params[:prev_cursor]
28
30
  @total_size, @next_cursor, @unique_digests =
29
- Digests.page(pattern: @filter, cursor: @current_cursor, page_size: @count)
31
+ SidekiqUniqueJobs::Digests.page(pattern: @filter, cursor: @current_cursor, page_size: @count)
30
32
 
31
33
  erb(unique_template(:unique_digests))
32
34
  end
33
35
 
34
- app.get '/unique_digests/:digest' do
36
+ app.get "/unique_digests/delete_all" do
37
+ SidekiqUniqueJobs::Digests.delete_by_pattern("*", count: SidekiqUniqueJobs::Digests.count)
38
+ redirect_to :unique_digests
39
+ end
40
+
41
+ app.get "/unique_digests/:digest" do
35
42
  @digest = params[:digest]
36
- @unique_keys = Util.keys("#{@digest}*", 1000)
43
+ @unique_keys = SidekiqUniqueJobs::Util.keys("#{@digest}*", 1000)
37
44
 
38
45
  erb(unique_template(:unique_digest))
39
46
  end
40
47
 
41
- app.get '/unique_digests/:digest/delete' do
42
- Digests.del(digest: params[:digest])
48
+ app.get "/unique_digests/:digest/delete" do
49
+ SidekiqUniqueJobs::Digests.delete_by_digest(params[:digest])
43
50
  redirect_to :unique_digests
44
51
  end
45
52
  end
@@ -48,6 +55,6 @@ end
48
55
 
49
56
  if defined?(Sidekiq::Web)
50
57
  Sidekiq::Web.register SidekiqUniqueJobs::Web
51
- Sidekiq::Web.tabs['Unique Digests'] = 'unique_digests'
58
+ Sidekiq::Web.tabs["Unique Digests"] = "unique_digests"
52
59
  # Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), 'locales')
53
60
  end