Skip to content
This repository has been archived by the owner on Jun 11, 2023. It is now read-only.

Commit

Permalink
Use idempotent setup system on Story steps
Browse files Browse the repository at this point in the history
  • Loading branch information
renatomassaro committed Feb 10, 2018
1 parent 3e58a0d commit 10b773f
Show file tree
Hide file tree
Showing 27 changed files with 552 additions and 154 deletions.
2 changes: 1 addition & 1 deletion events.json
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
"filters": ["Account.Created"],
"emits": []
},
"DownloadCrackerPublicFTP": {
"DownloadCracker": {
"filters": ["File.Downloaded"],
"emits": []
}
Expand Down
1 change: 1 addition & 0 deletions lib/core/listener/model/owner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ defmodule Helix.Core.Listener.Model.Owner do
%__MODULE__{}
|> cast(params, @creation_fields)
|> validate_required(@required_fields)
|> unique_constraint(:owner_id_object_id_event_subscriber)
end

defmodule Query do
Expand Down
3 changes: 2 additions & 1 deletion lib/hell/hack.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ defmodule HELL.Hack.Experience do
{:render, 3}
],
"Elixir.Helix.Story.Model.Steppable" => [
{:start, 2},
{:setup, 2},
{:handle_event, 3},
{:complete, 1},
{:fail, 1},
{:restart, 3},
{:next_step, 1},
{:get_contact, 1},
{:format_meta, 1},
Expand Down
16 changes: 10 additions & 6 deletions lib/software/make/file.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ defmodule Helix.Software.Make.File do
alias Helix.Software.Model.Software
alias Helix.Software.Model.Storage

alias Helix.Software.Event.File.Added, as: FileAddedEvent

@type modules :: cracker_modules

@type cracker_modules :: %{bruteforce: version, overflow: version}
Expand All @@ -20,27 +22,27 @@ defmodule Helix.Software.Make.File do
Generates a cracker.
"""
@spec cracker(file_parent, cracker_modules, data) ::
{:ok, File.t_of_type(:cracker), %{}}
{:ok, File.t_of_type(:cracker), %{}, []}
def cracker(parent, modules, data \\ %{}),
do: file(parent, :cracker, modules, data)

@spec cracker!(file_parent, cracker_modules, data) ::
File.t_of_type(:cracker)
def cracker!(parent, modules, data \\ %{}) do
{:ok, file, _} = cracker(parent, modules, data)
{:ok, file, _, _} = cracker(parent, modules, data)
file
end

@spec file(file_parent, Software.type, modules, data) ::
{:ok, File.t, %{}}
{:ok, File.t, %{}, []}
defp file(server = %Server{}, type, modules, data) do
server
|> CacheQuery.from_server_get_storages!()
|> List.first()
|> file(type, modules, data)
|> file(type, modules, data, server.server_id)
end

defp file(storage_id = %Storage.ID{}, type, modules, data) do
defp file(storage_id = %Storage.ID{}, type, modules, data, server_id) do
path = Map.get(data, :path, File.Default.path())

modules = format_modules(type, modules)
Expand All @@ -56,7 +58,9 @@ defmodule Helix.Software.Make.File do

{:ok, file} = FileAction.create(params, modules)

{:ok, file, %{}}
event = FileAddedEvent.new(file, server_id)

{:ok, file, %{}, [event]}
end

@spec format_modules(Software.type, modules) ::
Expand Down
8 changes: 4 additions & 4 deletions lib/software/make/pftp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ defmodule Helix.Software.Make.PFTP do
alias Helix.Software.Model.PublicFTP

@spec server(Server.t) ::
{:ok, PublicFTP.t, %{}}
{:ok, PublicFTP.t, %{}, []}
def server(server = %Server{}) do
{:ok, pftp} = PublicFTPFlow.enable_server(server)

{:ok, pftp, %{}}
{:ok, pftp, %{}, []}
end

@spec add_file(File.t, PublicFTP.t) ::
{:ok, PublicFTP.File.t, %{}}
{:ok, PublicFTP.File.t, %{}, []}
def add_file(file = %File{}, pftp = %PublicFTP{}) do
{:ok, pftp_file} = PublicFTPFlow.add_file(pftp, file)

{:ok, pftp_file, %{}}
{:ok, pftp_file, %{}, []}
end
end
8 changes: 8 additions & 0 deletions lib/software/model/public_ftp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ defmodule Helix.Software.Model.PublicFTP do
def disable_server(changeset = %Changeset{}),
do: put_change(changeset, :is_active, false)

@spec is_active?(t) ::
boolean
@doc """
Verifies whether the given server is active (enabled) or not.
"""
def is_active?(pftp = %__MODULE__{is_active: is_active?}),
do: is_active?

@spec create_changeset(creation_params) ::
changeset
defp create_changeset(params) do
Expand Down
12 changes: 8 additions & 4 deletions lib/software/query/public_ftp.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Helix.Software.Query.PublicFTP do
alias Helix.Software.Model.File
alias Helix.Software.Model.PublicFTP

@spec fetch_file(File.t) ::
@spec fetch_file(File.idt) ::
PublicFTP.File.t
| nil
@doc """
Expand All @@ -15,9 +15,11 @@ defmodule Helix.Software.Query.PublicFTP do
- The PublicFTP server is active.
"""
def fetch_file(file = %File{}),
do: PublicFTPInternal.fetch_file(file.file_id)
do: fetch_file(file.file_id)
def fetch_file(file_id = %File.ID{}),
do: PublicFTPInternal.fetch_file(file_id)

@spec fetch_server(Server.t) ::
@spec fetch_server(Server.idt) ::
PublicFTP.t
| nil
@doc """
Expand All @@ -26,7 +28,9 @@ defmodule Helix.Software.Query.PublicFTP do
Disabled/inactive servers are returned as well.
"""
def fetch_server(server = %Server{}),
do: PublicFTPInternal.fetch(server.server_id)
do: fetch_server(server.server_id)
def fetch_server(server_id = %Server.ID{}),
do: PublicFTPInternal.fetch(server_id)

@spec list_files(Server.idt) ::
[File.t]
Expand Down
2 changes: 1 addition & 1 deletion lib/story/action/flow/story.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule Helix.Story.Action.Flow.Story do
with \
{:ok, _} <- ContextFlow.setup(entity),
{:ok, story_step} <- StoryAction.proceed_step(first_step),
{:ok, _, events} <- Steppable.setup(first_step, nil),
{:ok, _, events} <- Steppable.start(first_step, nil),
on_success(fn -> Event.emit(events, from: relay) end)
do
{:ok, story_step}
Expand Down
19 changes: 11 additions & 8 deletions lib/story/event/handler/story.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule Helix.Story.Event.Handler.Story do
Emits:
- Events returned by `Steppable` methods
- StepProceededEvent.t when action is :complete
- StepFailedEvent.t, StepRestartedEvent.t when action is :fail
- StepFailedEvent.t, StepRestartedEvent.t when action is :restart
"""
def event_handler(event) do
with \
Expand Down Expand Up @@ -71,15 +71,18 @@ defmodule Helix.Story.Event.Handler.Story do
with \
{action, step, events} <-
Steppable.handle_event(step, step.event, step.meta),
on_success(fn -> Event.emit(events, from: step.event) end),

# HACK: Workaround for HELF #29
# on_success(fn -> Event.emit(events, from: step.event) end),
Event.emit(events, from: step.event),
handle_action(action, step)
do
:ok
end
end
end

@spec handle_action(Steppable.actions, Step.t) ::
@spec handle_action(Step.callback_action, Step.t) ::
term
docp """
When a step requests to be completed, we'll call `Steppable.complete/1`,
Expand All @@ -100,8 +103,8 @@ defmodule Helix.Story.Event.Handler.Story do
If the request is to fail/abort an step, we'll call `Steppable.fail/1`,
and then handle the failure with `fail_step/1`
"""
defp handle_action(:fail, step) do
with {:ok, step, events} <- Steppable.fail(step) do
defp handle_action({:restart, reason, checkpoint}, step) do
with {:ok, step, events} <- Steppable.restart(step, reason, checkpoint) do
Event.emit(events, from: step.event)

hespawn fn ->
Expand All @@ -121,8 +124,8 @@ defmodule Helix.Story.Event.Handler.Story do
docp """
Updates the database, so that the player gets moved to the next step.
This is where we call next step's `Steppable.setup`, as well as the
`StepProceededEvent` is sent to the client
This is where we call next step's `Steppable.start`, as well as make sure the
`StepProceededEvent` is sent to the client.
Emits: StepProceededEvent.t
"""
Expand All @@ -135,7 +138,7 @@ defmodule Helix.Story.Event.Handler.Story do
# /\ Proceeds player to the next step

# Generate next step data/meta
{:ok, next_step, events} <- Steppable.setup(next_step, prev_step),
{:ok, next_step, events} <- Steppable.start(next_step, prev_step),
Event.emit(events, from: prev_step.event),

# Update step meta
Expand Down
4 changes: 2 additions & 2 deletions lib/story/make/story.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule Helix.Story.Make.Story do
@typep char_related :: %{entity: Entity.t}

@spec char(Network.id) ::
{:ok, Server.t, char_related}
{:ok, Server.t, char_related, []}
def char(network_id = %Network.ID{}) do
network = NetworkQuery.fetch(network_id)
net_data =
Expand All @@ -28,7 +28,7 @@ defmodule Helix.Story.Make.Story do
{:ok, entity, _} <- MakeEntity.entity(npc),
{:ok, server, _} <- MakeServer.npc(entity, net_data)
do
{:ok, server, %{entity: entity}}
{:ok, server, %{entity: entity}, []}
end
end
end
79 changes: 59 additions & 20 deletions lib/story/mission/tutorial/steps.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,22 @@ defmodule Helix.Story.Mission.Tutorial do

step SetupPc do

email "welcome_pc_setup",
email "welcome",
reply: "back_thanks"

on_reply "back_thanks",
send: "watchiadoing"

email "watchiadoing",
reply: "hell_yeah"

on_reply "hell_yeah",
:complete

def setup(step, _) do
e1 = send_email step, "welcome_pc_setup"
empty_setup()

def start(step, _) do
e1 = send_email step, "welcome"

{:ok, step, e1}
end
Expand All @@ -22,10 +30,10 @@ defmodule Helix.Story.Mission.Tutorial do
{:ok, step, []}
end

next_step Helix.Story.Mission.Tutorial.DownloadCrackerPublicFtp
next_step Helix.Story.Mission.Tutorial.DownloadCracker
end

step DownloadCrackerPublicFtp do
step DownloadCracker do

alias Helix.Server.Model.Server
alias Helix.Server.Query.Server, as: ServerQuery
Expand All @@ -37,7 +45,7 @@ defmodule Helix.Story.Mission.Tutorial do
alias Helix.Software.Make.File, as: MakeFile
alias Helix.Software.Make.PFTP, as: MakePFTP

email "download_cracker_public_ftp",
email "download_cracker1",
reply: ["more_info"],
locked: ["sure"]

Expand All @@ -49,17 +57,38 @@ defmodule Helix.Story.Mission.Tutorial do
send: "give_more_info"

def setup(step, _) do
{:ok, server, %{entity: entity}} = StoryMake.char(step.manager.network_id)

ContextAction.save(step.entity_id, @contact, :server_id, server.server_id)
ContextAction.save(step.entity_id, @contact, :entity_id, entity.entity_id)
# Create the underlying character (@contact) and its server
{:ok, server, %{entity: entity}, e1} =
setup_once :char, {step.entity_id, @contact} do
result = {:ok, server, %{entity: entity}, events} =
StoryMake.char(step.manager.network_id)

ContextAction.save(
step.entity_id, @contact, :server_id, server.server_id
)
ContextAction.save(
step.entity_id, @contact, :entity_id, entity.entity_id
)

result
end

# Create the Cracker the player is supposed to download
cracker = MakeFile.cracker!(server, %{bruteforce: 10, overflow: 10})

# Enable a PFTP server and put the cracker in it
{:ok, pftp, _} = MakePFTP.server(server)
MakePFTP.add_file(cracker, pftp)
{:ok, cracker, _, e2} =
setup_once :file, step.meta[:cracker_id] do
MakeFile.cracker(server, %{bruteforce: 10, overflow: 10})
end

# Enable the PFTP server and put the cracker in it
{:ok, pftp, _, e3} =
setup_once :pftp_server, server do
MakePFTP.server(server)
end

{:ok, _, _, e4} =
setup_once :pftp_file, cracker do
MakePFTP.add_file(cracker, pftp)
end

ip = ServerQuery.get_ip(server, step.manager.network_id)

Expand All @@ -71,15 +100,25 @@ defmodule Helix.Story.Mission.Tutorial do
}

