this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add SimFile and FileStore

+274 -4
+18 -1
lib/construct/scheduler.ex
··· 2 2 use GenServer 3 3 4 4 alias Hobbes.Construct.SimLog 5 - alias Hobbes.Construct.Scheduler.{ProcStore, ProcQueue, ProcRegistry} 5 + alias Hobbes.Construct.Scheduler.{ProcStore, ProcQueue, ProcRegistry, FileStore} 6 6 alias Hobbes.Construct.Scheduler.ProcStore.ProcState 7 7 8 8 defmodule Spawn do ··· 37 37 proc_store: ProcStore.t, 38 38 proc_queue: ProcQueue.t, 39 39 proc_registry: ProcRegistry.t, 40 + file_stores: %{atom => FileStore.t}, 40 41 log_server_pid: pid, 41 42 resumes_without_send: non_neg_integer, 42 43 } ··· 47 48 proc_store: nil, 48 49 proc_queue: nil, 49 50 proc_registry: nil, 51 + file_stores: %{}, 50 52 51 53 log_server_pid: nil, 52 54 resumes_without_send: 0, ··· 131 133 GenServer.call(scheduler, {:register_process, self(), pid, name}) 132 134 end 133 135 136 + @spec get_file_store(pid) :: FileStore.t 137 + def get_file_store(scheduler) do 138 + GenServer.call(scheduler, {:get_file_store, self()}) 139 + end 140 + 134 141 @spec current_time(term, pid) :: non_neg_integer 135 142 def current_time(scheduler, for_pid \\ self()) when is_pid(for_pid) do 136 143 GenServer.call(scheduler, {:current_time, for_pid}) ··· 161 168 proc_store: ProcStore.new(), 162 169 proc_queue: ProcQueue.new(), 163 170 proc_registry: ProcRegistry.new(), 171 + # TODO: spawn per node instead 172 + file_stores: %{nonode: FileStore.new()}, 164 173 } 165 174 ProcStore.add_process(state.proc_store, initial_pid) 166 175 ··· 184 193 {:error, _error} -> 185 194 {:reply, :error, state} 186 195 end 196 + end 197 + 198 + def handle_call({:get_file_store, pid}, _from, %State{} = state) when is_pid(pid) do 199 + # TODO: node for pid 200 + node = :nonode 201 + 202 + file_store = Map.fetch!(state.file_stores, node) 203 + {:reply, file_store, state} 187 204 end 188 205 189 206 def handle_call({:current_time, for_pid}, _from, %State{} = state) when is_pid(for_pid) do
+111
lib/construct/scheduler/file_store.ex
··· 1 + defmodule Hobbes.Construct.Scheduler.FileStore do 2 + alias Hobbes.KV.FlatKV 3 + 4 + @type t :: FlatKV.t 5 + @type posix :: :enoent | :eexist | :enotdir | :eisdir 6 + 7 + @spec new :: t 8 + def new do 9 + kv = FlatKV.new(public: true) 10 + FlatKV.put(kv, "/", "directory") 11 + 12 + kv 13 + end 14 + 15 + @spec mkdir(t, binary) :: :ok | {:error, posix} 16 + def mkdir(kv, path) do 17 + path = normalize_directory(path) 18 + 19 + case all_components_dir?(kv, path) do 20 + true -> 21 + case exists?(kv, path) do 22 + false -> 23 + FlatKV.put(kv, path, "directory") 24 + :ok 25 + 26 + true -> {:error, :eexist} 27 + end 28 + 29 + false -> {:error, :enoent} 30 + end 31 + end 32 + 33 + @spec mkdir_p(t, binary) :: :ok | {:error, posix} 34 + def mkdir_p(kv, path) do 35 + path 36 + |> components() 37 + |> Enum.reduce_while(:ok, fn p, :ok -> 38 + case exists?(kv, p) do 39 + false -> 40 + :ok = mkdir(kv, p) 41 + {:cont, :ok} 42 + 43 + true -> 44 + case dir?(kv, p) do 45 + true -> {:cont, :ok} 46 + false -> {:halt, {:error, :enotdir}} 47 + end 48 + end 49 + end) 50 + end 51 + 52 + @spec write(t, binary, binary) :: :ok | {:error, posix} 53 + def write(kv, path, contents) do 54 + case all_components_dir?(kv, path) do 55 + true -> 56 + FlatKV.put(kv, path, "file:" <> contents) 57 + :ok 58 + false -> 59 + {:error, :enoent} 60 + end 61 + end 62 + 63 + @spec read(t, binary) :: {:ok, binary} | {:error, posix} 64 + def read(kv, path) do 65 + case FlatKV.get(kv, path) do 66 + "file:" <> contents -> 67 + {:ok, contents} 68 + 69 + "directory" -> 70 + {:error, :eisdir} 71 + 72 + nil -> 73 + {:error, :enoent} 74 + end 75 + end 76 + 77 + @spec dir?(t, binary) :: boolean 78 + def dir?(kv, path) do 79 + path = normalize_directory(path) 80 + FlatKV.get(kv, path) == "directory" 81 + end 82 + 83 + @spec exists?(t, binary) :: boolean 84 + def exists?(kv, path) do 85 + FlatKV.get(kv, path) != nil 86 + end 87 + 88 + defp components(path) do 89 + split = Path.split(path) 90 + 91 + Enum.map(1..length(split), fn count -> 92 + Enum.take(split, count) |> Path.join() 93 + end) 94 + end 95 + 96 + defp all_components_dir?(kv, path) do 97 + path 98 + |> components() 99 + |> case do 100 + [] -> [] 101 + list -> List.delete_at(list, -1) 102 + end 103 + |> Enum.all?(&dir?(kv, &1)) 104 + end 105 + 106 + defp normalize_directory("/"), do: "/" 107 + defp normalize_directory(path), do: String.trim_trailing(path, "/") 108 + 109 + @doc false 110 + def dump(kv), do: FlatKV.dump(kv) 111 + end
+42
lib/construct/sim_file.ex
··· 1 + defmodule Hobbes.Construct.SimFile do 2 + alias Hobbes.Construct.Scheduler 3 + alias Hobbes.Construct.Scheduler.FileStore 4 + 5 + import Hobbes.Construct.SimServer, only: [get_scheduler_pid: 0] 6 + 7 + @spec mkdir(binary) :: :ok | {:error, File.posix} 8 + def mkdir(path) when is_binary(path) do 9 + case get_scheduler_pid() do 10 + spid when is_pid(spid) -> 11 + fs = Scheduler.get_file_store(spid) 12 + FileStore.mkdir(fs, path) 13 + end 14 + end 15 + 16 + @spec mkdir_p(binary) :: :ok | {:error, File.posix} 17 + def mkdir_p(path) when is_binary(path) do 18 + case get_scheduler_pid() do 19 + spid when is_pid(spid) -> 20 + fs = Scheduler.get_file_store(spid) 21 + FileStore.mkdir_p(fs, path) 22 + end 23 + end 24 + 25 + @spec write(binary, binary) :: :ok | {:error, File.posix} 26 + def write(path, contents) when is_binary(path) and is_binary(contents) do 27 + case get_scheduler_pid() do 28 + spid when is_pid(spid) -> 29 + fs = Scheduler.get_file_store(spid) 30 + FileStore.write(fs, path, contents) 31 + end 32 + end 33 + 34 + @spec read(binary) :: {:ok, binary} | {:error, File.posix} 35 + def read(path) when is_binary(path) do 36 + case get_scheduler_pid() do 37 + spid when is_pid(spid) -> 38 + fs = Scheduler.get_file_store(spid) 39 + FileStore.read(fs, path) 40 + end 41 + end 42 + end
+5 -2
lib/kv/flat_kv.ex
··· 5 5 6 6 alias Hobbes.Structs.RangeResult 7 7 8 - def new do 9 - :ets.new(:flat_kv, [:ordered_set, :private]) 8 + @type t :: :ets.table 9 + 10 + @spec new :: t 11 + def new(opts \\ []) do 12 + :ets.new(:flat_kv, [:ordered_set, if(opts[:public], do: :public, else: :private)]) 10 13 end 11 14 12 15 @spec load(:ets.table, [{binary, binary}]) :: :ok
+84
test/construct/scheduler/file_store_test.exs
··· 1 + defmodule Hobbes.Construct.Scheduler.FileStoreTest do 2 + use ExUnit.Case, async: true 3 + 4 + alias Hobbes.Construct.Scheduler.FileStore 5 + 6 + @moduletag :file_store 7 + 8 + setup do 9 + %{fs: FileStore.new()} 10 + end 11 + 12 + describe "mkdir/2" do 13 + test "returns error if file in path", %{fs: fs} do 14 + :ok = FileStore.mkdir(fs, "/foo") 15 + :ok = FileStore.write(fs, "/foo/bar", "hello") 16 + 17 + assert {:error, :eexist} = FileStore.mkdir(fs, "/foo/bar") 18 + assert {:error, :enoent} = FileStore.mkdir(fs, "/foo/bar/baz") 19 + end 20 + 21 + test "returns error if directory exists", %{fs: fs} do 22 + :ok = FileStore.mkdir(fs, "/foo") 23 + assert {:error, :eexist} = FileStore.mkdir(fs, "/foo") 24 + end 25 + end 26 + 27 + describe "mkdir_p/2" do 28 + test "returns error if file in path", %{fs: fs} do 29 + assert :ok = FileStore.mkdir(fs, "/foo") 30 + assert :ok = FileStore.write(fs, "/foo/file", "hello world") 31 + 32 + assert {:error, :enotdir} = FileStore.mkdir_p(fs, "/foo/file/bar") 33 + end 34 + 35 + test "makes directories", %{fs: fs} do 36 + assert :ok = FileStore.mkdir_p(fs, "/foo/bar/baz") 37 + assert :ok = FileStore.mkdir_p(fs, "/foo/bar/baz/qux") 38 + 39 + assert FileStore.dir?(fs, "/") 40 + assert FileStore.dir?(fs, "/foo") 41 + assert FileStore.dir?(fs, "/foo/bar") 42 + assert FileStore.dir?(fs, "/foo/bar/baz") 43 + assert FileStore.dir?(fs, "/foo/bar/baz/qux") 44 + assert FileStore.dir?(fs, "/foo/bar/baz/qux/") 45 + end 46 + end 47 + 48 + describe "write/3" do 49 + test "returns error if no directory", %{fs: fs} do 50 + assert {:error, :enoent} = FileStore.write(fs, "/foo/bar.txt", "hello") 51 + end 52 + 53 + test "returns error if file in path", %{fs: fs} do 54 + :ok = FileStore.write(fs, "/foo", "hello") 55 + assert {:error, :enoent} = FileStore.write(fs, "/foo/bar.txt", "hello") 56 + end 57 + 58 + test "writes file", %{fs: fs} do 59 + :ok = FileStore.mkdir_p(fs, "/foo/bar") 60 + 61 + assert :ok = FileStore.write(fs, "/foo/bar/baz.txt", "hello world!") 62 + 63 + assert {:ok, "hello world!"} = FileStore.read(fs, "/foo/bar/baz.txt") 64 + end 65 + end 66 + 67 + describe "read/2" do 68 + test "returns error if file does not exist", %{fs: fs} do 69 + assert {:error, :enoent} = FileStore.read(fs, "/foo/bar.txt") 70 + end 71 + 72 + test "returns error if file is a directory", %{fs: fs} do 73 + :ok = FileStore.mkdir_p(fs, "/foo/bar") 74 + assert {:error, :eisdir} = FileStore.read(fs, "/foo/bar") 75 + end 76 + 77 + test "reads file", %{fs: fs} do 78 + :ok = FileStore.mkdir(fs, "/foo") 79 + :ok = FileStore.write(fs, "/foo/bar.txt", "hello world!") 80 + 81 + assert {:ok, "hello world!"} = FileStore.read(fs, "/foo/bar.txt") 82 + end 83 + end 84 + end
+14 -1
test/construct_test.exs
··· 1 1 defmodule Hobbes.ConstructTest do 2 2 use ExUnit.Case, async: false 3 - alias Hobbes.Construct.SimServer 3 + 4 + alias Hobbes.Construct.{SimServer, SimFile} 4 5 5 6 @moduletag :construct 6 7 ··· 50 51 51 52 assert_receive {:EXIT, ^pid, {%RuntimeError{message: "Some error"}, _}} 52 53 Process.sleep(1) 54 + end 55 + end 56 + 57 + describe "SimFile" do 58 + test "reads and writes files" do 59 + SimServer.start_scheduler() 60 + 61 + assert :ok = SimFile.mkdir_p("/foo/bar/baz") 62 + assert :ok = SimFile.write("/foo/bar/baz/qux.txt", "hello world!") 63 + 64 + assert {:ok, "hello world!"} = SimFile.read("/foo/bar/baz/qux.txt") 65 + assert {:error, :enoent} = SimFile.read("/foo/qux.txt") 53 66 end 54 67 end 55 68 end