.builds | ||
lib | ||
src | ||
test | ||
.credo.exs | ||
.formatter.exs | ||
.gitignore | ||
.tm_properties | ||
.tool-versions | ||
.travis.yml | ||
CHANGELOG.md | ||
dialyzer-ignore-warnings.exs | ||
Makefile | ||
mix.exs | ||
mix.lock | ||
README.md |
Majic
Majic provides a robust integration of libmagic for Elixir.
With this library, you can start an one-off process to run a single check, or run the process as a daemon if you expect to run many checks.
It is a friendly fork of gen_magic featuring a (arguably) more robust C-code using erl_interface, built in pooling, unified/clean API, and an optional Plug.
This package is regulary tested on multiple platforms (Debian, macOS, Fedora, Alpine, FreeBSD) to ensure it'll work fine in any environment.
Installation
The package can be installed by adding majic
to your list of dependencies in mix.exs
:
def deps do
[
{:majic, "~> 1.0"}
]
end
You must also have libmagic installed locally with headers, alongside common compilation tools (i.e. build-essential). These can be acquired by apt-get, yum, brew, etc.
Compilation of the underlying C program is automatic and handled by elixir_make.
Usage
Depending on the use case, you may utilise a single (one-off) Majic process without reusing it as a daemon, or utilise a connection pool (such as Poolboy) in your application to run multiple persistent Majic processes.
To use Majic directly, you can use Majic.Helpers.perform_once/1
:
iex(1)> Majic.perform(".", once: true)
{:ok,
%Majic.Result{
content: "directory",
encoding: "binary",
mime_type: "inode/directory"
}}
To use the Majic server as a daemon, you can start it first, keep a reference, then feed messages to it as you require:
{:ok, pid} = Majic.Server.start_link([])
{:ok, result} = Majic.perform(path, server: pid)
See Majic.Server.start_link/1
and t:Majic.Server.option/0
for more information on startup parameters.
See Majic.Result
for details on the result provided.
Configuration
When using Majic.Server.start_link/1
to start a persistent server, or Majic.Helpers.perform_once/2
to run an ad-hoc request, you can override specific options to suit your use case.
Name | Default | Description |
---|---|---|
:startup_timeout |
1000 | Number of milliseconds to wait for client startup |
:process_timeout |
30000 | Number of milliseconds to process each request |
:recycle_threshold |
10 | Number of cycles before the C process is replaced |
:database_patterns |
[:default] |
Databases to load |
See t:Majic.Server.option/0
for details.
Use Cases
Ad-Hoc Requests
For ad-hoc requests, you can use the helper method Majic.Helpers.perform_once/2
:
iex(1)> Majic.perform(Path.join(File.cwd!(), "Makefile"), once: true)
{:ok,
%Majic.Result{
content: "makefile script, ASCII text",
encoding: "us-ascii",
mime_type: "text/x-makefile"
}}
Supervised Requests
The Server should be run under a supervisor which provides resiliency.
Here we run it under a supervisor:
iex(1)> {:ok, pid} = Supervisor.start_link([{Majic.Server, name: :gen_magic}], strategy: :one_for_one)
{:ok, #PID<0.199.0>}
Now we can ask it to inspect a file:
iex(2)> Majic.perform(Path.expand("~/.bash_history"), server: :gen_magic)
{:ok, %Majic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}}
Note that in this case we have opted to use a named process.
Pool
For concurrency and resiliency, you may start the Majic.Pool
. By default, it will start a Majic.Server
worker per online scheduler:
You can add a pool in your application supervisor by adding it as a child:
children =
[
# ...
{Majic.Pool, [name: YourApp.MajicPool, pool_size: 2]}
]
opts = [strategy: :one_for_one, name: YourApp.Supervisor]
Supervisor.start_link(children, opts)
And then you can use it with Majic.perform/2
with pool: YourApp.MajicPool
option:
iex(1)> Majic.perform(Path.expand("~/.bash_history"), pool: YourApp.MajicPool)
{:ok, %Majic.Result{mime_type: "text/plain", encoding: "us-ascii", content: "ASCII text"}}
Check Uploaded Files
If you use Phoenix, you can inspect the file from your controller:
def upload(conn, %{"upload" => %{path: path}}) do,
{:ok, result} = Majic.perform(path, server: :gen_magic)
text(conn, "Received your file containing #{result.content}")
end
Obviously, it will be more ideal if you have wrapped Majic.Server
in a pool such as Poolboy, to avoid constantly starting and stopping the underlying C program.
Notes
Soak Test
Run an endless cycle to prove that the program is resilient:
find /usr/share/ -name *png | xargs mix run test/soak.exs
find . -name *ex | xargs mix run test/soak.exs
Acknowledgements
During design and prototype development of this library, the Author has drawn inspiration from the following individuals, and therefore thanks all contributors for their generosity:
- Evadne Wu
- Original work
- Mr James Every
- Enhanced Elixir Wrapper (based on GenServer)
- Initial Hex packaging (v.0.22)
- Soak Testing