Handle HAVE_ALL and HAVE_NONE. Cleanup the BITFIELD message.
[etorrent.git] / lib / etorrent-1.0 / src / etorrent_fs_checker.erl
blobdc48c2ed8e88371e9724c3bfc8e7167f15f9052d
1 %%%-------------------------------------------------------------------
2 %%% File : check_torrent.erl
3 %%% Author : Jesper Louis Andersen <jlouis@succubus.local.domain>
4 %%% License : See COPYING
5 %%% Description : Code for checking the correctness of a torrent file map
6 %%%
7 %%% Created : 6 Jul 2007 by Jesper Louis Andersen <jlouis@succubus.local.domain>
8 %%%-------------------------------------------------------------------
9 -module(etorrent_fs_checker).
11 -include("etorrent_piece.hrl").
13 %% API
14 -export([read_and_check_torrent/3, load_torrent/1, ensure_file_sizes_correct/1,
15 check_torrent/2]).
17 %%====================================================================
18 %% API
19 %%====================================================================
21 %% Check the contents of torrent Id, backed by filesystem FS and report a
22 %% list of bad pieces.
23 check_torrent(FS, Id) ->
24 Pieces = etorrent_piece_mgr:select(Id),
25 PieceCheck =
26 fun (#piece { idpn = {_, PN}, hash = Hash}) ->
27 {ok, Data} = etorrent_fs:read_piece(FS, PN),
28 Hash =/= crypto:sha(Data)
29 end,
30 [P#piece.idpn || P <- Pieces,
31 PieceCheck(P)].
33 read_and_check_torrent(Id, SupervisorPid, Path) ->
34 %% Load the torrent
35 {ok, Torrent, Files, Infohash} =
36 load_torrent(Path),
38 %% Ensure the files are filled up with garbage of correct size
39 ok = ensure_file_sizes_correct(Files),
41 %% Build the dictionary mapping pieces to file operations
42 {ok, FilePieceList, NumberOfPieces} =
43 build_dictionary_on_files(Id, Torrent, Files),
45 %% Check the contents of the torrent, updates the state of the piecemap
46 FS = etorrent_t_sup:get_pid(SupervisorPid, fs),
47 case etorrent_fast_resume:query_state(Id) of
48 seeding -> initialize_pieces_seed(Id, FilePieceList);
49 {bitfield, BF} -> initialize_pieces_from_bitfield(Id, BF, NumberOfPieces, FilePieceList);
50 leeching ->
51 ok = etorrent_piece_mgr:add_pieces(
52 Id,
53 [{PN, Hash, Fls, not_fetched} || {PN, {Hash, Fls}} <- FilePieceList]),
54 ok = initialize_pieces_from_disk(FS, Id, FilePieceList);
55 %% XXX: The next one here could initialize with not_fetched all over
56 unknown ->
57 ok = etorrent_piece_mgr:add_pieces(
58 Id,
59 [{PN, Hash, Fls, not_fetched} || {PN, {Hash, Fls}} <- FilePieceList]),
60 ok = initialize_pieces_from_disk(FS, Id, FilePieceList)
61 end,
63 {ok, Torrent, FS, Infohash, NumberOfPieces}.
65 load_torrent(Path) ->
66 {ok, Workdir} = application:get_env(etorrent, dir),
67 P = filename:join([Workdir, Path]),
68 Torrent = etorrent_bcoding:parse(P),
69 Files = etorrent_metainfo:get_files(Torrent),
70 Name = etorrent_metainfo:get_name(Torrent),
71 InfoHash = etorrent_metainfo:get_infohash(Torrent),
72 FilesToCheck =
73 [{filename:join([Name, Filename]), Size} ||
74 {Filename, Size} <- Files],
75 {ok, Torrent, FilesToCheck, InfoHash}.
77 ensure_file_sizes_correct(Files) ->
78 {ok, Workdir} = application:get_env(etorrent, dir),
79 lists:foreach(
80 fun ({Pth, ISz}) ->
81 F = filename:join([Workdir, Pth]),
82 Sz = filelib:file_size(F),
83 case Sz == ISz of
84 true ->
85 ok;
86 false ->
87 Missing = ISz - Sz,
88 fill_file_ensure_path(F, Missing)
89 end
90 end,
91 Files),
92 ok.
94 initialize_pieces_seed(Id, FilePieceList) ->
95 etorrent_piece_mgr:add_pieces(
96 Id,
97 [{PN, Hash, Files, fetched} || {PN, {Hash, Files}} <- FilePieceList]).
99 initialize_pieces_from_bitfield(Id, BitField, NumPieces, FilePieceList) ->
100 {ok, Set} = etorrent_peer_communication:destruct_bitfield(NumPieces, BitField),
101 F = fun (PN) ->
102 case gb_sets:is_element(PN, Set) of
103 true -> fetched;
104 false -> not_fetched
106 end,
107 Pieces = [{PN, Hash, Files, F(PN)} || {PN, {Hash, Files}} <- FilePieceList],
108 etorrent_piece_mgr:add_pieces(Id, Pieces).
110 initialize_pieces_from_disk(FS, Id, FilePieceList) ->
111 F = fun(PN, Hash, Files) ->
112 {ok, Data} = etorrent_fs:read_piece(FS, PN),
113 State = case Hash =:= crypto:sha(Data) of
114 true -> fetched;
115 false -> not_fetched
116 end,
117 {PN, Hash, Files, State}
118 end,
119 etorrent_piece_mgr:add_pieces(
121 [F(PN, Hash, Files) || {PN, {Hash, Files}} <- FilePieceList]).
123 build_dictionary_on_files(TorrentId, Torrent, Files) ->
124 Pieces = etorrent_metainfo:get_pieces(Torrent),
125 PSize = etorrent_metainfo:get_piece_length(Torrent),
126 LastPieceSize = lists:sum([S || {_F, S} <- Files]) rem PSize,
127 {ok, PieceList, N} = construct_fpmap(Files, 0, PSize, LastPieceSize,
128 lists:zip(lists:seq(0, length(Pieces)-1), Pieces),
129 [], 0),
130 MappedPieces = [{Num, {Hash, insert_into_path_map(Ops, TorrentId)}} ||
131 {Num, {Hash, Ops}} <- PieceList],
132 {ok, MappedPieces, N}.
134 %%====================================================================
135 %% Internal functions
136 %%====================================================================
138 extract_piece(0, Fs, Offset, B) ->
139 {ok, Fs, Offset, B};
140 extract_piece(Left, [], _O, _Building) ->
141 {error_need_files, Left};
142 extract_piece(Left, [{Pth, Sz} | R], Offset, Building) ->
143 case (Sz - Offset) > Left of
144 true ->
145 % There is enough bytes left in Pth
146 {ok, [{Pth, Sz} | R], Offset+Left,
147 [{Pth, Offset, Left} | Building]};
148 false ->
149 % There is not enough space left in Pth
150 BytesWeCanGet = Sz - Offset,
151 extract_piece(Left - BytesWeCanGet, R, 0,
152 [{Pth, Offset, BytesWeCanGet} | Building])
153 end.
155 construct_fpmap([], _Offset, _PieceSize, _LPS, [], Done, N) ->
156 {ok, Done, N};
157 construct_fpmap([], _O, _P, _LPS, _Pieces, _D, _N) ->
158 error_more_pieces;
159 construct_fpmap(FileList, Offset, PieceSize, LastPieceSize,
160 [{Num, Hash}], Done, N) -> % Last piece
161 {ok, FL, OS, Ops} = extract_piece(case LastPieceSize of
162 0 -> PieceSize;
163 K -> K
164 end,
165 FileList, Offset, []),
166 construct_fpmap(FL, OS, PieceSize, LastPieceSize, [],
167 [{Num, {Hash, lists:reverse(Ops)}} | Done], N+1);
168 construct_fpmap(FileList, Offset, PieceSize, LastPieceSize,
169 [{Num, Hash} | Ps], Done, N) ->
170 {ok, FL, OS, Ops} = extract_piece(PieceSize, FileList, Offset, []),
171 construct_fpmap(FL, OS, PieceSize, LastPieceSize, Ps,
172 [{Num, {Hash, lists:reverse(Ops)}} | Done], N+1).
174 insert_into_path_map(Ops, TorrentId) ->
175 insert_into_path_map(Ops, TorrentId, none, none).
177 insert_into_path_map([], _, _, _) -> [];
178 insert_into_path_map([{Path, Offset, Size} | Next], TorrentId, Path, Keyval) ->
179 [{Keyval, Offset, Size} | insert_into_path_map(Next, TorrentId, Path, Keyval)];
180 insert_into_path_map([{Path, Offset, Size} | Next], TorrentId, _Key, _KeyVal) ->
181 KeyVal = etorrent_path_map:select(Path, TorrentId),
182 [{KeyVal, Offset, Size} | insert_into_path_map(Next, TorrentId, Path, KeyVal)].
185 fill_file_ensure_path(Path, Missing) ->
186 case file:open(Path, [read, write, delayed_write, binary, raw]) of
187 {ok, FD} ->
188 fill_file(FD, Missing);
189 {error, enoent} ->
190 % File missing
191 ok = filelib:ensure_dir(Path),
192 case file:open(Path, [read, write, delayed_write, binary, raw]) of
193 {ok, FD} ->
194 fill_file(FD, Missing);
195 {error, Reason} ->
196 {error, Reason}
198 end.
200 fill_file(FD, Missing) ->
201 {ok, _NP} = file:position(FD, {eof, Missing-1}),
202 file:write(FD, <<0>>),
203 file:close(FD).