Skip to content

Commit d556315

Browse files
committed
add file manager
1 parent ea273fe commit d556315

File tree

8 files changed

+144
-11
lines changed

8 files changed

+144
-11
lines changed

lib/backend/application.ex

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ defmodule Backend.Application do
1717
# Start a worker by calling: Backend.Worker.start_link(arg)
1818
# {Backend.Worker, arg},
1919
# Start to serve requests, typically the last entry
20-
BackendWeb.Endpoint
20+
BackendWeb.Endpoint,
21+
Backend.Files.FileRegistry,
22+
Backend.Files.FileManager
2123
]
2224

2325
# See https://hexdocs.pm/elixir/Supervisor.html

lib/backend/files/file.ex

+3-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ defmodule Backend.Files.File do
1212
field :content, :string
1313
end
1414

15-
def changeset(file, attrs) do
16-
file
17-
|> cast(attrs, [:name, :content], empty_values: [:content])
18-
|> validate_required([:name])
15+
def changeset(file, attr) do
16+
cast(file, attr, [:name, :content])
1917
end
18+
2019
end

lib/backend/files/file_manager.ex

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
defmodule Backend.Files.FileManager do
2+
alias Backend.Files.FileProcess
3+
4+
def start_link() do
5+
IO.puts("Starting file manager")
6+
7+
DynamicSupervisor.start_link(
8+
name: __MODULE__,
9+
strategy: :one_for_one
10+
)
11+
end
12+
13+
defp start_child(file_id) do
14+
DynamicSupervisor.start_child(
15+
__MODULE__,
16+
{Backend.Files.FileProcess, file_id}
17+
)
18+
end
19+
20+
@spec child_spec(any()) :: %{
21+
id: Backend.Files.FileManager,
22+
start: {Backend.Files.FileManager, :start_link, []},
23+
type: :supervisor
24+
}
25+
def child_spec(_arg) do
26+
%{
27+
id: __MODULE__,
28+
start: {__MODULE__, :start_link, []},
29+
type: :supervisor
30+
}
31+
end
32+
33+
def get_or_start_process(file_id) do
34+
case Registry.lookup(Backend.Files.FileRegistry, file_id) do
35+
[] ->
36+
# Процесс не существует — запускаем новый
37+
start_child(file_id)
38+
:ok
39+
40+
[{_pid, _}] ->
41+
# Процесс уже существует
42+
:ok
43+
end
44+
end
45+
46+
def update_file_content(file_id, content) do
47+
# Убедимся, что процесс существует
48+
get_or_start_process(file_id)
49+
50+
# Обновляем содержимое файла в процессе
51+
FileProcess.update_content(file_id, content)
52+
end
53+
end

lib/backend/files/file_process.ex

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
defmodule Backend.Files.FileProcess do
2+
use GenServer, restart: :temporary
3+
alias Backend.Files
4+
5+
@timeout 5_000 # Время бездействия (например, 5 секунд)
6+
7+
# API
8+
def start_link(file_id) do
9+
GenServer.start_link(__MODULE__, file_id, name: via_tuple(file_id))
10+
end
11+
12+
def update_content(file_id, new_content) do
13+
GenServer.call(via_tuple(file_id), {:update_content, new_content})
14+
end
15+
16+
defp via_tuple(file_id) do
17+
{:via, Registry, {Backend.Files.FileRegistry, file_id}}
18+
end
19+
20+
# Callbacks
21+
def init(file_id) do
22+
file = Files.get_file(file_id) # Загружаем файл из БД при запуске
23+
{:ok, %{file: file, timer: reset_timer(nil)}}
24+
end
25+
26+
def handle_call({:update_content, new_content}, _from, %{file: file, timer: timer} = state) do
27+
new_file = %{file | content: new_content}
28+
{:reply, :ok, %{state | file: new_file, timer: reset_timer(timer)}}
29+
end
30+
31+
def handle_info(:timeout, %{file: file} = state) do
32+
IO.inspect file
33+
Files.save_file(file) # Сохраняем в БД перед завершением
34+
{:stop, :normal, state}
35+
end
36+
37+
defp reset_timer(timer) do
38+
# Отменяем старый таймер, если он существует
39+
if timer, do: Process.cancel_timer(timer)
40+
# Устанавливаем новый таймер
41+
Process.send_after(self(), :timeout, @timeout)
42+
end
43+
end

lib/backend/files/file_registry.ex

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
defmodule Backend.Files.FileRegistry do
2+
3+
def start_link do
4+
IO.puts("Starting file registry")
5+
6+
Registry.start_link(name: __MODULE__,keys: :unique)
7+
end
8+
9+
def via_tuple(key) do
10+
{:via, Registry, {__MODULE__, key}}
11+
end
12+
13+
def child_spec(_) do
14+
Supervisor.child_spec(
15+
Registry,
16+
id: __MODULE__,
17+
start: {__MODULE__, :start_link, []}
18+
)
19+
end
20+
21+
end

lib/backend/files/files.ex

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@ defmodule Backend.Files do
1616
end
1717

1818
def save_file(file) do
19+
# Это сделано специально, так как без обнуления контента cast думает, что ничего не изменилось
20+
content = file.content
21+
file = %{file | content: ""}
22+
1923
file
20-
|> File.changeset(%{})
21-
|> Repo.update()
24+
|> File.changeset(%{content: content})
25+
|> Repo.update!()
2226
end
2327

2428
def delete_file(file) do

lib/backend_web/channels/room_channel.ex

+14-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@ defmodule BackendWeb.FileChannel do
22
@moduledoc """
33
Websocket channel for each .mds file
44
"""
5+
alias Backend.Files
6+
alias Backend.Files.FileManager
57
use Phoenix.Channel
68

7-
def join("file:" <> _room_id, _params, socket) do
8-
{:ok, socket}
9+
def join("file:" <> file_id, _params, socket) do
10+
case Files.get_file(file_id) do
11+
nil -> {:error, %{reason: "File with id " <> file_id <> " does not exist"}}
12+
_ -> {:ok, assign(socket, :file_id, file_id)}
13+
end
914
end
1015

1116
def handle_in("edit", %{"content" => content}, socket) do
12-
# Рассылка обновлений всем клиентам
17+
# Получаем file_id из socket.assigns
18+
file_id = socket.assigns.file_id
19+
20+
# Вызываем FileManager для обновления содержимого файла
21+
FileManager.update_file_content(file_id, content)
22+
23+
# Рассылаем обновления всем клиентам
1324
broadcast!(socket, "update", %{content: content})
1425
{:noreply, socket}
1526
end

priv/repo/migrations/20241129212656_add_file.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule Backend.Repo.Migrations.AddFile do
44
def change do
55
create table(:files) do
66
add :name, :string, null: false
7-
add :content, :binary
7+
add :content, :string
88
end
99
end
1010
end

0 commit comments

Comments
 (0)