diff options
Diffstat (limited to 'shard/lib/app')
-rw-r--r-- | shard/lib/app/file.ex | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/shard/lib/app/file.ex b/shard/lib/app/file.ex new file mode 100644 index 0000000..a874222 --- /dev/null +++ b/shard/lib/app/file.ex @@ -0,0 +1,114 @@ +defmodule SApp.File do + @moduledoc""" + Shard application for a file identified by its infohash. The file cannot be modified. + + The infohash is the hash of an info struct containing: + + %Info{ + merkle_root: hash + file_hash: hash + size: int + mime_type: string + } + + The file is cut in blocks of 4kb that are collected in a 64-ary Merkle tree. + """ + + use GenServer + + require Logger + + alias SData.MerkleTree, as: MT + + defmodule Manifest do + @moduledoc""" + Manifest for a file. + The file is identified by the root hash of its Merkle tree and by its mime type. + """ + defstruct [:infohash] + + defimpl Shard.Manifest do + def module(_m), do: SApp.File + def is_valid?(m) do + byte_size(m.infohash) == 32 + end + end + end + + defmodule Info do + @moduledoc""" + A file info struct. + """ + defstruct [:merkle_root, :file_hash, :size, :mime_type] + end + + defmodule State do + defstruct [:infohash, :id, :manifest, :netgroup, :info, :infobin, :store, :missing, :path] + end + + def start_link(manifest) do + GenServer.start_link(__MODULE__, manifest) + end + + def init(manifest) do + %Manifest{infohash: infohash} = manifest + id = SData.term_hash manifest + + Shard.Manager.dispatch_to(id, nil, self()) + {infobin, info} = case Shard.Manager.load_state(id) do + nil -> {nil, nil} + infobin -> {infobin, SData.term_unbin infobin} + end + netgroup = %SNet.PubShardGroup{id: id} + SNet.Group.init_lookup(netgroup, self()) + + path = [Application.get_env(:shard, :data_path), "#{id|>Base.encode16}"] |> Path.join + + {:ok, store} = SApp.PageStore.start_link(id, :meta, netgroup) + + {:ok, %State{ + id: id, infohash: infohash, manifest: manifest, netgroup: netgroup, + infobin: infobin, info: info, store: store, missing: nil, path: path + }} + end + + def handle_cast({:init_with, file_path, infobin, mt}, state) do + info = SData.term_unbin(infobin) + for {k, v} <- mt.store do + {^k, _} = SData.PageStore.put(state.store, v) + end + File.copy!(file_path, state.path) + new_state = %{state | + infobin: infobin, + info: info, + } + {:noreply, new_state} + end + + # TODO networking etc + + # ========= + # INTERFACE + # ========= + + @doc""" + Create a File shard from a file path + """ + def create(path, mime_type) do + %File.Stat{size: size} = File.stat!(path) + mt = MT.create(path) + hash = SData.file_hash(path) + + info = %Info{ + merkle_root: mt.root, + file_hash: hash, + size: size, + mime_type: mime_type, + } + infobin = SData.term_bin(info) + infohash = SData.bin_hash(infobin) + manifest = %Manifest{infohash: infohash} + pid = Shard.Manager.find_or_start(manifest) + GenServer.cast(pid, {:init_with, path, infobin, mt}) + end +end |