Skip to content

Commit 2f6df23

Browse files
committed
document cache.term
1 parent 54d3861 commit 2f6df23

File tree

3 files changed

+119
-8
lines changed

3 files changed

+119
-8
lines changed

lib/scenic/cache/file.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ defmodule Scenic.Cache.File do
148148

149149
# --------------------------------------------------------
150150
@doc """
151-
Read a file into the memory.
151+
Read a file into memory.
152152
153-
The reason you would use this instead of File.read is to verify the file against
153+
The reason you would use this instead of File.read is to verify the data against
154154
a pre-computed hash.
155155
156156
Parameters:

lib/scenic/cache/term.ex

Lines changed: 117 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,109 @@
55

66
defmodule Scenic.Cache.Term do
77
@moduledoc """
8-
Simple functions to load a file, following the hashing rules.
9-
"""
8+
Helpers for loading file based Erlang terms directly into the cache.
9+
10+
Sometimes you want to pre-compile a big erlang term, such as a dictionary/map and
11+
distribute it to multiple applications. In this case you build your term, then use
12+
[`:erlang.term_to_binary/2`](http://erlang.org/doc/man/erlang.html#term_to_binary-2)
13+
to change it into binary data, which you write out to a file. Later you read the file
14+
and load the term.
15+
16+
This will be used in Scenic to store pre-compiled font metric data, such as character
17+
widths, kerning, etc. It is much better to compute that once and store it than to
18+
run the C code every time your program is run.
19+
20+
The `Scenic.Cache.Term` is very similar to [`Scenic.Cache.File`](Scenic.Cache.File.html)
21+
module, except that after the file has been loaded into memory, it also calls
22+
[`:erlang.binary_to_term/2`](http://erlang.org/doc/man/erlang.html#binary_to_term-2).
23+
24+
## Where to store your static file assets
25+
26+
You can store your assets anywhere in your app's `priv/` directory. This directory is
27+
special in the sense that the Elixir build system knows to copy its contents into the
28+
correct final build location. How you organize your assets inside of `priv/` is up to you.
29+
30+
my_app/
31+
priv/
32+
static/
33+
terms/
34+
asset.jpg
35+
36+
37+
At compile time you need to build the actual path of your asset by combining
38+
the build directory with the partial path inside of `priv/`
39+
40+
Example
41+
42+
path = :code.priv_dir(:my_app)
43+
|> Path.join("/static/terms/asset.jpg")
44+
45+
You can do this at either compile time or runtime.
46+
47+
## Security
48+
49+
A lesson learned the hard way is that static assets (fonts, images, etc) that your app
50+
loads out of storage can easily become attack vectors.
51+
52+
These formats are complicated! There is no guarantee (on any system) that a malformed
53+
asset will not cause an error in the C code that interprets it. Again - these are complicated
54+
and the renderers need to be fast...
55+
56+
The solution is to compute a SHA hash of these files during build-time of your
57+
and to store the result in your applications code itself. Then during run time, you
58+
compare then pre-computed hash against the run-time of the asset being loaded.
59+
60+
These scheme is much stronger when the application code itself is also signed and
61+
verified, but that is an exercise for the packaging tools.
62+
63+
When assets are loaded this way, the `@asset_hash` term is also used as the key in
64+
the cache. This has the additional benefit of allowing you to pre-compute
65+
the graph itself, using the correct keys for the correct assets.
66+
67+
Note that the hash is of the binary data in the file.
68+
69+
## Full example
1070
71+
defmodule MyApp.MyScene do
72+
use Scenic.Scene
73+
74+
# build the path to the static asset file (compile time)
75+
@asset_path :code.priv_dir(:my_app) |> Path.join("/static/terms/asset.jpg")
76+
77+
# pre-compute the hash (compile time)
78+
@asset_hash Scenic.Cache.Hash.file!( @asset_path, :sha )
79+
80+
def init( _, _ ) {
81+
# load the asset into the cache (run time)
82+
Scenic.Cache.File.load(@asset_path, @asset_hash)
83+
...
84+
end
85+
86+
end
87+
"""
1188
alias Scenic.Cache
1289
alias Scenic.Cache.Hash
1390

14-
# import IEx
15-
1691
# --------------------------------------------------------
92+
@doc """
93+
Load a file-based term directly into the cache.
94+
95+
Parameters:
96+
* `path` - the path to the term file
97+
* `hash` - the pre-computed hash of the file
98+
* `opts` - a list of options. See below.
99+
100+
Options:
101+
* `hash` - format of the hash. Valid formats include `:sha, :sha224, :sha256, :sha384, :sha512, :ripemd160`. If the hash option is not set, it will use `:sha` by default.
102+
* `scope` - Explicitly set the scope of the term in the cache.
103+
* `safe` - prevents the creation of new atoms. [See erlang docs](http://erlang.org/doc/man/erlang.html#binary_to_term-2).
104+
105+
On success, returns
106+
`{:ok, cache_key}`
107+
108+
The key in the cache will be the hash of the file.
109+
"""
110+
17111
def load(path, hash, opts \\ [])
18112

19113
# insecure loading. Loads file blindly even it is altered
@@ -56,6 +150,25 @@ defmodule Scenic.Cache.Term do
56150
end
57151

58152
# --------------------------------------------------------
153+
@doc """
154+
Read a file-based term into memory.
155+
156+
The reason you would use this instead of File.read is to verify the data against
157+
a pre-computed hash.
158+
159+
Parameters:
160+
* `path` - the path to the term file
161+
* `hash` - the pre-computed hash of the file
162+
* `opts` - a list of options. See below.
163+
164+
Options:
165+
* `hash` - format of the hash. Valid formats include `:sha, :sha224, :sha256, :sha384, :sha512, :ripemd160`. If the hash option is not set, it will use `:sha` by default.
166+
* `safe` - prevents the creation of new atoms. [See erlang docs](http://erlang.org/doc/man/erlang.html#binary_to_term-2).
167+
168+
On success, returns
169+
`{:ok, term}`
170+
"""
171+
59172
def read(path, hash, opts \\ [])
60173

61174
# insecure read

test/scenic/cache/file_test.exs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ defmodule Scenic.Cache.FileReadTest do
1212

1313
alias Scenic.Cache
1414

15-
# import IEx
16-
1715
@sample_path "test/artifacts/sample_file"
1816
@sample_gzip_path "test/artifacts/sample_file_gzip"
1917

0 commit comments

Comments
 (0)