1 %%%-------------------------------------------------------------------
3 %%% Author : Jesper Louis Andersen <jlouis@succubus.local.domain>
4 %%% License : See COPYING
5 %%% Description : Representation of a torrent for downloading
7 %%% Created : 9 Jul 2007 by Jesper Louis Andersen
8 %%% <jlouis@succubus.local.domain>
9 %%%-------------------------------------------------------------------
10 -module(etorrent_t_control
).
14 -include("etorrent_piece.hrl").
17 -export([start_link
/3, start
/1, stop
/1,
18 torrent_checked
/2, tracker_error_report
/2, seed
/1,
19 tracker_warning_report
/2,
24 -export([init
/1, handle_event
/3, initializing
/2, started
/2,
25 stopped
/2, handle_sync_event
/4, handle_info
/3, terminate
/3,
28 -record(state
, {id
= none
,
35 file_system_pid
= none
,
38 available_peers
= []}).
40 -define(CHECK_WAIT_TIME
, 3000).
42 %%====================================================================
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
], []).
55 gen_fsm:send_event(Pid
, stop
).
58 gen_fsm:send_event(Pid
, start
).
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
}).
73 gen_fsm:send_event(Pid
, seed
).
75 %%====================================================================
77 %%====================================================================
78 %%--------------------------------------------------------------------
79 %% Function: init(Args) -> {ok, StateName, State} |
80 %% {ok, StateName, State, Timeout} |
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
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
,
93 parent_pid
= Parent
}, 0}. % Force timeout instantly.
95 %%--------------------------------------------------------------------
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
111 {next_state
, initializing
, S
, ?CHECK_WAIT_TIME
};
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(
122 %% Update the tracking map. This torrent has been started, and we
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(
132 {left
, calculate_amount_left(S#state
.id
)},
133 {total
, etorrent_metainfo:get_length(Torrent
)}},
138 etorrent_t_sup:add_tracker(
140 etorrent_metainfo:get_url(Torrent
),
141 etorrent_metainfo:get_infohash(Torrent
),
145 %% Since the process will now go to a hibernation state, GC it
146 etorrent_event_mgr:started_torrent(S#state
.id
),
148 {next_state
, started
,
149 S#state
{file_system_pid
= FSPid
,
150 tracker_pid
= TrackerPid
}}
156 started(check_torrent
, S
) ->
157 case etorrent_fs_checker:check_torrent(S#state
.file_system_pid
,
159 [] -> {next_state
, started
, S
};
161 error_logger:info_report([errornous_pieces
, {Errors
}]),
162 {next_state
, started
, S
}
164 started({tracker_error_report
, Reason
}, S
) ->
165 io:format("Got tracker error: ~s~n", [Reason
]),
166 {next_state
, started
, S
}.
171 %%--------------------------------------------------------------------
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 %%--------------------------------------------------------------------
189 %% handle_event(Event, StateName, State) -> {next_state, NextStateName,
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
197 %%--------------------------------------------------------------------
198 handle_event(Msg
, SN
, S
) ->
199 io:format("Problem: ~p~n", [Msg
]),
202 %%--------------------------------------------------------------------
204 %% handle_sync_event(Event, From, StateName,
205 %% State) -> {next_state, NextStateName, NextState} |
206 %% {next_state, NextStateName, NextState,
208 %% {reply, Reply, NextStateName, NextState}|
209 %% {reply, Reply, NextStateName, NextState,
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
216 %%--------------------------------------------------------------------
217 handle_sync_event(_Event
, _From
, StateName
, State
) ->
219 {reply
, Reply
, StateName
, State
}.
221 %%--------------------------------------------------------------------
223 %% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}|
224 %% {next_state, NextStateName, NextState,
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 %%--------------------------------------------------------------------
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
]).