procrastinator 1.0.0.pre.rc2 → 1.0.0.pre.rc3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0d5fdab8cda8f1e8eee673be60373214b9619e6dfccbc8e1859bfd13d8f2709
4
- data.tar.gz: 932688bb438481332e0ad0f22bcb243760400fafe54a641db4687fb7242d28f2
3
+ metadata.gz: 40bd65d0f873fba25ce55e8ab2e00934c15340ae3106d64e461abee8a08ff043
4
+ data.tar.gz: f28a1f18b563a29f8d4f3030ae8df273b47d387fdde7b03e4ed92dc40eebf810
5
5
  SHA512:
6
- metadata.gz: c6d67aa9803f623778a2660075be6634f31f23a18463b2cd86cce55ccbba6d4c009febd2be35bcc411a635a530c10beaee8eb6f523d9312767ac77f89e59ca2a
7
- data.tar.gz: 2f0dc42e8824e40aee38526e0f4963c7347de9290c4b0b061e77e498282f8c40055ad477c7b0c6dfda78440bbd8d78d7b1c44040356aafc8c2ec0b0d1f582bc5
6
+ metadata.gz: '084cbefb918ee3193ef0589712d6ddbf2fde592d3f43c0d8b080e6816956c32d7f00fda8f136ee3d0ffa953314c288f195d7343eec110d0156386a72b792fe4f'
7
+ data.tar.gz: fcdbe66ed04995bdf89a6b4a13ddf5b9dc0302257d3f78521d9c28bbf7b81b227357254884afb56258a5191c7927588266d608e0aabc213121657d9107999a4f
@@ -19,36 +19,68 @@ module Procrastinator
19
19
  #
20
20
  # @param (see #define)
21
21
  # @see DaemonTasks#define
22
- def self.define(**args)
23
- new.define(**args)
22
+ def self.define(**args, &block)
23
+ new.define(**args, &block)
24
24
  end
25
25
 
26
26
  # Defines procrastinator:start and procrastinator:stop Rake tasks that operate on the given scheduler.
27
- # If provided a block, that block will run in the daemon process.
28
27
  #
29
- # @param scheduler [Procrastinator::Scheduler]
30
- # @param pid_path [Pathname, File, String, nil]
31
- def define(scheduler:, pid_path: nil, &block)
32
- pid_path = Scheduler::DaemonWorking.normalize_pid(pid_path)
28
+ # @param pid_path [Pathname, File, String, nil] The pid file path
29
+ # @yieldreturn scheduler [Procrastinator::Scheduler]
30
+ #
31
+ # @see Scheduler::DaemonWorking#daemonized!
32
+ def define(pid_path: nil)
33
+ raise ArgumentError, 'must provide a scheduler builder block' unless block_given?
34
+
35
+ @pid_path = Scheduler::DaemonWorking.normalize_pid pid_path
33
36
 
34
37
  namespace :procrastinator do
38
+ desc 'Start the Procrastinator daemon'
35
39
  task :start do
36
- scheduler.work.daemonized!(pid_path, &block)
40
+ start(yield)
37
41
  end
38
42
 
43
+ desc 'Show Procrastinator daemon status'
39
44
  task :status do
40
- if Scheduler::DaemonWorking.running?(pid_path)
41
- warn "Procrastinator instance running (pid #{ File.read(pid_path) })"
42
- else
43
- warn "No Procrastinator instance detected for #{ pid_path }"
44
- end
45
+ status
45
46
  end
46
47
 
47
- task :stop do
48
- Scheduler::DaemonWorking.halt!(pid_path)
48
+ desc 'Stop the Procrastinator daemon'
49
+ task stop: [:status] do
50
+ stop
49
51
  end
52
+
53
+ desc 'Restart Procrastinator daemon'
54
+ task restart: [:stop, :start]
50
55
  end
51
56
  end
57
+
58
+ private
59
+
60
+ def start(scheduler)
61
+ warn 'Starting Procrastinator'
62
+ scheduler.work.daemonized!(@pid_path)
63
+ end
64
+
65
+ def status
66
+ warn "Checking #{ @pid_path }..."
67
+ msg = if Scheduler::DaemonWorking.running?(@pid_path)
68
+ "Procrastinator pid #{ File.read(@pid_path) } instance running."
69
+ elsif File.exist?(@pid_path)
70
+ "Procrastinator pid #{ File.read(@pid_path) } is not running. Maybe it crashed?"
71
+ else
72
+ "Procrastinator is not running (No such file - #{ @pid_path })"
73
+ end
74
+
75
+ warn msg
76
+ end
77
+
78
+ def stop
79
+ return unless Scheduler::DaemonWorking.running?(@pid_path)
80
+
81
+ Scheduler::DaemonWorking.halt!(@pid_path)
82
+ warn "Procrastinator pid #{ File.read(@pid_path) } halted."
83
+ end
52
84
  end
