Skip to content

Commit 6f8f71b

Browse files
committed
Add genserver to process the keyword
1 parent 8e91089 commit 6f8f71b

7 files changed

Lines changed: 101 additions & 17 deletions

File tree

lib/google_crawler/application.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ defmodule GoogleCrawler.Application do
1313
# Start the endpoint when the application starts
1414
GoogleCrawlerWeb.Endpoint,
1515
# The supervisor for crawler background job
16-
{Task.Supervisor, name: GoogleCrawler.TaskSupervisor}
16+
{Task.Supervisor, name: GoogleCrawler.TaskSupervisor},
17+
GoogleCrawler.FetchKeywordWorker
1718
]
1819

1920
# See https://hexdocs.pm/elixir/Supervisor.html

lib/google_crawler/search.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ defmodule GoogleCrawler.Search do
7272
def create_and_search_keyword(attrs \\ %{}, user) do
7373
case create_keyword(attrs, user) do
7474
{:ok, %Keyword{} = keyword} ->
75-
GoogleCrawler.Task.perform(GoogleCrawler.Search.SearchKeywordTask, keyword)
75+
GoogleCrawler.FetchKeywordWorker.fetch_and_scrap(keyword.id)
7676
{:ok, %Keyword{}}
7777

7878
{:error, %Ecto.Changeset{} = changeset} ->
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
defmodule GoogleCrawler.FetchKeywordWorker do
2+
use GenServer
3+
4+
alias GoogleCrawler.Search
5+
alias GoogleCrawler.Search.Keyword
6+
7+
@max_retry_count 3
8+
9+
# Client
10+
11+
def start_link(_args) do
12+
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
13+
end
14+
15+
def fetch_and_scrap(keyword_id) do
16+
GenServer.call(__MODULE__, {:fetch_and_scrap, keyword_id})
17+
end
18+
19+
# Server Callbacks
20+
21+
def init(state) do
22+
{:ok, state}
23+
end
24+
25+
def handle_call({:fetch_and_scrap, keyword_id}, _from, state) do
26+
IO.puts "Handle call #{keyword_id}"
27+
28+
keyword = Search.get_keyword(keyword_id)
29+
Search.update_keyword(keyword, %{status: :in_progress})
30+
31+
# start task and store the state of the task with the retry count
32+
# as a tuple of {keyword, retry_count} -> {%Keyword{}, 1}
33+
task = start_task(keyword)
34+
new_state = Map.put(state, task.ref, {keyword, 0})
35+
36+
{:reply, :ok, new_state}
37+
end
38+
39+
def handle_info({ref, result}, state) do
40+
IO.puts "Handle info | success"
41+
42+
{keyword, _retry_count} = Map.get(state, ref)
43+
Search.update_keyword(keyword, %{
44+
status: :completed,
45+
raw_html_result: result.raw_html_result
46+
})
47+
48+
# Demonitor the task and remove from the state
49+
Process.demonitor(ref, [:flush])
50+
new_state = Map.delete(state, ref)
51+
52+
{:noreply, new_state}
53+
end
54+
55+
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
56+
IO.puts "Handle info | failed"
57+
58+
{keyword, retry_count} = Map.get(state, ref)
59+
new_state = if retry_count < @max_retry_count do
60+
IO.puts "Retry... #{retry_count}"
61+
task = start_task(keyword)
62+
63+
state
64+
|> Map.delete(ref)
65+
|> Map.put(task.ref, {keyword, retry_count + 1})
66+
else
67+
IO.puts "Done with failed..."
68+
Search.update_keyword(keyword, %{status: :failed})
69+
70+
Map.delete(state, ref)
71+
end
72+
73+
{:noreply, new_state}
74+
end
75+
76+
defp start_task(%Keyword{} = keyword) do
77+
Task.Supervisor.async_nolink(GoogleCrawler.TaskSupervisor, fn ->
78+
GoogleCrawler.Search.SearchKeywordTask.perform(keyword)
79+
end)
80+
end
81+
end

lib/google_crawler/search/keyword.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import EctoEnum
22

3-
defenum(GoogleCrawler.Search.Keyword.Status, in_queue: 0, in_progress: 1, completed: 2, failed: 3)
3+
defenum(GoogleCrawler.Search.Keyword.Status, in_queue: 0, in_progress: 1, failed: 2, completed: 3)
44

55
defmodule GoogleCrawler.Search.Keyword do
66
use Ecto.Schema

lib/google_crawler/search/page_fetcher.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ defmodule GoogleCrawler.Search.PageFetcher do
22
@url "https://www.google.com/search?q="
33

44
def fetch(keyword) do
5-
IO.puts "Performing search ... #{@url <> URI.encode(keyword)}"
65
case HTTPoison.get(@url <> URI.encode(keyword)) do
76
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
87
{:ok, body}

lib/google_crawler/search/search_keyword_task.ex

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,14 @@
11
defmodule GoogleCrawler.Search.SearchKeywordTask do
2-
alias GoogleCrawler.Search
32
alias GoogleCrawler.Search.Keyword
43
alias GoogleCrawler.Search.PageFetcher
54
alias GoogleCrawler.Search.PageScrapper
65

76
def perform(%Keyword{} = keyword) do
8-
Search.update_keyword(keyword, %{status: :in_progress})
9-
107
case PageFetcher.fetch(keyword.keyword) do
118
{:ok, body} ->
12-
result = PageScrapper.scrap(body)
13-
Search.update_keyword(keyword, %{
14-
status: :completed,
15-
raw_html_result: result.raw_html_result
16-
})
9+
PageScrapper.scrap(body)
1710

1811
{:error, reason} ->
19-
Search.update_keyword(keyword, %{ status: :failed })
2012
raise "Keyword search failed: #{reason}"
2113
end
2214
end

test/google_crawler/search_test.exs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ defmodule GoogleCrawler.SearchTest do
3030

3131
assert {:ok, %Keyword{} = keyword} = Search.create_keyword(keyword_attrs, user)
3232
assert keyword.keyword == "elixir"
33-
assert keyword.status == :in_queue
33+
assert keyword.status == :created
3434
assert keyword.user_id == user.id
3535
end
3636

@@ -45,9 +45,20 @@ defmodule GoogleCrawler.SearchTest do
4545
keyword = KeywordFactory.create()
4646
keyword_attrs = %{keyword: "new", status: :in_progress}
4747

48-
assert {:ok, %Keyword{} = keyword} = Search.update_keyword(keyword, keyword_attrs)
49-
assert keyword.keyword == "new"
50-
assert keyword.status == :in_progress
48+
# assert {:ok, %Keyword{} = keyword} = Search.update_keyword(keyword, keyword_attrs)
49+
# assert keyword.keyword == "new"
50+
# assert keyword.status == :in_progress
51+
52+
Search.update_keyword(keyword, %{status: :in_progress})
53+
Search.update_keyword(keyword, %{status: :failed})
54+
Search.update_keyword(keyword, %{status: :in_progress})
55+
Search.update_keyword(keyword, %{status: :failed})
56+
Search.update_keyword(keyword, %{status: :in_progress})
57+
Search.update_keyword(keyword, %{status: :failed})
58+
Search.update_keyword(keyword, %{status: :in_progress})
59+
Search.update_keyword(keyword, %{status: :completed})
60+
61+
IO.inspect Search.get_keyword(keyword.id)
5162
end
5263

5364
test "update_keyword/2 with invalid data returns error changeset" do

0 commit comments

Comments
 (0)