Skip to content

Commit

Permalink
VIP - add install function with entries functions
Browse files Browse the repository at this point in the history
  • Loading branch information
shahryarjb committed Jun 22, 2024
1 parent 3635955 commit a01d214
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ mishka_installer-*.tar
/lib/example/
.iex.exs
/.mnesia
/deployment/
27 changes: 18 additions & 9 deletions lib/installer/downloader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defmodule MishkaInstaller.Installer.Downloader do

@type error_return :: {:error, [%{action: atom(), field: atom(), message: String.t()}]}

@type okey_return :: {:ok, struct() | map()}
@type okey_return :: {:ok, struct() | map() | binary()}

####################################################################################
######################### (▰˘◡˘▰) Functions (▰˘◡˘▰) ##########################
Expand Down Expand Up @@ -81,21 +81,21 @@ defmodule MishkaInstaller.Installer.Downloader do
end
end

def download(:github, %{path: path, branch: branch}) do
def download(:github, %{path: path, branch: branch}) when not is_nil(branch) do
case build_url("https://github.com/#{String.trim(path)}/archive/refs/heads/#{branch}.tar.gz") do
%Req.Response{status: 200, body: body} -> {:ok, body}
_ -> mix_global_err()
end
end

def download(:github, %{path: path, release: release}) do
def download(:github, %{path: path, release: release}) when not is_nil(release) do
case build_url("https://github.com/#{String.trim(path)}/archive/refs/tags/#{release}.tar.gz") do
%Req.Response{status: 200, body: body} -> {:ok, body}
_ -> mix_global_err()
end
end

def download(:github, %{path: path, tag: tag}) do
def download(:github, %{path: path, tag: tag}) when not is_nil(tag) do
case build_url("https://github.com/#{String.trim(path)}/archive/refs/tags/#{tag}.tar.gz") do
%Req.Response{status: 200, body: body} -> {:ok, body}
_ -> mix_global_err()
Expand Down Expand Up @@ -146,9 +146,18 @@ defmodule MishkaInstaller.Installer.Downloader do
end
end

# ************************************************************
# ************************************************************
# ************************************************************
def download(:url, %{path: path}) do
case build_url(path) do
%Req.Response{status: 200, body: body} -> body
_ -> mix_global_err()
end
end

def download(_, _) do
message = "The information sent to download the desired library is wrong!"
{:error, [%{message: message, field: :path, action: :download}]}
end

