From cff0eb6ae72e231a55d18200bff9244b3d6a2659 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Tue, 19 Jul 2016 19:47:15 -0400 Subject: add theme installer --- src/guff/cli.cr | 12 +++ src/guff/config.cr | 9 ++- src/guff/models/theme.cr | 140 +++++++++++++++++++++++++++++++++- src/guff/theme/installer.cr | 31 ++++++++ src/guff/theme/installer/file-info.cr | 2 + src/guff/theme/installer/manifest.cr | 2 +- 6 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 src/guff/theme/installer.cr (limited to 'src') diff --git a/src/guff/cli.cr b/src/guff/cli.cr index 3752621..680e418 100644 --- a/src/guff/cli.cr +++ b/src/guff/cli.cr @@ -129,6 +129,16 @@ module Guff::CLI ) end end + + class ThemeInstallAction < Action + def run + # run theme installer + Theme::Installer.run( + Context.new(@config), + @config.theme_zip_path.not_nil! + ) + end + end end def self.run(app : String, args : Array(String)) @@ -151,6 +161,8 @@ module Guff::CLI Actions::ThemeListAction.run(config) when "pack" Actions::ThemePackAction.run(config) + when "install" + Actions::ThemeInstallAction.run(config) else # never reached raise "unknown theme action: #{config.theme_action}" diff --git a/src/guff/config.cr b/src/guff/config.cr index 205c65d..94f47df 100644 --- a/src/guff/config.cr +++ b/src/guff/config.cr @@ -2,7 +2,8 @@ require "option_parser" class Guff::Config property :mode, :env, :host, :port, :data_dir, :system_dir, - :theme_action, :theme_src_dir, :theme_dst_path + :theme_action, :theme_src_dir, :theme_dst_path, + :theme_zip_path DEFAULTS = { mode: "help", @@ -17,6 +18,7 @@ class Guff::Config @theme_action : String @theme_src_dir : String? @theme_dst_path : String? + @theme_zip_path : String? def initialize @mode = DEFAULTS[:mode] as String @@ -30,7 +32,7 @@ class Guff::Config VALID_MODES = %w{init run help theme} - VALID_THEME_ACTIONS = %w{list pack} + VALID_THEME_ACTIONS = %w{list pack install} def mode=(mode : String) raise "unknown mode: \"#{mode}\"" unless VALID_MODES.includes?(mode) @@ -168,6 +170,9 @@ Examples: else r.theme_dst_path = File.basename(r.theme_src_dir.not_nil!) + ".zip" end + when "install" + raise "missing theme zip file" unless args.size > 0 + r.theme_zip_path = args.shift end when "help" # print help diff --git a/src/guff/models/theme.cr b/src/guff/models/theme.cr index 61debce..1e09727 100644 --- a/src/guff/models/theme.cr +++ b/src/guff/models/theme.cr @@ -49,6 +49,52 @@ class Guff::Models::ThemeModel < Guff::Models::Model WHERE a.theme_id = ? AND b.name = ? ", + + add_theme: " + INSERT INTO themes ( + theme_name, + theme_version, + theme_date, + theme_slug, + theme_hash, + is_system + ) VALUES (?, ?, ?, ?, ?, 0) + ", + + add_theme_data: " + INSERT INTO theme_data ( + theme_id, + type_id, + data_key, + data_val + ) VALUES ( + ?, + (SELECT type_id FROM theme_data_types WHERE name = ?), + ?, + ? + ) + ", + + add_theme_file: " + INSERT INTO theme_files ( + theme_id, + file_path, + file_size, + file_hash + ) VALUES (?, ?, ?, ?) + ", + + add_theme_asset: " + INSERT INTO theme_assets ( + file_id, + type_id, + sort_order + ) VALUES ( + (SELECT file_id FROM theme_files WHERE theme_id = ? AND file_path = ?), + (SELECT type_id FROM theme_asset_types WHERE name = ?), + ? + ) + ", } def initialize(context : Context) @@ -75,14 +121,37 @@ class Guff::Models::ThemeModel < Guff::Models::Model rows end - def get(theme_id : Int32) - @context.dbs.ro.row(SQL[:get], [theme_id.to_s]).not_nil! + def get( + theme_id : Int32? = nil, + slug : String? = nil, + ) + sql = [] of String + args = [] of String + + if !theme_id && !slug + raise " ThemeModel#get(): missing theme_id and slug arguments" + end + + if theme_id + # add theme_id filter + sql << "theme_id = ?" + args << theme_id.to_s + end + + if slug + # add slug filter + sql << "theme_slug = ?" + args << slug + end + + # exec query, return row + @context.dbs.ro.row(SQL[:get] % sql.join(" AND "), args).not_nil! end def templates(theme_id : Int32) unless @cache[:templates].includes?(theme_id) # get theme - row = get(theme_id) + row = get(theme_id: theme_id) # load templates @cache[:templates][theme_id] = (if row["is_system"] == "1" @@ -105,7 +174,7 @@ class Guff::Models::ThemeModel < Guff::Models::Model def metadata(theme_id : Int32) unless @cache[:metadata].includes?(theme_id) # get theme - row = get(theme_id) + row = get(theme_id: theme_id) # load templates @cache[:metadata][theme_id] = (if row["is_system"] == "1" @@ -139,4 +208,67 @@ class Guff::Models::ThemeModel < Guff::Models::Model # return results r end + + ############### + # add methods # + ############### + + REQUIRED_FIELDS = %w{name version date} + + def add( + manifest : Guff::Theme::Installer::Manifest, + zip_hash : String, + &done_cb : Proc(Int64, Void) \ + ) + # get writable db + db = @context.dbs.rw + + # check theme format + unless manifest.format == 1 + raise "Unsupported theme format: #{manifest.format}" + end + + db.transaction do + # generate slug + slug = SecureRandom.hex(24) + + # add theme + db.query(SQL[:add_theme], REQUIRED_FIELDS.map { |key| + manifest.metadata[key] + }.concat([slug, zip_hash])) + + # get theme_id (as string) + theme_id = db.last_insert_row_id.to_s + + # add metadata + manifest.metadata.each do |key, val| + unless REQUIRED_FIELDS.includes?(key) + db.query(SQL[:add_theme_data], [theme_id, "metadata", key, val]) + end + end + + # add templates + manifest.templates.each do |key, val| + db.query(SQL[:add_theme_data], [theme_id, "template", key, val]) + end + + # add files + manifest.files.each do |f| + db.query(SQL[:add_theme_file], [theme_id, f.name, f.size.to_s, f.hash]) + end + + # add style assets + manifest.assets.styles.each_with_index do |path, i| + db.query(SQL[:add_theme_asset], [theme_id, path, "style", i.to_s]) + end + + # add script assets + manifest.assets.scripts.each_with_index do |path, i| + db.query(SQL[:add_theme_asset], [theme_id, path, "script", i.to_s]) + end + + # pass id to callback + done_cb.call(theme_id.to_i64) + end + end end diff --git a/src/guff/theme/installer.cr b/src/guff/theme/installer.cr new file mode 100644 index 0000000..a546156 --- /dev/null +++ b/src/guff/theme/installer.cr @@ -0,0 +1,31 @@ +require "file_utils" + +class Guff::Theme::Installer + def self.run(context : Context, zip_path : String) + new(context).run(zip_path) + end + + def initialize(@context : Context) + end + + def run(zip_path : String) + # parse manifest + manifest = Manifest.load(zip_path) + + # add theme to db + @context.models.theme.add(manifest, hash_file(zip_path)) do |theme_id| + # copy theme to themes dir + FileUtils.cp(zip_path, File.join(themes_dir, theme_id.to_s)) + end + end + + private def themes_dir + File.join(@context.config.data_dir, "themes") + end + + private def hash_file(path : String) + d = OpenSSL::Digest.new("SHA1") + d.file(path) + d.hexdigest + end +end diff --git a/src/guff/theme/installer/file-info.cr b/src/guff/theme/installer/file-info.cr index dfbd9d8..51bd383 100644 --- a/src/guff/theme/installer/file-info.cr +++ b/src/guff/theme/installer/file-info.cr @@ -1,4 +1,6 @@ class Guff::Theme::Installer::FileInfo + getter :name, :size, :hash + JSON.mapping( name: String, size: Int64, diff --git a/src/guff/theme/installer/manifest.cr b/src/guff/theme/installer/manifest.cr index 5e7e07f..364b422 100644 --- a/src/guff/theme/installer/manifest.cr +++ b/src/guff/theme/installer/manifest.cr @@ -10,7 +10,7 @@ class Guff::Theme::Installer::Manifest def self.load(zip_path : String) : Manifest json = "" Zip::Archive.open(zip_path) do |zip| - json = zip.read("guff-manifest.json") + json = String.new(zip.read("guff-manifest.json")) end # return result -- cgit v1.2.3