aboutsummaryrefslogtreecommitdiff
path: root/shard/lib/app/file.ex
diff options
context:
space:
mode:
Diffstat (limited to 'shard/lib/app/file.ex')
-rw-r--r--shard/lib/app/file.ex114
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