aboutsummaryrefslogtreecommitdiffstats
path: root/shard/lib/app/file.ex
blob: a874222ca0b13f47ddd7d7ef480286d41a4eff72 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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