~electric/shop-oregon-local

ref: 585941b0a562d8fc670478a9db83187bf0a1c793 shop-oregon-local/lib/shop_local/cache_provider.ex -rw-r--r-- 2.7 KiB
585941b0Micah D Add no results message / fix clothes tree links 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
defmodule ShopLocal.CacheProvider do
  use GenServer

  def start_link(opts) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  def add(pid, elements, search) do
    GenServer.cast(pid, {:add, elements, search})
  end

  def search(pid, s) do
    GenServer.call(pid, {:search, s})
  end

  defp search_map(cache, s_params) do
    Enum.filter_map(cache,
      fn ({ key, value }) ->
        Enum.all?(s_params, &(String.contains?(String.downcase(value.search), &1)))
      end,
      fn ({ key, value }) -> value end)
  end

  # If we miss x number of searches, stop asking for a while
  @no_search_threshold 1

  defp missed?(missed, s_params) do
    missed
    |> Enum.filter_map(
      fn ({ word, _count }) ->
        Enum.member?(s_params, word)
      end,
      fn ({ _word, count }) -> count end)
    |> Enum.any?(&(&1 > @no_search_threshold))
  end

  defp add_missed(missed, s_params) do
    s_params
    |> Enum.reduce(missed, fn(k, acc) ->
      new_count = if Map.has_key?(acc, k), do: acc[k] + 1, else: 1
      Map.put(acc, k, new_count)
    end)
  end

  # How long to keep cache records for
  @cache_for_seconds 18*60*60

  @impl true
  def init(:ok) do
    Process.send_after(self(), :clean, @cache_for_seconds*1000)
    {:ok, {%{}, %{}}}
  end

  @impl true
  def handle_cast({:add, entries, s}, {map, missed}) do
    s = URI.decode_www_form(String.downcase(s))
    if Enum.empty?(entries) do
        s_params = String.split(s, " ")
        missed = add_missed(missed, s_params)
        {:noreply, {map, missed}}
    else
        map = Enum.reduce(entries, map, fn(k, acc) -> 
          updated = k |> Map.put(:update_date, DateTime.now!("Etc/UTC"))
                      |> Map.put(:search, s)
          Map.put(acc, k.link, updated)
        end)
        {:noreply, {map, missed}}
    end
  end

  @impl true
  def handle_call({:search, s}, _from, {map, missed}) do
    s = String.downcase(s)
    s_params = String.split(URI.decode_www_form(s), " ")
    if missed?(missed, s_params) do
      {:reply, {:found, []}, {map, missed}}
    else
      found = search_map(map, s_params)
      if Enum.empty?(found) do
        {:reply, {:not_found}, {map, missed}}
      else
        {:reply, {:found, found}, {map, missed}}
      end
    end
  end

  @impl true
  def handle_info(:clean, {map, missed}) do
    keys = Enum.filter_map(map, fn ({_key, value}) ->
      now = DateTime.now!("Etc/UTC")
      DateTime.diff(now, value.update_date) < @cache_for_seconds
    end, fn ({key, _value}) -> key end)
    map = Map.drop(map, keys)

    missed = Enum.reduce(missed, missed, fn ({key, value}, acc) ->
      Map.put(acc, key, max(value - 1, 0))
    end)
    Process.send_after(self(), :clean, @cache_for_seconds*1000)
    {:noreply, {map, missed}}
  end
end