# Callbacks
hespawn fn ->

# React to the moment the cracker is downloaded
story_listen cracker.file_id, FileDownloadedEvent, do: :complete
# React to the moment the cracker is downloaded
story_listen cracker.file_id, FileDownloadedEvent, do: :complete
end

e1 = send_email step, "download_cracker_public_ftp", %{ip: ip}
events = e1 ++ e2 ++ e3 ++ e4

{meta, %{}, events}
end

def start(step, prev_step) do
{meta, _, e1} = setup(step, prev_step)

e2 = send_email step, "download_cracker1", %{ip: meta.ip}

step = %{step|meta: meta}

{:ok, step, e1}
{:ok, step, e1 ++ e2}
end

format_meta do
Expand All @@ -94,6 +133,6 @@ defmodule Helix.Story.Mission.Tutorial do
{:ok, step, []}
end

next_step __MODULE__
next_step Helix.Story.Mission.Tutorial.SetupPc
end
end
12 changes: 10 additions & 2 deletions lib/story/model/step.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,19 @@ defmodule Helix.Story.Model.Step do
@type contact :: Constant.t
@type contact_id :: contact

@typedoc """
The `callback_action` type lists all possible actions that may be applied to
a step. Notably, one of them must be returned by `Steppable.handle_event/3`,
but it's also used in other contexts, including on `StepActionRequestedEvent`.
The action will be interpreted and applied at the StoryEventHandler.
Note that `:restart` also includes metadata (`reason` and `checkpoint`).
"""
@type callback_action ::
:complete
| :fail
| :regenerate
| :noop
| {:restart, reason :: atom, checkpoint :: email_id}

@spec new(t, Event.t) ::
t
Expand Down
Loading

0 comments on commit 10b773f

Please sign in to comment.