Sambex Examples
View SourceThis guide provides practical examples for common Sambex use cases.
Basic File Operations
Reading and Writing Files
# Connect to SMB share
{:ok, conn} = Sambex.Connection.connect(
"smb://fileserver/documents",
"username",
"password"
)
# Read a configuration file
case Sambex.Connection.read_file(conn, "/config/app.json") do
{:ok, json_content} ->
config = Jason.decode!(json_content)
IO.inspect(config)
{:error, :enoent} ->
IO.puts("Config file not found, using defaults")
{:error, reason} ->
IO.puts("Failed to read config: #{reason}")
end
# Write a log file
log_entry = "#{DateTime.utc_now()} - Application started\n"
{:ok, _} = Sambex.Connection.write_file(conn, "/logs/app.log", log_entry)
# Clean up
Sambex.Connection.disconnect(conn)
Working with Binary Files
defmodule ImageProcessor do
def backup_and_resize_image(conn, image_path) do
with {:ok, image_data} <- Sambex.Connection.read_file(conn, image_path),
{:ok, resized_data} <- resize_image(image_data),
{:ok, _} <- Sambex.Connection.write_file(conn, "/backups" <> image_path, image_data),
{:ok, _} <- Sambex.Connection.write_file(conn, "/thumbnails" <> image_path, resized_data) do
{:ok, "Image processed successfully"}
else
{:error, reason} -> {:error, "Failed to process image: #{reason}"}
end
end
defp resize_image(image_data) do
# Your image resizing logic here
# This example just returns the original data
{:ok, image_data}
end
end
# Usage
{:ok, conn} = Sambex.Connection.connect("smb://server/images", "user", "pass")
ImageProcessor.backup_and_resize_image(conn, "/photos/vacation.jpg")
Directory Operations
Listing and Filtering Files
defmodule FileManager do
def list_files_by_extension(conn, directory, extension) do
case Sambex.Connection.list_dir(conn, directory) do
{:ok, entries} ->
files = entries
|> Enum.filter(fn {_name, type} -> type == :file end)
|> Enum.map(fn {name, _type} -> name end)
|> Enum.filter(fn name -> String.ends_with?(name, extension) end)
{:ok, files}
{:error, reason} ->
{:error, reason}
end
end
def get_directory_size(conn, directory) do
case Sambex.Connection.list_dir(conn, directory) do
{:ok, entries} ->
total_size = entries
|> Enum.filter(fn {_name, type} -> type == :file end)
|> Enum.map(fn {name, _type} ->
case Sambex.Connection.get_file_stats(conn, "#{directory}/#{name}") do
{:ok, %{size: size}} -> size
_ -> 0
end
end)
|> Enum.sum()
{:ok, total_size}
{:error, reason} ->
{:error, reason}
end
end
end
# Usage
{:ok, conn} = Sambex.Connection.connect("smb://server/files", "user", "pass")
# List all PDF files
{:ok, pdf_files} = FileManager.list_files_by_extension(conn, "/documents", ".pdf")
IO.inspect(pdf_files)
# Get total size of directory
{:ok, total_bytes} = FileManager.get_directory_size(conn, "/documents")
IO.puts("Directory size: #{total_bytes} bytes")
Recursive Directory Processing
defmodule RecursiveProcessor do
def process_directory_tree(conn, directory, processor_fn) do
case Sambex.Connection.list_dir(conn, directory) do
{:ok, entries} ->
Enum.each(entries, fn {name, type} ->
full_path = Path.join(directory, name)
case type do
:file ->
processor_fn.(conn, full_path, :file)
:directory ->
processor_fn.(conn, full_path, :directory)
# Recursively process subdirectory
process_directory_tree(conn, full_path, processor_fn)
end
end)
{:error, reason} ->
IO.puts("Error processing #{directory}: #{reason}")
end
end
end
# Example: Count files and directories
defmodule FileCounter do
def count_items(conn, root_directory) do
counts = %{files: 0, directories: 0}
agent = Agent.start_link(fn -> counts end)
processor = fn _conn, _path, type ->
Agent.update(agent, fn counts ->
Map.update(counts, type == :file && :files || :directories, 0, &(&1 + 1))
end)
end
RecursiveProcessor.process_directory_tree(conn, root_directory, processor)
final_counts = Agent.get(agent, & &1)
Agent.stop(agent)
final_counts
end
end
# Usage
{:ok, conn} = Sambex.Connection.connect("smb://server/archive", "user", "pass")
counts = FileCounter.count_items(conn, "/")
IO.puts("Found #{counts.files} files and #{counts.directories} directories")
File Synchronization
One-way Sync from Local to SMB
defmodule SyncManager do
def sync_local_to_smb(local_dir, conn, remote_dir) do
case File.ls(local_dir) do
{:ok, local_files} ->
# Get remote files for comparison
{:ok, remote_entries} = Sambex.Connection.list_dir(conn, remote_dir)
remote_files = remote_entries
|> Enum.filter(fn {_name, type} -> type == :file end)
|> Enum.map(fn {name, _type} -> name end)
|> MapSet.new()
# Sync each local file
Enum.each(local_files, fn filename ->
local_path = Path.join(local_dir, filename)
remote_path = "#{remote_dir}/#{filename}"
if File.regular?(local_path) do
should_sync = cond do
not MapSet.member?(remote_files, filename) ->
IO.puts("New file: #{filename}")
true
file_newer?(local_path, conn, remote_path) ->
IO.puts("Updated file: #{filename}")
true
true ->
false
end
if should_sync do
case Sambex.Connection.upload_file(conn, local_path, remote_path) do
{:ok, _} -> IO.puts("✓ Synced #{filename}")
{:error, reason} -> IO.puts("✗ Failed to sync #{filename}: #{reason}")
end
end
end
end)
{:error, reason} ->
IO.puts("Failed to read local directory: #{reason}")
end
end
defp file_newer?(local_path, conn, remote_path) do
with {:ok, local_stat} <- File.stat(local_path),
{:ok, remote_stat} <- Sambex.Connection.get_file_stats(conn, remote_path) do
local_mtime = local_stat.mtime |> NaiveDateTime.from_erl!() |> DateTime.from_naive!("Etc/UTC")
remote_mtime = DateTime.from_unix!(remote_stat.modification_time)
DateTime.compare(local_mtime, remote_mtime) == :gt
else
_ -> true # If we can't compare, assume local is newer
end
end
end
# Usage
{:ok, conn} = Sambex.Connection.connect("smb://backup-server/sync", "user", "pass")
SyncManager.sync_local_to_smb("/home/user/documents", conn, "/backup/documents")
Production Patterns
Connection Pool for High Throughput
defmodule SMBConnectionPool do
use GenServer
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def get_connection() do
GenServer.call(__MODULE__, :get_connection)
end
def return_connection(conn) do
GenServer.cast(__MODULE__, {:return_connection, conn})
end
def init(opts) do
url = Keyword.fetch!(opts, :url)
username = Keyword.fetch!(opts, :username)
password = Keyword.fetch!(opts, :password)
pool_size = Keyword.get(opts, :pool_size, 5)
connections = for _ <- 1..pool_size do
{:ok, conn} = Sambex.Connection.connect(url, username, password)
conn
end
{:ok, %{available: connections, in_use: MapSet.new()}}
end
def handle_call(:get_connection, _from, %{available: [conn | rest], in_use: in_use}) do
{:reply, {:ok, conn}, %{available: rest, in_use: MapSet.put(in_use, conn)}}
end
def handle_call(:get_connection, _from, %{available: []} = state) do
{:reply, {:error, :no_connections_available}, state}
end
def handle_cast({:return_connection, conn}, %{available: available, in_use: in_use}) do
if MapSet.member?(in_use, conn) do
new_state = %{
available: [conn | available],
in_use: MapSet.delete(in_use, conn)
}
{:noreply, new_state}
else
{:noreply, %{available: available, in_use: in_use}}
end
end
end
# High-throughput file processor
defmodule HighThroughputProcessor do
def process_files(file_list) do
tasks = Enum.map(file_list, fn file_path ->
Task.async(fn ->
case SMBConnectionPool.get_connection() do
{:ok, conn} ->
try do
process_single_file(conn, file_path)
after
SMBConnectionPool.return_connection(conn)
end
{:error, :no_connections_available} ->
{:error, "No connections available"}
end
end)
end)
Task.await_many(tasks, 30_000)
end
defp process_single_file(conn, file_path) do
with {:ok, content} <- Sambex.Connection.read_file(conn, file_path),
processed_content <- transform_content(content),
{:ok, _} <- Sambex.Connection.write_file(conn, "/processed" <> file_path, processed_content) do
{:ok, "Processed #{file_path}"}
else
{:error, reason} -> {:error, "Failed to process #{file_path}: #{reason}"}
end
end
defp transform_content(content) do
# Your transformation logic here
String.upcase(content)
end
end
# Usage
SMBConnectionPool.start_link([
url: "smb://processing-server/data",
username: "processor",
password: "secret",
pool_size: 10
])
files_to_process = ["/input/file1.txt", "/input/file2.txt", "/input/file3.txt"]
results = HighThroughputProcessor.process_files(files_to_process)
IO.inspect(results)
Supervised Production Service
defmodule DocumentService do
use GenServer
def start_link(_opts) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
def save_document(content, filename) do
GenServer.call(__MODULE__, {:save_document, content, filename})
end
def get_document(filename) do
GenServer.call(__MODULE__, {:get_document, filename})
end
def list_documents() do
GenServer.call(__MODULE__, :list_documents)
end
def init(:ok) do
# Start our SMB connection
{:ok, _} = Sambex.Connection.start_link([
url: Application.get_env(:my_app, :documents_smb_url),
username: Application.get_env(:my_app, :documents_smb_user),
password: Application.get_env(:my_app, :documents_smb_pass),
name: :documents
])
{:ok, %{}}
end
def handle_call({:save_document, content, filename}, _from, state) do
result = case Sambex.Connection.write_file(:documents, "/#{filename}", content) do
{:ok, _} ->
# Also create metadata
metadata = %{
filename: filename,
size: byte_size(content),
created_at: DateTime.utc_now(),
checksum: :crypto.hash(:sha256, content) |> Base.encode16()
}
Sambex.Connection.write_file(
:documents,
"/metadata/#{filename}.json",
Jason.encode!(metadata)
)
{:ok, "Document saved successfully"}
{:error, reason} ->
{:error, "Failed to save document: #{reason}"}
end
{:reply, result, state}
end
def handle_call({:get_document, filename}, _from, state) do
result = Sambex.Connection.read_file(:documents, "/#{filename}")
{:reply, result, state}
end
def handle_call(:list_documents, _from, state) do
result = case Sambex.Connection.list_dir(:documents, "/") do
{:ok, entries} ->
documents = entries
|> Enum.filter(fn {name, type} ->
type == :file and not String.starts_with?(name, ".")
end)
|> Enum.map(fn {name, _type} -> name end)
|> Enum.reject(fn name -> String.ends_with?(name, ".json") end)
{:ok, documents}
error -> error
end
{:reply, result, state}
end
end
# In your application.ex
children = [
DocumentService,
# Your other services...
]
# Usage in your application
DocumentService.save_document("Hello, World!", "greeting.txt")
{:ok, content} = DocumentService.get_document("greeting.txt")
{:ok, files} = DocumentService.list_documents()
Error Handling Patterns
Retry Logic with Backoff
defmodule ReliableFileOperations do
def read_file_with_retry(conn, path, max_retries \\ 3) do
do_with_retry(fn -> Sambex.Connection.read_file(conn, path) end, max_retries)
end
def write_file_with_retry(conn, path, content, max_retries \\ 3) do
do_with_retry(fn -> Sambex.Connection.write_file(conn, path, content) end, max_retries)
end
defp do_with_retry(operation, retries_left, delay \\ 1000)
defp do_with_retry(operation, retries_left, delay) when retries_left > 0 do
case operation.() do
{:ok, result} ->
{:ok, result}
{:error, reason} when reason in [:etimedout, :econnrefused, :ehostunreach] ->
IO.puts("Operation failed (#{reason}), retrying in #{delay}ms... (#{retries_left} retries left)")
Process.sleep(delay)
do_with_retry(operation, retries_left - 1, min(delay * 2, 10_000))
{:error, reason} ->
{:error, reason}
end
end
defp do_with_retry(_operation, 0, _delay) do
{:error, :max_retries_exceeded}
end
end
# Usage
{:ok, conn} = Sambex.Connection.connect("smb://unreliable-server/files", "user", "pass")
case ReliableFileOperations.read_file_with_retry(conn, "/important.txt") do
{:ok, content} -> IO.puts("Successfully read file: #{content}")
{:error, :max_retries_exceeded} -> IO.puts("Failed after all retries")
{:error, reason} -> IO.puts("Failed with error: #{reason}")
end
This collection of examples demonstrates real-world usage patterns for Sambex, from simple file operations to production-ready services with error handling and connection management.