53
85
  end
54
86
  end
@@ -47,7 +47,7 @@ module Procrastinator
47
47
  UpdateProxy.new(@config, identifier: identifier.merge(queue: queue.to_s))
48
48
  end
49
49
 
50
- # Removes an existing task, as located by the givne identifying information.
50
+ # Removes an existing task, as located by the given identifying information.
51
51
  #
52
52
  # The identifier can include any data field stored in the task loader. Often this is the information in :data.
53
53
  #
@@ -249,28 +249,27 @@ module Procrastinator
249
249
  # 15 chars is linux limit
250
250
  MAX_PROC_LEN = 15
251
251
 
252
- # Consumes the current process and turns it into a background daemon. A log will be started in the log
253
- # directory defined in the configuration block.
252
+ # Consumes the current process and turns it into a background daemon and proceed as #threaded.
253
+ # Additional logging is recorded in the directory specified by the Procrastinator.setup configuration.
254
254
  #
255
255
  # If pid_path ends with extension '.pid', the basename will be requested as process title (depending on OS
256
256
  # support). An extensionless path is assumed to be a directory and a default filename (and proctitle) is used.
257
257
  #
258
258
  # @param pid_path [Pathname, File, String, nil] Path to where the process ID file is to be kept.
259
- # @yield [void] Block to run after daemonization
260
- def daemonized!(pid_path = nil, &block)
261
- spawn_daemon(pid_path, &block)
259
+ def daemonized!(pid_path = nil)
260
+ spawn_daemon(pid_path)
262
261
 
263
262
  threaded
264
263
  end
265
264
 
266
265
  # Normalizes the given pid path, including conversion to absolute path and defaults.
267
266
  #
268
- # @param pid_path [Pathname, String] path to normalize
267
+ # @param pid_path [Pathname, File, String, nil] path to normalize
269
268
  def self.normalize_pid(pid_path)
270
- pid_path = Pathname.new(pid_path || DEFAULT_PID_DIR)
271
- pid_path /= "#{ PROG_NAME.downcase }#{ PID_EXT }" unless pid_path.extname == PID_EXT
269
+ normalized = Pathname.new(pid_path || DEFAULT_PID_DIR)
270
+ normalized /= "#{ PROG_NAME.downcase }#{ PID_EXT }" unless normalized.extname == PID_EXT
272
271
 
273
- pid_path.expand_path
272
+ normalized.expand_path
274
273
  end
275
274
 
276
275
  # Stops the procrastinator process denoted by the provided pid file
@@ -287,15 +286,13 @@ module Procrastinator
287
286
  Process.getpgid pid
288
287
 
289
288
  true
290
- rescue Errno::ESRCH
289
+ rescue Errno::ENOENT, Errno::ESRCH
291
290
  false
292
291
  end
293
292
 
294
293
  private
295
294
 
296
- # "You, search from the spastic dentistry department down through disembowelment. You, cover children's dance
297
- # recitals through holiday weekend IKEA. Go."
298
- def spawn_daemon(pid_path, &block)
295
+ def spawn_daemon(pid_path)
299
296
  pid_path = DaemonWorking.normalize_pid pid_path
300
297
 
301
298
  open_log quiet: true
@@ -303,14 +300,14 @@ module Procrastinator
303
300
 
304
301
  print_debug_context
305
302
 
303
+ # "You, search from the spastic dentistry department down through disembowelment.
304
+ # You, cover children's dance recitals through holiday weekend IKEA. Go."
306
305
  Process.daemon
307
306
 
308
307
  manage_pid pid_path
309
308
  rename_process pid_path
310
-
311
- yield if block
312
309
  rescue StandardError => e
313
- @logger.fatal ([e.message] + e.backtrace).join("\n")
310
+ @logger&.fatal ([e.message] + e.backtrace).join("\n")
314
311
  raise e
315
312
  end
316
313
 
@@ -346,10 +343,10 @@ module Procrastinator
346
343
  end
347
344
 
348
345
  def print_debug_context
349
- @logger.debug "Ruby Path: #{ ENV['RUBY_ROOT'] }"
350
- @logger.debug "Bundler Path: #{ ENV['BUNDLE_BIN_PATH'] }"
351
- # logname is the posix standard and is set by cron, so probably reliable.
352
- @logger.debug "Runtime User: #{ ENV['LOGNAME'] || ENV['USERNAME'] }"
346
+ @logger.debug "Ruby Path: #{ ENV.fetch 'RUBY_ROOT' }"
347
+ @logger.debug "Bundler Path: #{ ENV.fetch 'BUNDLE_BIN_PATH' }"
348
+ # LOGNAME is the posix standard and is set by cron, so probably reliable.
349
+ @logger.debug "Runtime User: #{ ENV.fetch('LOGNAME') || ENV.fetch('USERNAME') }"
353
350
  end
