discourse/lib/theme_store/zip_exporter.rb
David Taylor 1fd8f6df5f
PERF: Improve theme stylesheet compilation performance (#12850)
When building the `scss_load_paths`, we were creating a full export of the theme (including uploads), and not cleaning it up. With many uploads, this can be extremely slow (because it downloads every upload from S3), and the lack of cleanup could cause a disk to fill up over time.

This commit updates the ZipExporter to provide a `with_export_dir` API, which takes care of cleanup. It also adds a kwarg which allows exporting only extra_scss fields. This should make things much faster for themes with many uploads.
2021-04-27 14:33:43 +01:00

80 lines
2.2 KiB
Ruby

# frozen_string_literal: true
require_dependency 'compression/zip'
module ThemeStore; end
class ThemeStore::ZipExporter
def initialize(theme)
@theme = theme
@temp_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_#{SecureRandom.hex}"
@export_name = @theme.name.downcase.gsub(/[^0-9a-z.\-]/, '-')
@export_name = "discourse-#{@export_name}" unless @export_name.starts_with?("discourse")
end
def export_name
@export_name
end
def package_filename
export_package
end
def cleanup!
FileUtils.rm_rf(@temp_folder)
end
def with_export_dir(**kwargs)
export_to_folder(**kwargs)
yield File.join(@temp_folder, @export_name)
ensure
cleanup!
end
private
def export_to_folder(extra_scss_only: false)
destination_folder = File.join(@temp_folder, @export_name)
FileUtils.mkdir_p(destination_folder)
@theme.theme_fields.each do |field|
next if extra_scss_only && !field.extra_scss_field?
next unless path = field.file_path
# Belt and braces approach here. All the user input should already be
# sanitized, but check for attempts to leave the temp directory anyway
pathname = Pathname.new(File.join(destination_folder, path))
folder_path = pathname.parent.cleanpath
raise RuntimeError.new("Theme exporter tried to leave directory") unless folder_path.to_s.starts_with?(destination_folder)
pathname.parent.mkpath
path = pathname.realdirpath
raise RuntimeError.new("Theme exporter tried to leave directory") unless path.to_s.starts_with?(destination_folder)
if ThemeField.types[field.type_id] == :theme_upload_var
filename = Discourse.store.path_for(field.upload)
content = filename ? File.read(filename) : Discourse.store.download(field.upload).read
else
content = field.value
end
File.write(path, content)
end
if !extra_scss_only
File.write(
File.join(destination_folder, "about.json"),
JSON.pretty_generate(@theme.generate_metadata_hash)
)
end
@temp_folder
end
def export_package
export_to_folder
Compression::Zip.new.compress(@temp_folder, @export_name)
end
end