@doc """
Retrieves the `mix.exs` file for the specified package.
Expand Down Expand Up @@ -241,8 +250,8 @@ defmodule MishkaInstaller.Installer.Downloader do
########################## (▰˘◡˘▰) Helper (▰˘◡˘▰) ############################
####################################################################################
defp mix_global_err(msg \\ nil) do
message = msg || "There is a problem downloading the mix.exs file."
{:error, [%{message: message, field: :path, action: :package}]}
message = msg || "There is a problem downloading the mix.exs/project."
{:error, [%{message: message, field: :path, action: :download}]}
end

# Based on https://hexdocs.pm/req/Req.Test.html#module-example
Expand Down
64 changes: 62 additions & 2 deletions lib/installer/installer.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
defmodule MishkaInstaller.Installer.Installer do
use GuardedStruct
alias MishkaDeveloperTools.Helper.{Extra, UUID}
alias MishkaInstaller.Installer.{Downloader, LibraryHandler}

@type download_type ::
:hex
Expand All @@ -12,27 +14,85 @@ defmodule MishkaInstaller.Installer.Installer do

@type dep_type :: :none | :force_update

@type branch :: String.t() | {String.t(), [git: boolean()]}

@mnesia_info [
type: :set,
index: [:app, :type, :dependency_type],
record_name: __MODULE__,
storage_properties: [ets: [{:read_concurrency, true}, {:write_concurrency, true}]]
]
####################################################################################
########################## (▰˘◡˘▰) Schema (▰˘◡˘▰) ############################
####################################################################################
guardedstruct do
@ext_type "hex::github::github_latest_release::github_latest_tag::github_release::github_tag::url"
@ext_type "hex::github::github_latest_release::github_latest_tag::url"
@dep_type "enum=Atom[none::force_update]"

field(:id, UUID.t(), auto: {UUID, :generate}, derive: "validate(uuid)")
field(:app, String.t(), enforce: true, derive: "validate(not_empty_string)")
field(:version, String.t(), enforce: true, derive: "validate(not_empty_string)")
field(:type, download_type(), enforce: true, derive: "validate(enum=Atom[#{@ext_type}])")
field(:path, String.t(), derive: "validate(either=[not_empty_string, url])")
field(:path, String.t(), enforce: true, derive: "validate(either=[not_empty_string, url])")
field(:tag, String.t(), derive: "validate(not_empty_string)")
field(:release, String.t(), derive: "validate(not_empty_string)")
field(:branch, branch(), derive: "validate(either=[tuple, not_empty_string])")
field(:custom_command, String.t(), derive: "validate(not_empty_string)")
field(:dependency_type, dep_type(), default: :none, derive: "validate(#{@dep_type})")
field(:depends, list(String.t()), default: [], derive: "validate(list)")
field(:checksum, String.t(), derive: "validate(not_empty_string)")
# This type can be used when you want to introduce an event inserted_at unix time(timestamp).
field(:inserted_at, DateTime.t(), auto: {Extra, :get_unix_time})
# This type can be used when you want to introduce an event updated_at unix time(timestamp).
field(:updated_at, DateTime.t(), auto: {Extra, :get_unix_time})
end

################################################################################
######################## (▰˘◡˘▰) Init data (▰˘◡˘▰) #######################
################################################################################
@doc false
@spec database_config() :: keyword()
if Mix.env() != :test do
def database_config(),
do: Keyword.merge(@mnesia_info, attributes: keys(), disc_copies: [node()])
else
def database_config(),
do: Keyword.merge(@mnesia_info, attributes: keys(), ram_copies: [node()])
end

####################################################################################
######################### (▰˘◡˘▰) Functions (▰˘◡˘▰) ##########################
####################################################################################
def install(app) do
with {:ok, data} <- __MODULE__.builder(app),
{:ok, archived_file} <- Downloader.download(Map.get(data, :type), data),
{:ok, path} <- LibraryHandler.move(app, archived_file),
:ok <- LibraryHandler.extract(:tar, path) do
{:ok,
%{
download: path,
extensions: data,
dir: "#{LibraryHandler.extensions_path()}/#{app.app}-#{app.version}"
}}
end

# TODO: Create an item inside LibraryHandler queue
# |__ TODO: CheckSum file if exist
# |__ TODO: Do compile based on strategy developer wants
# |__ TODO: Store builded files for re-start project
# |__ TODO: Do runtime steps
# |__ TODO: Update all stuff in mnesia db
# |__ TODO: Re-cover if the process not correct, especially mix manipulating
after
File.cd!("/Users/shahryar/Documents/Programming/Elixir/mishka_installer")
end

def uninstall(%__MODULE__{} = _app) do
end

####################################################################################
########################## (▰˘◡˘▰) Query (▰˘◡˘▰) ############################
####################################################################################

####################################################################################
########################## (▰˘◡˘▰) Helper (▰˘◡˘▰) ############################
Expand Down
78 changes: 63 additions & 15 deletions lib/installer/library_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,37 @@ defmodule MishkaInstaller.Installer.LibraryHandler do
@moduledoc """
"""
alias MishkaInstaller.Installer.Installer

@type posix :: :file.posix()

@type io_device :: :file.io_device()

@type error_return :: {:error, [%{action: atom(), field: atom(), message: String.t()}]}

@type okey_return :: {:ok, struct() | map()}
@type okey_return :: {:ok, struct() | map() | binary()}

@type app :: String.t() | atom()
@type app :: Installer.t()

@type runtime_type :: :add | :force_update | :uninstall

@type compile_time_type :: :cmd | :port | :mix

####################################################################################
######################## (▰˘◡˘▰) Public API (▰˘◡˘▰) ##########################
####################################################################################
@spec do_runtime(app, runtime_type) :: any()
def do_runtime(_app, :add) do
def do_runtime(%Installer{} = _app, :add) do
end

def do_runtime(_app, :force_update) do
def do_runtime(%Installer{} = _app, :force_update) do
end

def do_runtime(_app, :uninstall) do
def do_runtime(%Installer{} = _app, :uninstall) do
end

@spec do_runtime(app, compile_time_type) :: any()
def do_compile_time(_app, type) when type in [:cmd, :port, :mix] do
def do_compile_time(%Installer{} = _app, type) when type in [:cmd, :port, :mix] do
end

####################################################################################
Expand Down Expand Up @@ -62,19 +69,23 @@ defmodule MishkaInstaller.Installer.LibraryHandler do
end) ++ extra
end

# TODO: should be changed
# erl_tar:extract("rel/project-1.0.tar.gz", [compressed]);
@spec extract(:tar, String.t()) :: {:error, :extract} | {:ok, :extract}
@spec extract(:tar, binary()) :: :ok | error_return()
def extract(:tar, archived_file) do
extract_output =
:erl_tar.extract(
~c'#{extensions_path()}/#{archived_file}.tar.gz',
[:compressed, {:cwd, ~c'#{extensions_path()}/#{archived_file}'}]
~c'#{archived_file}',
[:compressed, {:cwd, ~c'#{extensions_path()}'}]
)

case extract_output do
:ok -> {:ok, :extract}
{:error, term} -> {:error, :extract, term}
:ok ->
:ok

{:error, term} ->
message =
"There is a problem in extracting the compressed file of the ready-made library."

{:error, [%{message: message, field: :path, action: :move, source: term}]}
end
end

Expand All @@ -96,6 +107,25 @@ defmodule MishkaInstaller.Installer.LibraryHandler do
|> Kernel.==(Map.get(app_info, :checksum))
end

@spec move(Installer.t(), binary()) :: okey_return() | error_return()
def move(app, archived_file) do
with {:mkdir_p, :ok} <- {:mkdir_p, File.mkdir_p(extensions_path())},
{:ok, path} <- write_downloaded_lib(app, archived_file) do
{:ok, path}
else
{:mkdir_p, {:error, error}} ->
message = "An error occurred in downloading and transferring the library file you want."
{:error, [%{message: message, field: :path, action: :move, source: error}]}

error ->
error
end
end

####################################################################################
########################## (▰˘◡˘▰) Callback (▰˘◡˘▰) ##########################
####################################################################################

####################################################################################
########################## (▰˘◡˘▰) Helper (▰˘◡˘▰) ############################
####################################################################################
Expand All @@ -120,8 +150,26 @@ defmodule MishkaInstaller.Installer.LibraryHandler do

defp convert_mix_ast_output(_), do: {:error, :package, :convert_mix_ast_output}

defp write_downloaded_lib(app, extracted) do
open_file =
File.open("#{extensions_path()}/#{app.app}-#{app.version}.tar", [:read, :write], fn file ->
IO.binwrite(file, extracted)
File.close(file)
end)

case open_file do
{:ok, _ress} ->
{:ok, "#{extensions_path()}/#{app.app}-#{app.version}.tar"}

error ->
message = "An error occurred in downloading and transferring the library file you want."
{:error, [%{message: message, field: :path, action: :move, source: error}]}
end
end

# TODO: should be changed
defp extensions_path() do
Path.join("project_path", ["deployment/", "extensions"])
def extensions_path() do
info = MishkaInstaller.__information__()
Path.join(info.path, ["deployment/", "#{info.env}/", "extensions"])
end
end
13 changes: 13 additions & 0 deletions lib/mishka_installer.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
defmodule MishkaInstaller do
if Code.ensure_loaded?(Mix) and function_exported?(Mix, :env, 0) do
@project_env Mix.env()
else
@project_env nil
end

@moduledoc false
def broadcast(channel, status, data, broadcast \\ true) do
if broadcast do
Expand All @@ -22,4 +28,11 @@ defmodule MishkaInstaller do
def unsubscribe(channel) do
Phoenix.PubSub.unsubscribe(MishkaInstaller.PubSub, "mishka:plugin:#{channel}")
end

def __information__() do
%{
path: System.get_env("PROJECT_PATH") || Application.get_env(:mishka, :project_path),
env: System.get_env("MIX_ENV") || Application.get_env(:mishka, :project_env) || @project_env
}
end
end
3 changes: 2 additions & 1 deletion lib/mnesia_repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule MishkaInstaller.MnesiaRepo do
use GenServer
require Logger
alias MishkaInstaller.Event.Event
alias MishkaInstaller.Installer.Installer
alias MnesiaAssistant.Error, as: MError

@env_mod Mix.env()
Expand Down Expand Up @@ -70,7 +71,7 @@ defmodule MishkaInstaller.MnesiaRepo do

Logger.info("Identifier: #{inspect(@identifier)} ::: Mnesia tables Synchronized...")

essential_tables(Keyword.get(config, :essential, [Event]))
essential_tables(Keyword.get(config, :essential, [Installer, Event]))

MishkaInstaller.broadcast("mnesia", :synchronized, %{
identifier: :mishka_mnesia_repo,
Expand Down

0 comments on commit a01d214

Please sign in to comment.