Class: Opal::Cache::FileCache

Inherits:
Object
  • Object
show all
Defined in:
opal/lib/opal/cache/file_cache.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dir: nil, max_size: nil) ⇒ FileCache

Returns a new instance of FileCache.



9
10
11
12
13
14
15
16
17
18
# File 'opal/lib/opal/cache/file_cache.rb', line 9

def initialize(dir: nil, max_size: nil)
  @dir = dir || self.class.find_dir
  # Store at most 32MB of cache - de facto this 32MB is larger,
  # as we don't account for inode size for instance. In fact, it's
  # about 50M. Also we run this check before anything runs, so things
  # may go up to 64M or even larger.
  @max_size = max_size || 32 * 1024 * 1024

  tidy_up_cache
end

Class Method Details

.dir_writable?(*paths) ⇒ Boolean

This complex piece of code tries to check if we can robustly mkdir_p a directory.

Returns:

  • (Boolean)


82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'opal/lib/opal/cache/file_cache.rb', line 82

def self.dir_writable?(*paths)
  dir = nil
  paths = paths.reduce([]) do |a, b|
    [*a, dir = a.last ? File.expand_path(b, a.last) : b]
  end

  File.exist?(paths.first) &&
    paths.reverse.all? do |i|
      !File.exist?(i) || (File.directory?(i) && File.writable?(i))
    end

  dir
end

.find_dirObject



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'opal/lib/opal/cache/file_cache.rb', line 96

def self.find_dir
  @find_dir ||= case
                # Try to write cache into a directory pointed by an environment variable if present
                when dir = ENV['OPAL_CACHE_DIR']
                  FileUtils.mkdir_p(dir)
                  dir
                # Otherwise, we write to the place where Opal is installed...
                # I don't think it's a good location to store cache, so many things can go wrong.
                # when dir = dir_writable?(Opal.gem_dir, '..', 'tmp', 'cache')
                #   FileUtils.mkdir_p(dir)
                #   FileUtils.chmod(0o700, dir)
                #   dir
                # Otherwise, ~/.cache/opal...
                when dir = dir_writable?(Dir.home, '.cache', 'opal')
                  FileUtils.mkdir_p(dir)
                  FileUtils.chmod(0o700, dir)
                  dir
                # Only /tmp is writable... or isn't it?
                when (dir = dir_writable?('/tmp', "opal-cache-#{ENV['USER']}")) && File.sticky?('/tmp')
                  FileUtils.mkdir_p(dir)
                  FileUtils.chmod(0o700, dir)
                  dir
                # No way... we can't write anywhere...
                else
                  warn "Couldn't find a writable path to store Opal cache. " \
                       'Try setting OPAL_CACHE_DIR environment variable'
                  nil
                end
end

Instance Method Details

#get(key) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
# File 'opal/lib/opal/cache/file_cache.rb', line 39

def get(key)
  file = cache_filename_for(key)

  if File.exist?(file)
    FileUtils.touch(file)
    out = File.binread(file)
    out = Zlib.gunzip(out)
    Marshal.load(out) # rubocop:disable Security/MarshalLoad
  end
rescue Zlib::GzipFile::Error
  nil
end

#set(key, data) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'opal/lib/opal/cache/file_cache.rb', line 20

def set(key, data)
  file = cache_filename_for(key)
  out = Marshal.dump(data)

  # Sometimes `Zlib::BufError` gets raised, unsure why, makes no sense, possibly
  # some race condition (see https://github.com/ruby/zlib/issues/49).
  # Limit the number of retries to avoid infinite loops.
  retries = 5
  begin
    out = Zlib.gzip(out, level: 9)
  rescue Zlib::BufError
    warn "\n[Opal]: Zlib::BufError; retrying (#{retries} retries left)"
    retries -= 1
    retry if retries > 0
  end

  File.binwrite(file, out)
end