Sambex.HotFolder (sambex v0.3.0)
View SourceA GenServer that monitors an SMB share directory for new files and processes them automatically.
HotFolder implements the "hot folder" pattern common in printing and document processing industries, where files dropped into a monitored directory trigger automated processing workflows.
Features
- Sequential Processing: Files are processed one at a time to ensure deterministic behavior
- Flexible Connection Management: Use existing connections or create new ones
- Rich File Filtering: Filter files by name patterns, size, and MIME types
- Automatic Folder Management: Creates and manages processing, success, and error folders
- Robust Error Handling: Retries failed processing with exponential backoff
- Efficient Polling: Smart polling with backoff to minimize network overhead
Basic Usage
# Simple configuration with direct connection
{:ok, pid} = Sambex.HotFolder.start_link(%{
url: "smb://server/print-queue",
username: "printer",
password: "secret",
handler: &MyApp.process_print_job/1
})
# Using an existing named connection
{:ok, pid} = Sambex.HotFolder.start_link(%{
connection: :print_server,
handler: &MyApp.process_print_job/1
})
Advanced Configuration
config = %Sambex.HotFolder.Config{
connection: :print_server,
base_path: "hot-folders/pdf-processor",
handler: {MyApp.PDFProcessor, :process, [:high_quality]},
folders: %{
incoming: "inbox",
processing: "working",
success: "completed",
errors: "failed"
},
filters: %{
name_patterns: [~r/.pdf$/i],
min_size: 1024,
max_size: 100_000_000, # 100MB
exclude_patterns: [~r/^./, ~r/~$/]
},
poll_interval: %{
initial: 1_000,
max: 30_000,
backoff_factor: 2.0
},
handler_timeout: 300_000, # 5 minutes
max_retries: 5
}
{:ok, pid} = Sambex.HotFolder.start_link(config)
File Processing Workflow
- Discovery: Files are discovered in the incoming folder during polling
- Filtering: Files are checked against configured filters
- Stability Check: Files must have stable size to ensure complete upload
- Processing: File is moved to processing folder and handler is called
- Success: On success, file is moved to success folder
- Error: On failure, file is moved to errors folder with error report
Handler Interface
Handlers receive a file info map and should return {:ok, result}
or {:error, reason}
:
def process_file(file_info) do
# file_info contains: %{path: "...", name: "...", size: ...}
case do_processing(file_info.path) do
:ok -> {:ok, %{processed_at: DateTime.utc_now()}}
{:error, reason} -> {:error, reason}
end
end
Monitoring and Stats
# Get current statistics
Sambex.HotFolder.stats(pid)
# => %{files_processed: 150, files_failed: 3, uptime: 3600, ...}
# Get current status
Sambex.HotFolder.status(pid)
# => :polling | {:processing, "filename.pdf"} | :error
Summary
Functions
Returns a specification to start this module under a supervisor.
Forces an immediate poll for new files.
Starts a HotFolder GenServer.
Returns the current statistics for the HotFolder.
Returns the current status of the HotFolder.
Stops the HotFolder gracefully.
Types
@type file_info() :: %{path: String.t(), name: String.t(), size: non_neg_integer()}
@type stats() :: %{ files_processed: non_neg_integer(), files_failed: non_neg_integer(), current_status: atom(), uptime: non_neg_integer(), last_poll: DateTime.t() | nil, current_interval: pos_integer() }
Functions
Returns a specification to start this module under a supervisor.
See Supervisor
.
@spec poll_now(GenServer.server()) :: :ok | {:error, atom()}
Forces an immediate poll for new files.
Returns :ok
if poll was triggered, or {:error, reason}
if not possible.
@spec start_link( Sambex.HotFolder.Config.t() | map(), keyword() ) :: GenServer.on_start()
Starts a HotFolder GenServer.
Options
config
- ASambex.HotFolder.Config
struct or map of configuration optionsname
- Optional name for the GenServer (for registration)
Examples
{:ok, pid} = Sambex.HotFolder.start_link(%{
url: "smb://server/share",
username: "user",
password: "pass",
handler: &MyApp.process/1
})
{:ok, pid} = Sambex.HotFolder.start_link(config, name: :pdf_processor)
@spec stats(GenServer.server()) :: stats()
Returns the current statistics for the HotFolder.
Examples
stats = Sambex.HotFolder.stats(pid)
# => %{
# files_processed: 42,
# files_failed: 3,
# current_status: :polling,
# uptime: 3600,
# last_poll: ~U[2025-01-15 10:30:00Z],
# current_interval: 5000
# }
@spec status(GenServer.server()) :: atom() | {atom(), String.t()}
Returns the current status of the HotFolder.
Possible statuses:
:starting
- HotFolder is initializing:polling
- Actively polling for files{:processing, filename}
- Currently processing a file:error
- An error has occurred
@spec stop(GenServer.server(), term()) :: :ok
Stops the HotFolder gracefully.
Any file currently being processed will complete before shutdown.