card-mod-carrierwave 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ require "mini_magick"
2
+
3
+ module CarrierWave
4
+ # Adds image specific version handling to {FileCardUploader}.
5
+ # The ImageCardUploader creates five versions of different sizes when it
6
+ # uploads an imagae file:
7
+ # icon (16x16), small (75x75), medium (200X200), large (500x500) and
8
+ # the original size.
9
+ class ImageCardUploader < FileCardUploader
10
+ include CarrierWave::MiniMagick
11
+
12
+ def path version=nil
13
+ (version && version != :original) ? versions[version].path : super()
14
+ end
15
+
16
+ version :icon, if: :create_versions?, from_version: :small do
17
+ process resize_and_pad: [16, 16]
18
+ end
19
+ version :small, if: :create_versions?, from_version: :medium do
20
+ process resize_to_fit: [75, 75]
21
+ end
22
+ version :medium, if: :create_versions? do
23
+ process resize_to_limit: [200, 200]
24
+ end
25
+ version :large, if: :create_versions? do
26
+ process resize_to_limit: [500, 500]
27
+ end
28
+
29
+ # version :small_square, if: :create_versions?,
30
+ # from_version: :medium_square do
31
+ # process resize_to_fill: [75, 75]
32
+ # end
33
+ # version :medium_square, if: :create_versions? do
34
+ # process resize_to_fill: [200, 200]
35
+ # end
36
+ #
37
+ # In case we decide to support the squared versions
38
+ # we have to update all existing images with the following snippet:
39
+ # Card.search(type_id: Card::ImageID) do |card|
40
+ # card.image.cache_stored_file!
41
+ # card.image.recreate_versions!
42
+ # end
43
+
44
+ def identifier
45
+ full_filename(super())
46
+ end
47
+
48
+ # add 'original' if no version is given
49
+ def full_filename for_file
50
+ name = super(for_file)
51
+ if version_name
52
+ name
53
+ else
54
+ parts = name.split "."
55
+ "#{parts.shift}-original.#{parts.join('.')}"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,112 @@
1
+ attr_writer :empty_ok
2
+
3
+ def self.included host_class
4
+ host_class.extend CarrierWave::CardMount
5
+ end
6
+
7
+ event :select_file_revision, after: :select_action do
8
+ attachment.retrieve_from_store!(attachment.identifier)
9
+ end
10
+
11
+ # we need a card id for the path so we have to update db_content when we have
12
+ # an id
13
+ event :correct_identifier, :finalize, on: :create, when: proc { |c| !c.web? } do
14
+ update_column(:db_content, attachment.db_content)
15
+ expire
16
+ end
17
+
18
+ event :save_original_filename, :prepare_to_store, on: :save, when: :file_ready_to_save? do
19
+ return unless @current_action
20
+ @current_action.update! comment: original_filename
21
+ end
22
+
23
+ event :validate_file_exist, :validate, on: :create do
24
+ return if empty_ok?
25
+ if will_be_stored_as == :web
26
+ errors.add "url is missing" if content.blank?
27
+ elsif !attachment.file.present?
28
+ errors.add attachment_name, "is missing"
29
+ end
30
+ end
31
+
32
+ event :write_identifier, after: :save_original_filename, when: proc { |c| !c.web? } do
33
+ self.content = attachment.db_content
34
+ end
35
+
36
+ def file_ready_to_save?
37
+ attachment.file.present? &&
38
+ !preliminary_upload? &&
39
+ !save_preliminary_upload? &&
40
+ attachment_is_changing?
41
+ end
42
+
43
+ def item_names _args={} # needed for flexmail attachments. hacky.
44
+ [name]
45
+ end
46
+
47
+ def original_filename
48
+ return content.split("/").last if web?
49
+ attachment.original_filename
50
+ end
51
+
52
+ def unfilled?
53
+ !attachment.present? && !save_preliminary_upload? && !subcards? && blank_content?
54
+ end
55
+
56
+ def attachment_changed?
57
+ send "#{attachment_name}_changed?"
58
+ end
59
+
60
+ def attachment_is_changing?
61
+ send "#{attachment_name}_is_changing?"
62
+ end
63
+
64
+ def attachment_before_act
65
+ send "#{attachment_name}_before_act"
66
+ end
67
+
68
+ def create_versions? _new_file
69
+ true
70
+ end
71
+
72
+ def empty_ok?
73
+ @empty_ok
74
+ end
75
+
76
+ def assign_set_specific_attributes
77
+ # reset content if we really have something to upload
78
+ self.content = nil if set_specific[attachment_name.to_s].present?
79
+ super
80
+ end
81
+
82
+ def delete_files_for_action action
83
+ with_selected_action_id(action.id) do
84
+ attachment.file.delete
85
+ attachment.versions.each_value do |version|
86
+ version.file.delete
87
+ end
88
+ end
89
+ end
90
+
91
+ def revision action, before_action=false
92
+ return unless (result = super)
93
+ result[:empty_ok] = true
94
+ result
95
+ end
96
+
97
+ def attachment_format ext
98
+ if ext.present? && attachment && (original_ext = attachment.extension.sub(/^\./, ""))
99
+ if ["file", original_ext].member? ext
100
+ original_ext
101
+ elsif (exts = Mime::Types[attachment.content_type])
102
+ if exts.find { |mt| mt.extensions.member? ext }
103
+ ext
104
+ else
105
+ exts[0].extensions[0]
106
+ end
107
+ end
108
+ end
109
+ rescue => e
110
+ Rails.logger.info "attachment_format issue: #{e.message}"
111
+ nil
112
+ end
@@ -0,0 +1,133 @@
1
+ event :change_bucket_if_read_only, :initialize,
2
+ on: :update, when: :change_bucket_if_read_only? do
3
+ @new_storage_type = storage_type_from_config
4
+ end
5
+
6
+ event :validate_storage_type_update, :validate, on: :update, when: :cloud? do
7
+ # FIXME: make it possible to retrieve the file from cloud storage
8
+ # to store it somewhere else. Currently, it only works to change the
9
+ # storage type if a new file is provided
10
+ # i.e. `update storage_type: :local` fails but
11
+ # `update storage_type: :local, file: [file handle]` is ok
12
+ return unless storage_type_changed? && !attachment_is_changing?
13
+
14
+ errors.add :storage_type, tr(:moving_files_is_not_supported)
15
+ end
16
+
17
+ def bucket
18
+ @bucket ||= cloud? && (new_card_bucket || bucket_from_content || bucket_from_config)
19
+ end
20
+
21
+ def new_card_bucket
22
+ return unless new_card?
23
+ # If the file is assigned before the bucket option we have to
24
+ # check if there is a bucket options in set_specific.
25
+ # That happens for exmaple when the file appears before the bucket in the
26
+ # options hash:
27
+ # Card.create file: file_handle, bucket: "my_bucket"
28
+ set_specific[:bucket] || set_specific["bucket"] || bucket_from_config
29
+ end
30
+
31
+ def bucket_config
32
+ @bucket_config ||= load_bucket_config
33
+ end
34
+
35
+ def load_bucket_config
36
+ return {} unless bucket
37
+ bucket_config = Cardio.config.file_buckets&.dig(bucket.to_sym) || {}
38
+ bucket_config.symbolize_keys!
39
+ bucket_config[:credentials]&.symbolize_keys!
40
+ # we don't want :attributes hash symbolized, so we can't use
41
+ # deep_symbolize_keys
42
+ ensure_bucket_config do
43
+ load_bucket_config_from_env bucket_config
44
+ end
45
+ end
46
+
47
+ def ensure_bucket_config
48
+ yield.tap do |config|
49
+ require_configuration! config
50
+ require_credentials! config
51
+ end
52
+ end
53
+
54
+ def require_configuration! config
55
+ cant_find_in_bucket! "configuration" unless config.present?
56
+ end
57
+
58
+ def require_credentials! config
59
+ cant_find_in_bucket! "credentials" unless config[:credentials]
60
+ end
61
+
62
+ def cant_find_in_bucket! need
63
+ raise Card::Error, "couldn't find #{need} for bucket #{bucket}"
64
+ end
65
+
66
+ def load_bucket_config_from_env config
67
+ config ||= {}
68
+ each_config_option_from_env do |key|
69
+ replace_with_env_variable config, key
70
+ end
71
+ credential_config config do |cred_hash|
72
+ load_bucket_credentials_from_env cred_hash
73
+ end
74
+ end
75
+
76
+ def credential_config config
77
+ config[:credentials] ||= {}
78
+ yield config[:credentials]
79
+ config.delete :credentials if config[:credentials].blank?
80
+ config
81
+ end
82
+
83
+ def each_config_option_from_env
84
+ CarrierWave::FileCardUploader::CONFIG_OPTIONS.each do |key|
85
+ yield key unless key.in? %i[attributes credentials]
86
+ end
87
+ end
88
+
89
+ def load_bucket_credentials_from_env cred_config
90
+ each_credential_from_env do |option|
91
+ replace_with_env_variable cred_config, option, "credentials"
92
+ end
93
+ end
94
+
95
+ def each_credential_from_env
96
+ regexp = credential_from_env_regexp
97
+ ENV.each_key do |env_key|
98
+ next unless (m = regexp.match env_key)
99
+ yield m[:option].downcase.to_sym
100
+ end
101
+ end
102
+
103
+ def credential_from_env_regexp
104
+ Regexp.new "^(?:#{bucket.to_s.upcase}_)?CREDENTIALS_(?<option>.+)$"
105
+ end
106
+
107
+ def replace_with_env_variable config, option, prefix=nil
108
+ env_key = [prefix, option].compact.join("_").upcase
109
+ new_value = ENV["#{bucket.to_s.upcase}_#{env_key}"] || ENV[env_key]
110
+ config[option] = new_value if new_value
111
+ end
112
+
113
+ def bucket_from_content
114
+ return unless content
115
+ content.match(/^\((?<bucket>[^)]+)\)/) { |m| m[:bucket] }
116
+ end
117
+
118
+ def bucket_from_config
119
+ cnf = Cardio.config
120
+ cnf.file_default_bucket || cnf.file_buckets&.keys&.first
121
+ end
122
+
123
+ def change_bucket_if_read_only?
124
+ cloud? && bucket_config[:read_only] && attachment_is_changing?
125
+ end
126
+
127
+ def bucket= value
128
+ if @action == :update
129
+ @new_bucket = value
130
+ else
131
+ @bucket = value
132
+ end
133
+ end
@@ -0,0 +1,22 @@
1
+ event :lose_coded_status_on_update, :initialize, on: :update, when: :coded? do
2
+ # unless explicit
3
+ return if @new_mod
4
+ @new_storage_type ||= storage_type_from_config
5
+ end
6
+
7
+ event :validate_coded_storage_type, :validate, on: :save, when: :will_become_coded? do
8
+ errors.add :storage_type, tr(:mod_argument_needed_to_save) unless mod || @new_mod
9
+ errors.add :storage_type, tr(:codename_needed_for_storage) if codename.blank?
10
+ end
11
+
12
+ def will_become_coded?
13
+ will_be_stored_as == :coded
14
+ end
15
+
16
+ def mod= value
17
+ if @action == :update && mod != value
18
+ @new_mod = value.to_s
19
+ else
20
+ @mod = value.to_s
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+
2
+ event :update_public_link_on_create, :integrate, on: :create, when: :local? do
3
+ update_public_link
4
+ end
5
+
6
+ event :remove_public_link_on_delete, :integrate, on: :delete, when: :local? do
7
+ remove_public_links
8
+ end
9
+
10
+ event :update_public_link, after: :update_read_rule, when: :local? do
11
+ return if content.blank?
12
+ if who_can(:read).include? Card::AnyoneID
13
+ create_public_links
14
+ else
15
+ remove_public_links
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def create_public_links
22
+ path = attachment.public_path
23
+ return if File.exist? path
24
+ FileUtils.mkdir_p File.dirname(path)
25
+ File.symlink attachment.path, path unless File.symlink? path
26
+ create_versions_public_links
27
+ end
28
+
29
+ def create_versions_public_links
30
+ attachment.versions.each_value do |version|
31
+ next if File.symlink? version.public_path
32
+ File.symlink version.path, version.public_path
33
+ end
34
+ end
35
+
36
+ def remove_public_links
37
+ symlink_dir = File.dirname attachment.public_path
38
+ return unless Dir.exist? symlink_dir
39
+ FileUtils.rm_rf symlink_dir
40
+ end
@@ -0,0 +1,58 @@
1
+ MOD_FILE_DIR = "file".freeze
2
+
3
+ def store_dir
4
+ will_become_coded? ? coded_dir(@new_mod) : upload_dir
5
+ end
6
+
7
+ def retrieve_dir
8
+ coded? ? coded_dir : upload_dir
9
+ end
10
+
11
+ # place for files of regular file cards
12
+ def upload_dir
13
+ id ? "#{files_base_dir}/#{id}" : tmp_upload_dir
14
+ end
15
+
16
+ # place for files of mod file cards
17
+ def coded_dir new_mod=nil
18
+ dir = File.join mod_dir(new_mod), MOD_FILE_DIR, codename.to_s
19
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
20
+ dir
21
+ end
22
+
23
+ def mod_dir new_mod=nil
24
+ mod_name = new_mod || mod
25
+ dir = Cardio::Mod.dirs.path(mod_name) || (mod_name.to_sym == :test && "test")
26
+
27
+ raise Error, "can't find mod \"#{mod_name}\"" unless dir
28
+ dir
29
+ end
30
+
31
+ def files_base_dir
32
+ dir = bucket ? bucket_config[:subdirectory] : Card.paths["files"].existent.first
33
+ dir || files_base_dir_configuration_error
34
+ end
35
+
36
+ def files_base_dir_configuration_error
37
+ raise StandardError,
38
+ "missing directory for file cache (default is `files` in deck root)"
39
+ end
40
+
41
+ # used in the indentifier
42
+ def file_dir
43
+ if coded?
44
+ ":#{codename}"
45
+ elsif cloud?
46
+ "(#{bucket})/#{file_id}"
47
+ else
48
+ "~#{file_id}"
49
+ end
50
+ end
51
+
52
+ def public?
53
+ who_can(:read).include? Card::AnyoneID
54
+ end
55
+
56
+ def file_id
57
+ id? ? id : upload_cache_card.id
58
+ end
@@ -0,0 +1,170 @@
1
+ attr_writer :bucket, :storage_type
2
+
3
+ event :storage_type_change, :store, on: :update, when: :storage_type_changed? do
4
+ # carrierwave stores file if @cache_id is not nil
5
+ attachment.cache_stored_file!
6
+ # attachment.retrieve_from_cache!(attachment.cache_name)
7
+ update_storage_attributes
8
+ # next line might be necessary to move files to cloud
9
+
10
+ # make sure that we get the new identifier
11
+ # otherwise action_id will return wrong id for new identifier
12
+ db_content_will_change!
13
+ write_identifier
14
+ end
15
+
16
+ event :validate_storage_type, :validate, on: :save do
17
+ unless known_storage_type? will_be_stored_as
18
+ errors.add :storage_type, tr(
19
+ :unknown_storage_type,
20
+ new_storage_type: @new_storage_type
21
+ )
22
+ end
23
+ end
24
+
25
+ def will_be_stored_as
26
+ @new_storage_type || storage_type
27
+ end
28
+
29
+ def read_only?
30
+ web? || (cloud? && bucket_config[:read_only])
31
+ end
32
+
33
+ def cloud?
34
+ storage_type == :cloud
35
+ end
36
+
37
+ def web?
38
+ storage_type == :web
39
+ end
40
+
41
+ def local?
42
+ storage_type == :local
43
+ end
44
+
45
+ def coded?
46
+ storage_type == :coded
47
+ end
48
+
49
+ def remote_storage?
50
+ cloud? || web?
51
+ end
52
+
53
+ def storage_type
54
+ @storage_type ||=
55
+ new_card? ? storage_type_from_config : storage_type_from_content
56
+ end
57
+
58
+ def deprecated_mod_file?
59
+ content && (lines = content.split("\n")) && lines.size == 4
60
+ end
61
+
62
+ def mod
63
+ @mod ||= coded? && mod_from_content
64
+ end
65
+
66
+ def mod_from_content
67
+ if content =~ %r{^:[^/]+/([^.]+)}
68
+ Regexp.last_match(1) # current mod_file format
69
+ else
70
+ mod_from_deprecated_content
71
+ end
72
+ end
73
+
74
+ # old format is still used in card_changes
75
+ def mod_from_deprecated_content
76
+ return if content =~ /^\~/
77
+ return unless (lines = content.split("\n")) && lines.size == 4
78
+ lines.last
79
+ end
80
+
81
+ def storage_type_from_config
82
+ valid_storage_type ENV["FILE_STORAGE"] || Cardio.config.file_storage
83
+ end
84
+
85
+ def valid_storage_type storage_type
86
+ storage_type.to_sym.tap do |type|
87
+ invalid_storage_type! type unless type.in? valid_storage_type_list
88
+ end
89
+ end
90
+
91
+ def valid_storage_type_list
92
+ CarrierWave::FileCardUploader::STORAGE_TYPES
93
+ end
94
+
95
+ def invalid_storage_type! type
96
+ raise Card::Error, tr(:error_invalid_storage_type, type: type)
97
+ end
98
+
99
+ def storage_type_from_content
100
+ case content
101
+ when /^\(/ then :cloud
102
+ when %r{/^https?\:/} then :web
103
+ when /^~/ then :local
104
+ when /^\:/ then :coded
105
+ else
106
+ if deprecated_mod_file?
107
+ :coded
108
+ else
109
+ storage_type_from_config
110
+ end
111
+ end
112
+ end
113
+
114
+ def update_storage_attributes
115
+ @mod = @new_mod if @new_mod
116
+ @bucket = @new_bucket if @new_bucket
117
+ @storage_type = @new_storage_type
118
+ end
119
+
120
+ def storage_type_changed?
121
+ @new_bucket || (@new_storage_type && @new_storage_type != storage_type) || @new_mod
122
+ end
123
+
124
+ def storage_type= value
125
+ known_storage_type? value
126
+ if @action == :update #&& storage_type != value
127
+ # we cant update the storage type directly here
128
+ # if we do then the uploader doesn't find the file we want to update
129
+ @new_storage_type = value
130
+ else
131
+ @storage_type = value
132
+ end
133
+ end
134
+
135
+ def with_storage_options opts={}
136
+ old_values = {}
137
+ validate_temporary_storage_type_change opts[:storage_type]
138
+ %i[storage_type mod bucket].each do |opt_name|
139
+ next unless opts[opt_name]
140
+ old_values[opt_name] = instance_variable_get "@#{opt_name}"
141
+ instance_variable_set "@#{opt_name}", opts[opt_name]
142
+ @temp_storage_type = true
143
+ end
144
+ yield
145
+ ensure
146
+ @temp_storage_type = false
147
+ old_values.each do |key, val|
148
+ instance_variable_set "@#{key}", val
149
+ end
150
+ end
151
+
152
+ def temporary_storage_type_change?
153
+ @temp_storage_type
154
+ end
155
+
156
+ def validate_temporary_storage_type_change new_storage_type=nil
157
+ new_storage_type ||= @new_storage_type
158
+ return unless new_storage_type
159
+ unless known_storage_type? new_storage_type
160
+ raise Error, tr(:unknown_storage_type, new_storage_type: new_storage_type)
161
+ end
162
+
163
+ if new_storage_type == :coded && codename.blank?
164
+ raise Error, "codename needed for storage type :coded"
165
+ end
166
+ end
167
+
168
+ def known_storage_type? type=storage_type
169
+ type.in? CarrierWave::FileCardUploader::STORAGE_TYPES
170
+ end