fractor 0.1.6 → 0.1.8
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +227 -102
- data/README.adoc +113 -1940
- data/docs/.lycheeignore +16 -0
- data/docs/Gemfile +24 -0
- data/docs/README.md +157 -0
- data/docs/_config.yml +151 -0
- data/docs/_features/error-handling.adoc +1192 -0
- data/docs/_features/index.adoc +80 -0
- data/docs/_features/monitoring.adoc +589 -0
- data/docs/_features/signal-handling.adoc +202 -0
- data/docs/_features/workflows.adoc +1235 -0
- data/docs/_guides/continuous-mode.adoc +736 -0
- data/docs/_guides/cookbook.adoc +1133 -0
- data/docs/_guides/index.adoc +55 -0
- data/docs/_guides/pipeline-mode.adoc +730 -0
- data/docs/_guides/troubleshooting.adoc +358 -0
- data/docs/_pages/architecture.adoc +1390 -0
- data/docs/_pages/core-concepts.adoc +1392 -0
- data/docs/_pages/design-principles.adoc +862 -0
- data/docs/_pages/getting-started.adoc +290 -0
- data/docs/_pages/installation.adoc +143 -0
- data/docs/_reference/api.adoc +1080 -0
- data/docs/_reference/error-reporting.adoc +670 -0
- data/docs/_reference/examples.adoc +181 -0
- data/docs/_reference/index.adoc +96 -0
- data/docs/_reference/troubleshooting.adoc +862 -0
- data/docs/_tutorials/complex-workflows.adoc +1022 -0
- data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
- data/docs/_tutorials/first-application.adoc +384 -0
- data/docs/_tutorials/index.adoc +48 -0
- data/docs/_tutorials/long-running-services.adoc +931 -0
- data/docs/assets/images/favicon-16.png +0 -0
- data/docs/assets/images/favicon-32.png +0 -0
- data/docs/assets/images/favicon-48.png +0 -0
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/images/favicon.svg +45 -0
- data/docs/assets/images/fractor-icon.svg +49 -0
- data/docs/assets/images/fractor-logo.svg +61 -0
- data/docs/index.adoc +131 -0
- data/docs/lychee.toml +39 -0
- data/examples/api_aggregator/README.adoc +627 -0
- data/examples/api_aggregator/api_aggregator.rb +376 -0
- data/examples/auto_detection/README.adoc +407 -29
- data/examples/continuous_chat_common/message_protocol.rb +1 -1
- data/examples/error_reporting.rb +207 -0
- data/examples/file_processor/README.adoc +170 -0
- data/examples/file_processor/file_processor.rb +615 -0
- data/examples/file_processor/sample_files/invalid.csv +1 -0
- data/examples/file_processor/sample_files/orders.xml +24 -0
- data/examples/file_processor/sample_files/products.json +23 -0
- data/examples/file_processor/sample_files/users.csv +6 -0
- data/examples/hierarchical_hasher/README.adoc +629 -41
- data/examples/image_processor/README.adoc +610 -0
- data/examples/image_processor/image_processor.rb +349 -0
- data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
- data/examples/image_processor/test_images/sample_1.png +1 -0
- data/examples/image_processor/test_images/sample_10.png +1 -0
- data/examples/image_processor/test_images/sample_2.png +1 -0
- data/examples/image_processor/test_images/sample_3.png +1 -0
- data/examples/image_processor/test_images/sample_4.png +1 -0
- data/examples/image_processor/test_images/sample_5.png +1 -0
- data/examples/image_processor/test_images/sample_6.png +1 -0
- data/examples/image_processor/test_images/sample_7.png +1 -0
- data/examples/image_processor/test_images/sample_8.png +1 -0
- data/examples/image_processor/test_images/sample_9.png +1 -0
- data/examples/log_analyzer/README.adoc +662 -0
- data/examples/log_analyzer/log_analyzer.rb +579 -0
- data/examples/log_analyzer/sample_logs/apache.log +20 -0
- data/examples/log_analyzer/sample_logs/json.log +15 -0
- data/examples/log_analyzer/sample_logs/nginx.log +15 -0
- data/examples/log_analyzer/sample_logs/rails.log +29 -0
- data/examples/multi_work_type/README.adoc +576 -26
- data/examples/performance_monitoring.rb +120 -0
- data/examples/pipeline_processing/README.adoc +740 -26
- data/examples/pipeline_processing/pipeline_processing.rb +2 -2
- data/examples/priority_work_example.rb +155 -0
- data/examples/producer_subscriber/README.adoc +889 -46
- data/examples/scatter_gather/README.adoc +829 -27
- data/examples/simple/README.adoc +347 -0
- data/examples/specialized_workers/README.adoc +622 -26
- data/examples/specialized_workers/specialized_workers.rb +44 -8
- data/examples/stream_processor/README.adoc +206 -0
- data/examples/stream_processor/stream_processor.rb +284 -0
- data/examples/web_scraper/README.adoc +625 -0
- data/examples/web_scraper/web_scraper.rb +285 -0
- data/examples/workflow/README.adoc +406 -0
- data/examples/workflow/circuit_breaker/README.adoc +360 -0
- data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
- data/examples/workflow/conditional/README.adoc +483 -0
- data/examples/workflow/conditional/conditional_workflow.rb +215 -0
- data/examples/workflow/dead_letter_queue/README.adoc +374 -0
- data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
- data/examples/workflow/fan_out/README.adoc +381 -0
- data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
- data/examples/workflow/retry/README.adoc +248 -0
- data/examples/workflow/retry/retry_workflow.rb +195 -0
- data/examples/workflow/simple_linear/README.adoc +267 -0
- data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
- data/examples/workflow/simplified/README.adoc +329 -0
- data/examples/workflow/simplified/simplified_workflow.rb +222 -0
- data/exe/fractor +10 -0
- data/lib/fractor/cli.rb +288 -0
- data/lib/fractor/configuration.rb +307 -0
- data/lib/fractor/continuous_server.rb +60 -65
- data/lib/fractor/error_formatter.rb +72 -0
- data/lib/fractor/error_report_generator.rb +152 -0
- data/lib/fractor/error_reporter.rb +244 -0
- data/lib/fractor/error_statistics.rb +147 -0
- data/lib/fractor/execution_tracer.rb +162 -0
- data/lib/fractor/logger.rb +230 -0
- data/lib/fractor/main_loop_handler.rb +406 -0
- data/lib/fractor/main_loop_handler3.rb +135 -0
- data/lib/fractor/main_loop_handler4.rb +299 -0
- data/lib/fractor/performance_metrics_collector.rb +181 -0
- data/lib/fractor/performance_monitor.rb +215 -0
- data/lib/fractor/performance_report_generator.rb +202 -0
- data/lib/fractor/priority_work.rb +93 -0
- data/lib/fractor/priority_work_queue.rb +189 -0
- data/lib/fractor/result_aggregator.rb +32 -0
- data/lib/fractor/shutdown_handler.rb +168 -0
- data/lib/fractor/signal_handler.rb +80 -0
- data/lib/fractor/supervisor.rb +382 -269
- data/lib/fractor/supervisor_logger.rb +88 -0
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/work.rb +12 -0
- data/lib/fractor/work_distribution_manager.rb +151 -0
- data/lib/fractor/work_queue.rb +20 -0
- data/lib/fractor/work_result.rb +181 -9
- data/lib/fractor/worker.rb +73 -0
- data/lib/fractor/workflow/builder.rb +210 -0
- data/lib/fractor/workflow/chain_builder.rb +169 -0
- data/lib/fractor/workflow/circuit_breaker.rb +183 -0
- data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
- data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
- data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
- data/lib/fractor/workflow/execution_hooks.rb +39 -0
- data/lib/fractor/workflow/execution_strategy.rb +225 -0
- data/lib/fractor/workflow/execution_trace.rb +134 -0
- data/lib/fractor/workflow/helpers.rb +191 -0
- data/lib/fractor/workflow/job.rb +290 -0
- data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
- data/lib/fractor/workflow/logger.rb +110 -0
- data/lib/fractor/workflow/pre_execution_context.rb +193 -0
- data/lib/fractor/workflow/retry_config.rb +156 -0
- data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
- data/lib/fractor/workflow/retry_strategy.rb +93 -0
- data/lib/fractor/workflow/structured_logger.rb +30 -0
- data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
- data/lib/fractor/workflow/visualizer.rb +211 -0
- data/lib/fractor/workflow/workflow_context.rb +132 -0
- data/lib/fractor/workflow/workflow_executor.rb +669 -0
- data/lib/fractor/workflow/workflow_result.rb +55 -0
- data/lib/fractor/workflow/workflow_validator.rb +295 -0
- data/lib/fractor/workflow.rb +333 -0
- data/lib/fractor/wrapped_ractor.rb +66 -101
- data/lib/fractor/wrapped_ractor3.rb +161 -0
- data/lib/fractor/wrapped_ractor4.rb +242 -0
- data/lib/fractor.rb +92 -4
- metadata +179 -6
- data/tests/sample.rb.bak +0 -309
- data/tests/sample_working.rb.bak +0 -209
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../../lib/fractor"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "json"
|
|
7
|
+
|
|
8
|
+
module ImageProcessor
|
|
9
|
+
# ImageWork encapsulates an image file and the processing operations to perform
|
|
10
|
+
class ImageWork < Fractor::Work
|
|
11
|
+
def initialize(input_path, output_path, operations = {})
|
|
12
|
+
super({
|
|
13
|
+
input_path: input_path,
|
|
14
|
+
output_path: output_path,
|
|
15
|
+
operations: operations
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def input_path
|
|
20
|
+
input[:input_path]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def output_path
|
|
24
|
+
input[:output_path]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def operations
|
|
28
|
+
input[:operations]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
"ImageWork(#{File.basename(input_path)} -> #{operations.keys.join(", ")})"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# ImageProcessorWorker performs image processing operations
|
|
37
|
+
class ImageProcessorWorker < Fractor::Worker
|
|
38
|
+
def process(work)
|
|
39
|
+
unless work.is_a?(ImageWork)
|
|
40
|
+
raise ArgumentError, "Expected ImageWork, got #{work.class}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Simulate image processing without requiring actual image libraries
|
|
44
|
+
# In real implementation, would use mini_magick or similar
|
|
45
|
+
process_image(work)
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
input: work.input_path,
|
|
49
|
+
output: work.output_path,
|
|
50
|
+
operations: work.operations,
|
|
51
|
+
status: "success",
|
|
52
|
+
file_size: simulate_file_size(work),
|
|
53
|
+
processing_time: rand(0.1..0.5)
|
|
54
|
+
}
|
|
55
|
+
rescue ArgumentError => e
|
|
56
|
+
# Re-raise ArgumentError for invalid work type
|
|
57
|
+
raise e
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
{
|
|
60
|
+
input: work.input_path,
|
|
61
|
+
output: work.output_path,
|
|
62
|
+
status: "error",
|
|
63
|
+
error: e.message
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def process_image(work)
|
|
70
|
+
# Validate input file exists (in real scenario)
|
|
71
|
+
unless File.exist?(work.input_path)
|
|
72
|
+
raise "Input file not found: #{work.input_path}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Create output directory if needed
|
|
76
|
+
FileUtils.mkdir_p(File.dirname(work.output_path))
|
|
77
|
+
|
|
78
|
+
# Simulate processing based on operations
|
|
79
|
+
operations = work.operations
|
|
80
|
+
|
|
81
|
+
# Simulate different processing times based on operations
|
|
82
|
+
sleep_time = 0.05 # Base time
|
|
83
|
+
|
|
84
|
+
if operations[:resize]
|
|
85
|
+
sleep_time += 0.02
|
|
86
|
+
validate_resize_params(operations[:resize])
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if operations[:convert]
|
|
90
|
+
sleep_time += 0.01
|
|
91
|
+
validate_format(operations[:convert])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if operations[:filter]
|
|
95
|
+
sleep_time += 0.03
|
|
96
|
+
validate_filter(operations[:filter])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if operations[:brightness]
|
|
100
|
+
sleep_time += 0.01
|
|
101
|
+
validate_brightness(operations[:brightness])
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Simulate processing time
|
|
105
|
+
sleep(sleep_time)
|
|
106
|
+
|
|
107
|
+
# In real implementation, would process the actual image here
|
|
108
|
+
# For simulation, copy the file or create a marker file
|
|
109
|
+
create_processed_output(work)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def validate_resize_params(resize_params)
|
|
113
|
+
width = resize_params[:width]
|
|
114
|
+
height = resize_params[:height]
|
|
115
|
+
|
|
116
|
+
if width && width <= 0
|
|
117
|
+
raise "Invalid width: #{width}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if height && height <= 0
|
|
121
|
+
raise "Invalid height: #{height}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def validate_format(format)
|
|
126
|
+
valid_formats = %w[jpg jpeg png gif bmp webp]
|
|
127
|
+
unless valid_formats.include?(format.to_s.downcase)
|
|
128
|
+
raise "Unsupported format: #{format}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def validate_filter(filter)
|
|
133
|
+
valid_filters = %w[grayscale sepia blur sharpen]
|
|
134
|
+
unless valid_filters.include?(filter.to_s.downcase)
|
|
135
|
+
raise "Unknown filter: #{filter}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def validate_brightness(brightness)
|
|
140
|
+
unless brightness.is_a?(Numeric) && brightness >= -100 && brightness <= 100
|
|
141
|
+
raise "Brightness must be between -100 and 100"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def simulate_file_size(work)
|
|
146
|
+
# Simulate output file size based on operations
|
|
147
|
+
base_size = 102400 # 100KB base
|
|
148
|
+
|
|
149
|
+
if work.operations[:resize]
|
|
150
|
+
width = work.operations[:resize][:width] || 1000
|
|
151
|
+
height = work.operations[:resize][:height] || 1000
|
|
152
|
+
base_size = (width * height * 3) / 10 # Rough estimate
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if work.operations[:convert] == "jpg"
|
|
156
|
+
base_size = (base_size * 0.6).to_i # JPEG compression
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
base_size
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def create_processed_output(work)
|
|
163
|
+
# In simulation mode, create a JSON file with processing metadata
|
|
164
|
+
# In real mode, this would be the actual processed image
|
|
165
|
+
output_dir = File.dirname(work.output_path)
|
|
166
|
+
FileUtils.mkdir_p(output_dir)
|
|
167
|
+
|
|
168
|
+
metadata = {
|
|
169
|
+
original: work.input_path,
|
|
170
|
+
operations: work.operations,
|
|
171
|
+
processed_at: Time.now.iso8601
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Create a marker file to show processing occurred
|
|
175
|
+
File.write("#{work.output_path}.json", JSON.pretty_generate(metadata))
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# ProgressTracker monitors and displays processing progress
|
|
180
|
+
class ProgressTracker
|
|
181
|
+
attr_reader :total, :completed, :errors
|
|
182
|
+
|
|
183
|
+
def initialize(total)
|
|
184
|
+
@total = total
|
|
185
|
+
@completed = 0
|
|
186
|
+
@errors = 0
|
|
187
|
+
@start_time = Time.now
|
|
188
|
+
@lock = Mutex.new
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def increment_completed
|
|
192
|
+
@lock.synchronize do
|
|
193
|
+
@completed += 1
|
|
194
|
+
print_progress
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def increment_errors
|
|
199
|
+
@lock.synchronize do
|
|
200
|
+
@errors += 1
|
|
201
|
+
@completed += 1
|
|
202
|
+
print_progress
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def percentage
|
|
207
|
+
return 0 if total.zero?
|
|
208
|
+
|
|
209
|
+
((completed.to_f / total) * 100).round(2)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def elapsed_time
|
|
213
|
+
Time.now - @start_time
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def estimated_remaining
|
|
217
|
+
return 0 if completed.zero?
|
|
218
|
+
|
|
219
|
+
rate = elapsed_time / completed
|
|
220
|
+
(total - completed) * rate
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def print_progress
|
|
224
|
+
print "\rProcessing: #{completed}/#{total} (#{percentage}%) | "
|
|
225
|
+
print "Errors: #{errors} | "
|
|
226
|
+
print "Elapsed: #{format_time(elapsed_time)} | "
|
|
227
|
+
print "Est. remaining: #{format_time(estimated_remaining)}"
|
|
228
|
+
$stdout.flush
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def print_summary
|
|
232
|
+
puts "\n\n=== Processing Complete ==="
|
|
233
|
+
puts "Total: #{total}"
|
|
234
|
+
puts "Successful: #{completed - errors}"
|
|
235
|
+
puts "Errors: #{errors}"
|
|
236
|
+
puts "Total time: #{format_time(elapsed_time)}"
|
|
237
|
+
puts "Average time per image: #{format_time(elapsed_time / total)}"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
private
|
|
241
|
+
|
|
242
|
+
def format_time(seconds)
|
|
243
|
+
if seconds < 60
|
|
244
|
+
format("%.2fs", seconds)
|
|
245
|
+
else
|
|
246
|
+
minutes = (seconds / 60).to_i
|
|
247
|
+
secs = (seconds % 60).to_i
|
|
248
|
+
format("%dm %ds", minutes, secs)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Main execution
|
|
254
|
+
if __FILE__ == $PROGRAM_NAME
|
|
255
|
+
# Create sample test images if they don't exist
|
|
256
|
+
test_images_dir = File.join(__dir__, "test_images")
|
|
257
|
+
FileUtils.mkdir_p(test_images_dir)
|
|
258
|
+
|
|
259
|
+
# Create dummy test image files for demonstration
|
|
260
|
+
sample_images = []
|
|
261
|
+
10.times do |i|
|
|
262
|
+
img_path = File.join(test_images_dir, "sample_#{i + 1}.png")
|
|
263
|
+
unless File.exist?(img_path)
|
|
264
|
+
File.write(img_path, "FAKE_PNG_DATA_#{i}")
|
|
265
|
+
end
|
|
266
|
+
sample_images << img_path
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
puts "=== Image Batch Processor with Fractor ==="
|
|
270
|
+
puts "Processing #{sample_images.size} images in parallel"
|
|
271
|
+
puts
|
|
272
|
+
|
|
273
|
+
# Create output directory
|
|
274
|
+
output_dir = File.join(__dir__, "processed_images")
|
|
275
|
+
FileUtils.mkdir_p(output_dir)
|
|
276
|
+
|
|
277
|
+
# Define processing operations
|
|
278
|
+
operations = {
|
|
279
|
+
resize: { width: 800, height: 600 },
|
|
280
|
+
filter: "grayscale",
|
|
281
|
+
convert: "jpg"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Create work items
|
|
285
|
+
work_items = sample_images.map do |img_path|
|
|
286
|
+
output_path = File.join(
|
|
287
|
+
output_dir,
|
|
288
|
+
File.basename(img_path, ".*") + "_processed.jpg"
|
|
289
|
+
)
|
|
290
|
+
ImageProcessor::ImageWork.new(img_path, output_path, operations)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Initialize progress tracker
|
|
294
|
+
tracker = ImageProcessor::ProgressTracker.new(work_items.size)
|
|
295
|
+
|
|
296
|
+
# Process with Fractor
|
|
297
|
+
start_time = Time.now
|
|
298
|
+
|
|
299
|
+
supervisor = Fractor::Supervisor.new(
|
|
300
|
+
worker_pools: [
|
|
301
|
+
{ worker_class: ImageProcessor::ImageProcessorWorker, num_workers: 4 }
|
|
302
|
+
]
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Submit all work
|
|
306
|
+
supervisor.add_work_items(work_items)
|
|
307
|
+
|
|
308
|
+
# Start processing
|
|
309
|
+
supervisor.run
|
|
310
|
+
|
|
311
|
+
# Collect results and update progress
|
|
312
|
+
results = []
|
|
313
|
+
all_results = supervisor.results.results + supervisor.results.errors
|
|
314
|
+
|
|
315
|
+
all_results.each do |work_result|
|
|
316
|
+
result = work_result.result || {
|
|
317
|
+
status: "error",
|
|
318
|
+
error: work_result.error&.message || "Unknown error"
|
|
319
|
+
}
|
|
320
|
+
results << result
|
|
321
|
+
|
|
322
|
+
if result[:status] == "error"
|
|
323
|
+
tracker.increment_errors
|
|
324
|
+
else
|
|
325
|
+
tracker.increment_completed
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Print summary
|
|
330
|
+
tracker.print_summary
|
|
331
|
+
|
|
332
|
+
# Show sample results
|
|
333
|
+
puts "\n=== Sample Results ==="
|
|
334
|
+
results.first(3).each do |result|
|
|
335
|
+
puts "\nInput: #{File.basename(result[:input])}"
|
|
336
|
+
puts "Output: #{File.basename(result[:output])}"
|
|
337
|
+
puts "Operations: #{result[:operations].inspect}"
|
|
338
|
+
puts "Status: #{result[:status]}"
|
|
339
|
+
if result[:status] == "success"
|
|
340
|
+
puts "File size: #{result[:file_size]} bytes"
|
|
341
|
+
puts "Processing time: #{format("%.3f", result[:processing_time])}s"
|
|
342
|
+
else
|
|
343
|
+
puts "Error: #{result[:error]}"
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
puts "\nProcessed images saved to: #{output_dir}"
|
|
348
|
+
end
|
|
349
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_10.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_1.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_2.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_3.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_4.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_5.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_6.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_7.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_8.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"original": "/Users/mulgogi/src/mn/fractor/examples/image_processor/test_images/sample_9.png",
|
|
3
|
+
"operations": {
|
|
4
|
+
"resize": {
|
|
5
|
+
"width": 800,
|
|
6
|
+
"height": 600
|
|
7
|
+
},
|
|
8
|
+
"filter": "grayscale",
|
|
9
|
+
"convert": "jpg"
|
|
10
|
+
},
|
|
11
|
+
"processed_at": "2025-10-25T22:16:20+09:00"
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_2
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_4
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_7
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FAKE_PNG_DATA_8
|