diff --git a/apps/efka/src/efka_downloader.erl b/apps/efka/src/efka_downloader.erl index 7708a09..dad394a 100644 --- a/apps/efka/src/efka_downloader.erl +++ b/apps/efka/src/efka_downloader.erl @@ -12,15 +12,14 @@ -behaviour(gen_server). %% API --export([start_link/2, download/1]). +-export([start_link/0, download/3]). -export([test/0]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, { - url :: string(), - target_file :: string() + }). %%%=================================================================== @@ -28,26 +27,28 @@ %%%=================================================================== test() -> - {ok, Pid} = start_link("https://codeload.github.com/genadyo/LivePhotoDemo/zip/refs/heads/master", "/tmp/master.tar"), - Ref = download(Pid), + {ok, Pid} = start_link(), + Url = "https://codeload.github.com/genadyo/LivePhotoDemo/zip/refs/heads/master", + TargetDir = "/tmp/", + Ref = download(Pid, Url, TargetDir), receive {download_response, Ref, Info} -> lager:debug("info is: ~p", [Info]) end, ok. --spec download(Pid :: pid()) -> reference(). -download(Pid) when is_pid(Pid) -> +-spec download(Pid :: pid(), Url :: string(), TargetDir :: string()) -> reference(). +download(Pid, Url, TargetDir) when is_pid(Pid), is_list(Url), is_list(TargetDir) -> Ref = make_ref(), ReceiverPid = self(), - gen_server:cast(Pid, {download, ReceiverPid, Ref}), + gen_server:cast(Pid, {download, ReceiverPid, Ref, Url, TargetDir}), Ref. %% @doc Spawns the server and registers the local name (unique) --spec(start_link(Url :: string(), TargetFile :: string()) -> +-spec(start_link() -> {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). -start_link(Url, TargetFile) when is_list(Url), is_list(TargetFile) -> - gen_server:start_link(?MODULE, [Url, TargetFile], []). +start_link() -> + gen_server:start_link(?MODULE, [], []). %%%=================================================================== %%% gen_server callbacks @@ -58,8 +59,8 @@ start_link(Url, TargetFile) when is_list(Url), is_list(TargetFile) -> -spec(init(Args :: term()) -> {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | {stop, Reason :: term()} | ignore). -init([Url, TargetFile]) -> - {ok, #state{url = Url, target_file = TargetFile}}. +init([]) -> + {ok, #state{}}. %% @private %% @doc Handling call messages @@ -80,15 +81,16 @@ handle_call(_Request, _From, State = #state{}) -> {noreply, NewState :: #state{}} | {noreply, NewState :: #state{}, timeout() | hibernate} | {stop, Reason :: term(), NewState :: #state{}}). -handle_cast({download, ReceiverPid, Ref}, State = #state{url = Url, target_file = TargetFile}) -> +handle_cast({download, ReceiverPid, Ref, Url, TargetDir}, State = #state{}) -> SSLOpts = {ssl_options, [ % 完全禁用证书验证 {verify, verify_none} ]}, - case hackney:request(get, Url, [], <<>>, [async, {pool, false}, SSLOpts]) of + TargetFile = get_filename_from_url(Url), + case hackney:request(get, Url, [], <<>>, [async, {stream_to, self()}, {pool, false}, SSLOpts]) of {ok, ClientRef} -> - case receive_data(ClientRef, TargetFile) of + case receive_data(ClientRef, TargetDir, TargetFile) of ok -> ReceiverPid ! {download_response, Ref, ok}; {error, Reason} -> @@ -130,57 +132,56 @@ code_change(_OldVsn, State = #state{}, _Extra) -> %%% Internal functions %%%=================================================================== -receive_data(ClientRef, DefaultFile) -> +%% 读取请求 ResponseLine +receive_data(ClientRef, TargetDir, DefaultFile) when is_list(TargetDir), is_list(DefaultFile) -> receive {hackney_response, ClientRef, {status, 200, _Reason}} -> - lager:debug("22"), - receive_data0(ClientRef, DefaultFile); + receive_data0(ClientRef, TargetDir, DefaultFile); {hackney_response, ClientRef, {status, StatusCode, _Reason}} -> {error, {http_status, StatusCode}} end. -receive_data0(ClientRef, DefaultFile) -> +%% 处理头部信息, 解析可能的文件名 +receive_data0(ClientRef, TargetDir, DefaultFile) -> receive {hackney_response, ClientRef, {headers, Headers}} -> - lager:debug("11 data bytes: ~p", [extra_filename(Headers)]), - TargetFile = case extra_filename(Headers) of - error -> - DefaultFile; - {ok, Filename} -> - Filename - end, - {ok, File} = file:open(TargetFile, [write, binary]), + TargetFilename = extra_filename(Headers, DefaultFile), + FullFilename = TargetDir ++ TargetFilename, + {ok, File} = file:open(FullFilename, [write, binary]), receive_data1(ClientRef, File) end. +%% 接受文件数据 receive_data1(ClientRef, File) -> receive {hackney_response, ClientRef, {error, Reason}} -> - lager:debug("error11"), ok = file:close(File), {error, Reason}; {hackney_response, ClientRef, done} -> - lager:debug("error22"), ok = file:close(File), hackney:close(ClientRef), ok; {hackney_response, ClientRef, Data} -> - lager:debug("data: ~p", [byte_size(Data)]), file:write(File, Data), receive_data1(ClientRef, File) end. --spec extra_filename(Headers :: list()) -> error | {ok, Filename :: string()}. -extra_filename(Headers) when is_list(Headers) -> +-spec extra_filename(Headers :: list(), Default :: string()) -> Filename :: string(). +extra_filename(Headers, Default) when is_list(Headers), is_list(Default) -> case lists:filter(fun({K, _}) -> string:lowercase(K) =:= <<"content-disposition">> end, Headers) of [{_, <<"attachment; ", Rest/binary>>}|_] -> Params0 = binary:split(Rest, <<";">>, [global]), Params = lists:map(fun(P) -> list_to_tuple(binary:split(P, <<"=">>)) end, Params0), - filename(Params); + case proplists:get_value(<<"filename">>, Params) of + undefined -> + Default; + Filename -> + binary_to_list(Filename) + end; _ -> - error + Default end. -filename([]) -> - error; -filename([{<<"filename">>, Filename}|_]) -> - {ok, binary_to_list(Filename)}; -filename([_|Tail]) -> - filename(Tail). \ No newline at end of file + +-spec get_filename_from_url(Url :: string()) -> string(). +get_filename_from_url(Url) when is_list(Url) -> + URIMap = uri_string:parse(Url), + Path = maps:get(path, URIMap), + filename:basename(Path). \ No newline at end of file