Files
wanderer/lib/wanderer_app/character/tracking_utils.ex
2025-08-29 02:30:19 +02:00

294 lines
8.5 KiB
Elixir

defmodule WandererApp.Character.TrackingUtils do
@moduledoc """
Utility functions for handling character tracking and following operations.
"""
require Logger
@doc """
Toggles the tracking state for a character on a map.
Returns the updated tracking data for all characters with access to the map.
"""
def update_tracking(
map_id,
character_eve_id,
current_user_id,
track,
caller_pid,
only_tracked_characters
)
when not is_nil(caller_pid) do
with {:ok, character} <-
WandererApp.Character.get_by_eve_id("#{character_eve_id}"),
{:ok, %{tracked: is_tracked}} <-
do_update_character_tracking(character, map_id, track, caller_pid) do
# Determine which event to send based on tracking mode and previous state
if only_tracked_characters && not is_tracked do
{:ok, nil, :not_all_characters_tracked}
else
# Get updated tracking data
{:ok, tracking_data} = build_tracking_data(map_id, current_user_id)
{:ok, tracking_data, %{event: :refresh_user_characters}}
end
else
error ->
Logger.error("Failed to toggle tracking: #{inspect(error)}")
{:error, "Failed to toggle tracking"}
end
end
def update_tracking(
_map_id,
_character_id,
_current_user_id,
_track,
_caller_pid,
_only_tracked_characters
) do
Logger.error("Failed to update tracking")
{:error, "Failed to update tracking"}
end
@doc """
Builds tracking data for all characters with access to a map.
"""
def build_tracking_data(map_id, current_user_id) do
with {:ok, map} <-
WandererApp.MapRepo.get(map_id,
acls: [
:owner_id,
members: [:role, :eve_character_id, :eve_corporation_id, :eve_alliance_id]
]
),
{:ok, user_settings} <- WandererApp.MapUserSettingsRepo.get(map_id, current_user_id),
{:ok, %{characters: characters_with_access}} <-
WandererApp.Maps.load_characters(map, current_user_id) do
# Map characters to tracking data
{:ok, characters_data} =
build_character_tracking_data(characters_with_access)
{:ok, main_character} =
get_main_character(user_settings, characters_with_access, characters_with_access)
following_character_eve_id =
case user_settings do
nil -> nil
%{following_character_eve_id: following_character_eve_id} -> following_character_eve_id
end
main_character_eve_id =
case main_character do
nil -> nil
%{eve_id: eve_id} -> eve_id
end
{:ok,
%{
characters: characters_data,
main: main_character_eve_id,
following: following_character_eve_id
}}
else
nil ->
Logger.warning("User not found when building tracking data", %{user_id: current_user_id})
{:error, "User not found"}
error ->
Logger.error("Error building tracking data: #{inspect(error)}")
{:error, "Failed to build tracking data"}
end
end
# Helper to build tracking data for each character
defp build_character_tracking_data(characters) do
{:ok,
Enum.map(characters, fn char ->
%{
character: char |> WandererAppWeb.MapEventHandler.map_ui_character_stat(),
tracked: char.tracked
}
end)}
end
# Private implementation of update character tracking
defp do_update_character_tracking(character, map_id, track, caller_pid) do
WandererApp.MapCharacterSettingsRepo.get(map_id, character.id)
|> case do
# Untracking flow
{:ok, %{tracked: true} = existing_settings} ->
if not track do
{:ok, updated_settings} =
WandererApp.MapCharacterSettingsRepo.untrack(existing_settings)
:ok = untrack([character], map_id, caller_pid)
{:ok, updated_settings}
else
{:ok, existing_settings}
end
# Tracking flow
{:ok, %{tracked: false} = existing_settings} ->
if track do
{:ok, updated_settings} = WandererApp.MapCharacterSettingsRepo.track(existing_settings)
:ok = track([character], map_id, true, caller_pid)
{:ok, updated_settings}
else
{:ok, existing_settings}
end
{:error, :not_found} ->
if track do
# Create new settings with tracking enabled
{:ok, settings} =
WandererApp.MapCharacterSettingsRepo.create(%{
character_id: character.id,
map_id: map_id,
tracked: true
})
:ok = track([character], map_id, true, caller_pid)
{:ok, settings}
else
{:error, "Character settings not found"}
end
error ->
error
end
end
# Helper functions for character tracking
def track([], _map_id, _is_track_character?, _), do: :ok
def track([character | characters], map_id, is_track_allowed, caller_pid) do
with :ok <- track_character(character, map_id, is_track_allowed, caller_pid) do
track(characters, map_id, is_track_allowed, caller_pid)
end
end
defp track_character(
%{
id: character_id,
eve_id: eve_id
},
map_id,
is_track_allowed,
caller_pid
)
when not is_nil(caller_pid) do
WandererAppWeb.Presence.update(caller_pid, map_id, character_id, %{
tracked: is_track_allowed,
from: DateTime.utc_now()
})
|> case do
{:ok, _} ->
:ok
{:error, :nopresence} ->
WandererAppWeb.Presence.track(caller_pid, map_id, character_id, %{
tracked: is_track_allowed,
from: DateTime.utc_now()
})
error ->
Logger.error("Failed to update presence: #{inspect(error)}")
{:error, "Failed to update presence"}
end
cache_key = "#{inspect(caller_pid)}_map_#{map_id}:character_#{character_id}:tracked"
case WandererApp.Cache.lookup!(cache_key, false) do
true ->
:ok
_ ->
:ok = Phoenix.PubSub.subscribe(WandererApp.PubSub, "character:#{eve_id}")
:ok = WandererApp.Cache.put(cache_key, true)
end
if is_track_allowed do
:ok = WandererApp.Character.TrackerManager.start_tracking(character_id)
end
:ok
end
defp track_character(
_character,
_map_id,
_is_track_allowed,
_caller_pid
) do
Logger.error("caller_pid is required for tracking characters")
{:error, "caller_pid is required"}
end
def untrack(characters, map_id, caller_pid) do
with false <- is_nil(caller_pid) do
character_ids = characters |> Enum.map(& &1.id)
character_ids
|> Enum.each(fn character_id ->
WandererAppWeb.Presence.update(caller_pid, map_id, character_id, %{
tracked: false,
from: DateTime.utc_now()
})
end)
# WandererApp.Map.Server.untrack_characters(map_id, character_ids)
:ok
else
true ->
Logger.error("caller_pid is required for untracking characters")
{:error, "caller_pid is required"}
end
end
# def add_characters([], _map_id, _track_character), do: :ok
# def add_characters([character | characters], map_id, track_character) do
# :ok = WandererApp.Map.Server.add_character(map_id, character, track_character)
# add_characters(characters, map_id, track_character)
# end
# def remove_characters([], _map_id), do: :ok
# def remove_characters([character | characters], map_id) do
# :ok = WandererApp.Map.Server.remove_character(map_id, character.id)
# remove_characters(characters, map_id)
# end
def get_main_character(
nil,
current_user_characters,
available_map_characters
),
do:
get_main_character(
%{main_character_eve_id: nil},
current_user_characters,
available_map_characters
)
def get_main_character(
%{main_character_eve_id: nil} = _map_user_settings,
_current_user_characters,
available_map_characters
),
do: {:ok, available_map_characters |> Enum.sort_by(& &1.inserted_at) |> List.first()}
def get_main_character(
%{main_character_eve_id: main_character_eve_id} = _map_user_settings,
current_user_characters,
_available_map_characters
),
do:
{:ok,
current_user_characters
|> Enum.find(fn c -> c.eve_id === main_character_eve_id end)}
end