354
351
 
355
352
  def rename_process(pid_path)
@@ -54,8 +54,8 @@ module Procrastinator
54
54
  end
55
55
 
56
56
  def read(filter = {})
57
- FileTransaction.new(@path).read do |existing_data|
58
- parse(existing_data).select do |row|
57
+ CSVFileTransaction.new(@path).read do |existing_data|
58
+ existing_data.select do |row|
59
59
  filter.keys.all? do |key|
60
60
  row[key] == filter[key]
61
61
  end
@@ -70,8 +70,7 @@ module Procrastinator
70
70
  # @param initial_run_at [Time, nil] first time to run the task at. Defaults to run_at.
71
71
  # @param expire_at [Time, nil] time to expire the task
72
72
  def create(queue:, run_at:, expire_at:, data: '', initial_run_at: nil)
73
- FileTransaction.new(@path).write do |existing_data|
74
- tasks = parse(existing_data)
73
+ CSVFileTransaction.new(@path).write do |tasks|
75
74
  max_id = tasks.collect { |task| task[:id] }.max || 0
76
75
 
77
76
  new_data = {
@@ -89,8 +88,7 @@ module Procrastinator
89
88
  end
90
89
 
91
90
  def update(id, data)
92
- FileTransaction.new(@path).write do |existing_data|
93
- tasks = parse(existing_data)
91
+ CSVFileTransaction.new(@path).write do |tasks|
94
92
  task_data = tasks.find do |task|
95
93
  task[:id] == id
96
94
  end
@@ -102,8 +100,7 @@ module Procrastinator
102
100
  end
103
101
 
104
102
  def delete(id)
105
- FileTransaction.new(@path).write do |file_content|
106
- existing_data = parse(file_content)
103
+ CSVFileTransaction.new(@path).write do |existing_data|
107
104
  generate(existing_data.reject { |task| task[:id] == id })
108
105
  end
109
106
  end
@@ -121,39 +118,45 @@ module Procrastinator
121
118
  lines.join("\n") << "\n"
122
119
  end
123
120
 
124
- private
121
+ # Adds CSV parsing to the file reading
122
+ class CSVFileTransaction < FileTransaction
123
+ def transact(writable: nil)
124
+ super(writable: writable) do |file_str|
125
+ yield(parse(file_str))
126
+ end
127
+ end
125
128
 
126
- def parse(csv_string)
127
- data = CSV.parse(csv_string,
128
- headers: true,
129
- header_converters: :symbol,
130
- skip_blanks: true,
131
- converters: READ_CONVERTER,
132
- force_quotes: true).to_a
129
+ private
133
130
 
134
- headers = data.shift || HEADERS
131
+ def parse(csv_string)
132
+ data = CSV.parse(csv_string,
133
+ headers: true, header_converters: :symbol,
134
+ skip_blanks: true, converters: READ_CONVERTER, force_quotes: true).to_a
135
135
 
136
- data = data.collect do |d|
137
- headers.zip(d).to_h
138
- end
136
+ headers = data.shift || HEADERS
139
137
 
140
- correct_types(data)
141
- end
138
+ data = data.collect do |d|
139
+ headers.zip(d).to_h
140
+ end
142
141
 
143
- def correct_types(data)
144
- non_empty_keys = [:run_at, :expire_at, :attempts, :last_fail_at]
142
+ correct_types(data)
143
+ end
145
144
 
146
- data.collect do |hash|
147
- non_empty_keys.each do |key|
148
- hash.delete(key) if hash[key].is_a?(String) && hash[key].empty?
149
- end
145
+ def correct_types(data)
146
+ non_empty_keys = [:run_at, :expire_at, :attempts, :last_fail_at]
150
147
 
151
- hash[:attempts] ||= 0
148
+ data.collect do |hash|
149
+ non_empty_keys.each do |key|
150
+ hash.delete(key) if hash[key].is_a?(String) && hash[key].empty?
151
+ end
152
152
 
153
- # hash[:data] = (hash[:data] || '').gsub('""', '"')
154
- hash[:queue] = hash[:queue].to_sym
153
+ hash[:attempts] ||= 0
155
154
 
156
- hash
155
+ # hash[:data] = (hash[:data] || '').gsub('""', '"')
156
+ hash[:queue] = hash[:queue].to_sym
157
+
158
+ hash
159
+ end
157
160
  end
158
161
  end
159
162
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Procrastinator
4
- VERSION = '1.0.0-rc2'
4
+ VERSION = '1.0.0-rc3'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: procrastinator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.rc2
4
+ version: 1.0.0.pre.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robin Miller
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-14 00:00:00.000000000 Z
11
+ date: 2022-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler