Handle HAVE_ALL and HAVE_NONE. Cleanup the BITFIELD message.
[etorrent.git] / lib / etorrent-1.0 / src / etorrent_t_control.erl
blob1ee6b728dcd78ddea61e858d8e1e75c626e93103
1 %%%-------------------------------------------------------------------
2 %%% File : torrent.erl
3 %%% Author : Jesper Louis Andersen <jlouis@succubus.local.domain>
4 %%% License : See COPYING
5 %%% Description : Representation of a torrent for downloading
6 %%%
7 %%% Created : 9 Jul 2007 by Jesper Louis Andersen
8 %%% <jlouis@succubus.local.domain>
9 %%%-------------------------------------------------------------------
10 -module(etorrent_t_control).
12 -behaviour(gen_fsm).
14 -include("etorrent_piece.hrl").
16 %% API
17 -export([start_link/3, start/1, stop/1,
18 torrent_checked/2, tracker_error_report/2, seed/1,
19 tracker_warning_report/2,
21 check_torrent/1]).
23 %% gen_fsm callbacks
24 -export([init/1, handle_event/3, initializing/2, started/2,
25 stopped/2, handle_sync_event/4, handle_info/3, terminate/3,
26 code_change/4]).
28 -record(state, {id = none,
30 path = none,
31 peer_id = none,
33 parent_pid = none,
34 tracker_pid = none,
35 file_system_pid = none,
37 disk_state = none,
38 available_peers = []}).
40 -define(CHECK_WAIT_TIME, 3000).
42 %%====================================================================
43 %% API
44 %%====================================================================
45 %%--------------------------------------------------------------------
46 %% Function: start_link() -> ok,Pid} | ignore | {error,Error}
47 %% Description:Creates a gen_fsm process which calls Module:init/1 to
48 %% initialize. To ensure a synchronized start-up procedure, this function
49 %% does not return until Module:init/1 has returned.
50 %%--------------------------------------------------------------------
51 start_link(Id, Path, PeerId) ->
52 gen_fsm:start_link(?MODULE, [self(), Id, Path, PeerId], []).
54 stop(Pid) ->
55 gen_fsm:send_event(Pid, stop).
57 start(Pid) ->
58 gen_fsm:send_event(Pid, start).
60 check_torrent(Pid) ->
61 gen_fsm:send_event(Pid, check_torrent).
63 torrent_checked(Pid, DiskState) ->
64 gen_fsm:send_event(Pid, {torrent_checked, DiskState}).
66 tracker_error_report(Pid, Report) ->
67 gen_fsm:send_event(Pid, {tracker_error_report, Report}).
69 tracker_warning_report(Pid, Report) ->
70 gen_fsm:send_event(Pid, {tracker_warning_report, Report}).
72 seed(Pid) ->
73 gen_fsm:send_event(Pid, seed).
75 %%====================================================================
76 %% gen_fsm callbacks
77 %%====================================================================
78 %%--------------------------------------------------------------------
79 %% Function: init(Args) -> {ok, StateName, State} |
80 %% {ok, StateName, State, Timeout} |
81 %% ignore |
82 %% {stop, StopReason}
83 %% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or
84 %% gen_fsm:start_link/3,4, this function is called by the new process to
85 %% initialize.
86 %%--------------------------------------------------------------------
87 init([Parent, Id, Path, PeerId]) ->
88 process_flag(trap_exit, true),
89 etorrent_tracking_map:new(Path, Parent, Id),
90 {ok, initializing, #state{id = Id,
91 path = Path,
92 peer_id = PeerId,
93 parent_pid = Parent}, 0}. % Force timeout instantly.
95 %%--------------------------------------------------------------------
96 %% Function:
97 %% state_name(Event, State) -> {next_state, NextStateName, NextState}|
98 %% {next_state, NextStateName,
99 %% NextState, Timeout} |
100 %% {stop, Reason, NewState}
101 %% Description:There should be one instance of this function for each possible
102 %% state name. Whenever a gen_fsm receives an event sent using
103 %% gen_fsm:send_event/2, the instance of this function with the same name as
104 %% the current state name StateName is called to handle the event. It is also
105 %% called if a timeout occurs.
106 %%--------------------------------------------------------------------
107 % Load a torrent at Path with Torrent
108 initializing(timeout, S) ->
109 case etorrent_tracking_map:is_ready_for_checking(S#state.id) of
110 false ->
111 {next_state, initializing, S, ?CHECK_WAIT_TIME};
112 true ->
113 %% TODO: Try to coalesce some of these operations together.
115 %% Read the torrent, check its contents for what we are missing
116 etorrent_event_mgr:checking_torrent(S#state.id),
117 {ok, Torrent, FSPid, InfoHash, NumberOfPieces} =
118 etorrent_fs_checker:read_and_check_torrent(
119 S#state.id,
120 S#state.parent_pid,
121 S#state.path),
122 %% Update the tracking map. This torrent has been started, and we
123 %% know its infohash
124 etorrent_tracking_map:statechange(S#state.id, {infohash, InfoHash}),
125 etorrent_tracking_map:statechange(S#state.id, started),
127 %% Add a torrent entry for this torrent.
128 ok = etorrent_torrent:new(
129 S#state.id,
130 {{uploaded, 0},
131 {downloaded, 0},
132 {left, calculate_amount_left(S#state.id)},
133 {total, etorrent_metainfo:get_length(Torrent)}},
134 NumberOfPieces),
136 %% Start the tracker
137 {ok, TrackerPid} =
138 etorrent_t_sup:add_tracker(
139 S#state.parent_pid,
140 etorrent_metainfo:get_url(Torrent),
141 etorrent_metainfo:get_infohash(Torrent),
142 S#state.peer_id,
143 S#state.id),
145 %% Since the process will now go to a hibernation state, GC it
146 etorrent_event_mgr:started_torrent(S#state.id),
147 garbage_collect(),
148 {next_state, started,
149 S#state{file_system_pid = FSPid,
150 tracker_pid = TrackerPid}}
152 end.
154 started(stop, S) ->
155 {stop, argh, S};
156 started(check_torrent, S) ->
157 case etorrent_fs_checker:check_torrent(S#state.file_system_pid,
158 S#state.id) of
159 [] -> {next_state, started, S};
160 Errors ->
161 error_logger:info_report([errornous_pieces, {Errors}]),
162 {next_state, started, S}
163 end;
164 started({tracker_error_report, Reason}, S) ->
165 io:format("Got tracker error: ~s~n", [Reason]),
166 {next_state, started, S}.
168 stopped(start, S) ->
169 {stop, argh, S}.
171 %%--------------------------------------------------------------------
172 %% Function:
173 %% state_name(Event, From, State) -> {next_state, NextStateName, NextState} |
174 %% {next_state, NextStateName,
175 %% NextState, Timeout} |
176 %% {reply, Reply, NextStateName, NextState}|
177 %% {reply, Reply, NextStateName,
178 %% NextState, Timeout} |
179 %% {stop, Reason, NewState}|
180 %% {stop, Reason, Reply, NewState}
181 %% Description: There should be one instance of this function for each
182 %% possible state name. Whenever a gen_fsm receives an event sent using
183 %% gen_fsm:sync_send_event/2,3, the instance of this function with the same
184 %% name as the current state name StateName is called to handle the event.
185 %%--------------------------------------------------------------------
187 %%--------------------------------------------------------------------
188 %% Function:
189 %% handle_event(Event, StateName, State) -> {next_state, NextStateName,
190 %% NextState} |
191 %% {next_state, NextStateName,
192 %% NextState, Timeout} |
193 %% {stop, Reason, NewState}
194 %% Description: Whenever a gen_fsm receives an event sent using
195 %% gen_fsm:send_all_state_event/2, this function is called to handle
196 %% the event.
197 %%--------------------------------------------------------------------
198 handle_event(Msg, SN, S) ->
199 io:format("Problem: ~p~n", [Msg]),
200 {next_state, SN, S}.
202 %%--------------------------------------------------------------------
203 %% Function:
204 %% handle_sync_event(Event, From, StateName,
205 %% State) -> {next_state, NextStateName, NextState} |
206 %% {next_state, NextStateName, NextState,
207 %% Timeout} |
208 %% {reply, Reply, NextStateName, NextState}|
209 %% {reply, Reply, NextStateName, NextState,
210 %% Timeout} |
211 %% {stop, Reason, NewState} |
212 %% {stop, Reason, Reply, NewState}
213 %% Description: Whenever a gen_fsm receives an event sent using
214 %% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle
215 %% the event.
216 %%--------------------------------------------------------------------
217 handle_sync_event(_Event, _From, StateName, State) ->
218 Reply = ok,
219 {reply, Reply, StateName, State}.
221 %%--------------------------------------------------------------------
222 %% Function:
223 %% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}|
224 %% {next_state, NextStateName, NextState,
225 %% Timeout} |
226 %% {stop, Reason, NewState}
227 %% Description: This function is called by a gen_fsm when it receives any
228 %% other message than a synchronous or asynchronous event
229 %% (or a system message).
230 %%--------------------------------------------------------------------
231 handle_info(Info, StateName, State) ->
232 error_logger:info_report([unknown_info, Info, StateName]),
233 {next_state, StateName, State}.
235 %%--------------------------------------------------------------------
236 %% Function: terminate(Reason, StateName, State) -> void()
237 %% Description:This function is called by a gen_fsm when it is about
238 %% to terminate. It should be the opposite of Module:init/1 and do any
239 %% necessary cleaning up. When it returns, the gen_fsm terminates with
240 %% Reason. The return value is ignored.
241 %%--------------------------------------------------------------------
242 terminate(_Reason, _StateName, S) ->
243 ok = etorrent_piece_mgr:delete(S#state.id),
244 etorrent_torrent:delete(S#state.id),
246 etorrent_tracking_map:delete(S#state.id),
249 %%--------------------------------------------------------------------
250 %% Function:
251 %% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
252 %% Description: Convert process state when code is changed
253 %%--------------------------------------------------------------------
254 code_change(_OldVsn, StateName, State, _Extra) ->
255 {ok, StateName, State}.
257 %%--------------------------------------------------------------------
258 %%% Internal functions
259 %%--------------------------------------------------------------------
260 calculate_amount_left(Id) when is_integer(Id) ->
261 Pieces = etorrent_piece_mgr:select(Id),
262 lists:sum([size_piece(P) || P <- Pieces]).
264 size_piece(#piece{state = fetched}) -> 0;
265 size_piece(#piece{state = not_fetched, files = Files}) ->
266 lists:sum([Sz || {_F, _O, Sz} <- Files]).