aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/cache.lua
blob: 0aeb01690450e36c2eb59d1bba71ce4206abd70f (plain)
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
-- Cache: ~/.cache total and its four largest subdirectories.
--
-- Reads a cache file written by bin/cache-sample.sh. du walks the tree and
-- costs about 100ms, which cannot happen on a 2-second draw.

local card = require 'lib.card'
local data = require 'lib.data'

local CACHE = (os.getenv('XDG_CACHE_HOME') or (os.getenv('HOME') .. '/.cache'))
  .. '/udt/cachesize.txt'

local M = {}

function M.draw(cr, rect, colors)
  local inner = card.card(cr, rect, colors)
  local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end
  -- Fluid type. One base size S drives every element. The height budget makes
  -- the content fill the cell; the measured width fit pulls S down only where
  -- the header or a row would overrun the card. A tall cell gets larger type
  -- instead of empty space, and nothing collides.
  local LABEL_F, ROW_F = 0.30, 0.32

  local c = data.du_parse(data.slurp(CACHE) or '')
  if not c then
    local S = clamp(inner.h * 0.94 / (1.78 + 2 * 0.72), 10, 72)
    S = math.min(S, card.fit_unit(cr, inner.w * 0.96, {
      { { 'CACHE', card.FONT_MONO, LABEL_F } },
      { { 'no cache data', card.FONT_UI, 0.36 } },
      { { 'bin/cache-sample.sh', card.FONT_MONO, ROW_F } },
    }, 100))
    card.font(cr, card.FONT_MONO, S * LABEL_F, false)
    card.rgba(cr, colors.label)
    card.text(cr, inner.x, inner.y + S * LABEL_F, 'CACHE')
    card.font(cr, card.FONT_UI, S * 0.36, true)
    card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6, 'no cache data')
    card.font(cr, card.FONT_MONO, S * ROW_F, false)
    card.text(cr, inner.x, inner.y + S + S * LABEL_F * 2.6 + S * 0.72,
      'bin/cache-sample.sh')
    return
  end

  local items = c.items or {}
  local names = {}
  for i, e in ipairs(items) do
    local name = e.name
    if #name > 18 then name = name:sub(1, 17) .. '\u{2026}' end
    names[i] = name
  end

  local row_n = #items
  local S_h = inner.h * 0.94 / (1.78 + row_n * 0.64)
  local groups = {
    { { 'CACHE', card.FONT_MONO, LABEL_F }, { c.total.label, card.FONT_HEAVY, 1.0 } },
  }
  for i = 1, row_n do
    groups[#groups + 1] = { { names[i], card.FONT_MONO, ROW_F },
                            { items[i].label, card.FONT_MONO, ROW_F } }
  end
  local S = clamp(math.min(S_h, card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72)
  local label_size = S * LABEL_F
  local row_size   = S * ROW_F

  card.font(cr, card.FONT_MONO, label_size, false)
  card.rgba(cr, colors.label)
  card.text(cr, inner.x, inner.y + label_size, 'CACHE')

  -- The total shares the header line with the label, set on the same baseline
  -- and right-aligned: the width carries the number instead of an otherwise
  -- empty row beneath it.
  card.font(cr, card.FONT_HEAVY, S, false)
  card.rgba(cr, colors.body)
  card.text_right(cr, inner.x + inner.w, inner.y + S, c.total.label)

  local ey = inner.y + S + label_size * 2.6
  local step = row_size * 2.0

  card.font(cr, card.FONT_MONO, row_size, false)
  for i, e in ipairs(items) do
    if ey + step > inner.y + inner.h then break end
    -- The biggest offender is the point of the card, so it carries colour:
    -- critical when it is over half the total, warning otherwise. The rest
    -- stay in the ordinary value colour so the eye lands on the one to delete.
    local colour = colors.value
    if i == 1 then
      local share = c.total.bytes > 0 and (e.bytes / c.total.bytes) or 0
      colour = share > 0.5 and colors.critical or colors.warning
    end
    card.rgba(cr, colors.label)
    card.text(cr, inner.x, ey, names[i])
    card.rgba(cr, colour)
    card.text_right(cr, inner.x + inner.w, ey, e.label)
    ey = ey + step
  end
end

return M