Packages
phoenix_kit
1.7.113
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/storage/providers/local.ex
defmodule PhoenixKit.Modules.Storage.Providers.Local do
@moduledoc """
Local filesystem storage provider.
Stores files on the local filesystem using the configured endpoint path.
Suitable for development, testing, and single-server deployments.
"""
@behaviour PhoenixKit.Modules.Storage.Provider
require Logger
@impl true
def store_file(bucket, source_path, destination_path, _opts \\ []) do
# Build the full destination path
full_destination = Path.join(bucket.endpoint || "priv/media", destination_path)
Logger.info(
"[LocalStorage] store_file: source=#{source_path}, destination=#{full_destination}"
)
with :ok <- validate_source_exists(source_path),
{:ok, source_size} <- get_source_size(source_path),
:ok <- ensure_destination_dir(full_destination),
:ok <- copy_file(source_path, full_destination),
:ok <- verify_copy(full_destination, source_size) do
{:ok, full_destination}
end
rescue
error ->
Logger.error("[LocalStorage] Exception storing file: #{inspect(error)}")
{:error, "Error storing file: #{inspect(error)}"}
end
defp validate_source_exists(source_path) do
if File.exists?(source_path) do
:ok
else
Logger.error("[LocalStorage] Source file does not exist: #{source_path}")
{:error, "Source file does not exist: #{source_path}"}
end
end
defp get_source_size(source_path) do
case File.stat(source_path) do
{:ok, %{size: size}} ->
Logger.info("[LocalStorage] Source file size: #{size} bytes")
{:ok, size}
{:error, reason} ->
Logger.error("[LocalStorage] Failed to get source file size: #{inspect(reason)}")
{:error, "Failed to get source file size: #{inspect(reason)}"}
end
end
defp ensure_destination_dir(full_destination) do
destination_dir = Path.dirname(full_destination)
case File.mkdir_p(destination_dir) do
:ok ->
Logger.debug("[LocalStorage] Created/verified directory: #{destination_dir}")
:ok
{:error, reason} ->
Logger.error(
"[LocalStorage] Failed to create directory #{destination_dir}: #{inspect(reason)}"
)
{:error, "Failed to create directory: #{inspect(reason)}"}
end
end
defp copy_file(source_path, full_destination) do
Logger.info("[LocalStorage] Copying #{source_path} -> #{full_destination}")
case File.cp(source_path, full_destination) do
:ok ->
Logger.info("[LocalStorage] File.cp completed successfully")
:ok
{:error, reason} ->
Logger.error("[LocalStorage] Failed to copy file: #{inspect(reason)}")
{:error, "Failed to copy file: #{inspect(reason)}"}
end
end
defp verify_copy(full_destination, expected_size) do
if File.exists?(full_destination) do
case File.stat(full_destination) do
{:ok, %{size: dest_size}} ->
if dest_size == expected_size do
Logger.info("[LocalStorage] File verified: #{full_destination} (#{dest_size} bytes)")
:ok
else
Logger.error(
"[LocalStorage] Size mismatch! Expected #{expected_size}, got #{dest_size}"
)
{:error, "File size mismatch after copy"}
end
{:error, reason} ->
Logger.error("[LocalStorage] Failed to stat destination file: #{inspect(reason)}")
{:error, "Failed to verify destination file: #{inspect(reason)}"}
end
else
Logger.error("[LocalStorage] File.cp returned :ok but file not found: #{full_destination}")
{:error, "File copy reported success but file not found"}
end
end
@impl true
def retrieve_file(bucket, file_path, destination_path) do
full_source = Path.join(bucket.endpoint || "priv/media", file_path)
# Ensure destination directory exists
destination_dir = Path.dirname(destination_path)
case File.mkdir_p(destination_dir) do
:ok -> :ok
{:error, reason} -> {:error, "Failed to create destination directory: #{inspect(reason)}"}
end
# Copy the file
case File.cp(full_source, destination_path) do
:ok -> :ok
{:error, reason} -> {:error, "Failed to copy file: #{inspect(reason)}"}
end
rescue
error -> {:error, "Error retrieving file: #{inspect(error)}"}
end
@impl true
def delete_file(bucket, file_path) do
full_path = Path.join(bucket.endpoint || "priv/media", file_path)
case File.rm(full_path) do
:ok -> :ok
# File doesn't exist, that's fine
{:error, :enoent} -> :ok
{:error, reason} -> {:error, "Failed to delete file: #{inspect(reason)}"}
end
rescue
error -> {:error, "Error deleting file: #{inspect(error)}"}
end
@impl true
def file_exists?(bucket, file_path) do
full_path = Path.join(bucket.endpoint || "priv/media", file_path)
File.exists?(full_path)
end
@impl true
def public_url(_bucket, _file_path) do
# Local storage doesn't have public URLs by default
# This would need to be configured with a web server path
nil
end
@impl true
def test_connection(bucket) do
base_path = bucket.endpoint || "priv/media"
# Test if we can create the directory
case File.mkdir_p(base_path) do
:ok ->
# Test if we can write a temporary file
test_file = Path.join(base_path, ".phoenix_kit_test")
case File.write(test_file, "test") do
:ok ->
File.rm(test_file)
:ok
{:error, reason} ->
{:error, "Cannot write to directory: #{inspect(reason)}"}
end
{:error, reason} ->
{:error, "Cannot create directory: #{inspect(reason)}"}
end
rescue
error -> {:error, "Error testing connection: #{inspect(error)}"}
end
end