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
|