card 1.20.0 → 1.20.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/card.gemspec +1 -1
  4. data/lib/card.rb +7 -3
  5. data/lib/card/act_manager/stage_director.rb +22 -10
  6. data/lib/card/cache/persistent.rb +15 -9
  7. data/lib/card/format/nest.rb +12 -5
  8. data/lib/card/format/nest/fetch.rb +2 -1
  9. data/lib/card/format/render.rb +5 -2
  10. data/lib/card/set/event.rb +7 -1
  11. data/lib/card/subcards.rb +3 -3
  12. data/lib/card/view/cache.rb +1 -1
  13. data/lib/card/view/fetch.rb +17 -5
  14. data/lib/card/view/options.rb +52 -26
  15. data/lib/cardio.rb +2 -1
  16. data/mod/account/spec/set/right/account_spec.rb +17 -3
  17. data/mod/admin/set/self/admin.rb +0 -2
  18. data/mod/admin/spec/set/self/admin_spec.rb +14 -11
  19. data/mod/bootstrap/set/all/bootstrap/helper.rb +27 -29
  20. data/mod/carrierwave/set/type/file.rb +1 -1
  21. data/mod/carrierwave/set/type/image.rb +18 -6
  22. data/mod/core/set/all/collection.rb +33 -7
  23. data/mod/core/set/all/fetch.rb +16 -3
  24. data/mod/core/set/all/permissions.rb +6 -12
  25. data/mod/core/set/all/trash.rb +3 -1
  26. data/mod/core/spec/set/all/collection_spec.rb +9 -8
  27. data/mod/email/set/all/notify.rb +27 -17
  28. data/mod/email/set/right/follow.rb +49 -36
  29. data/mod/email/set/type/email_template.rb +25 -69
  30. data/mod/email/set/type/email_template/email_config.rb +63 -0
  31. data/mod/email/set/type_plus_right/user/follow.rb +3 -3
  32. data/mod/machines/lib/stylesheets/style_cards.scss +292 -0
  33. data/mod/pointer/set/abstract/01_pointer.rb +8 -8
  34. data/mod/pointer/set/abstract/01_pointer/edit.rb +6 -2
  35. data/mod/prosemirror_editor/lib/javascript/script_prosemirror.js +12283 -11605
  36. data/mod/prosemirror_editor/lib/javascript/script_prosemirror_config.js.coffee +1 -1
  37. data/mod/standard/set/abstract/01_search_params.rb +1 -1
  38. data/mod/standard/set/abstract/search/paging.rb +4 -4
  39. data/mod/standard/set/all/comment.rb +67 -47
  40. data/mod/standard/set/all/links.rb +2 -2
  41. data/mod/standard/set/all/rich_html/content.rb +1 -1
  42. data/mod/standard/set/all/rich_html/editing.rb +3 -2
  43. data/mod/standard/set/all/rich_html/form.rb +21 -12
  44. data/mod/standard/set/all/rich_html/header.rb +9 -0
  45. data/mod/standard/set/all/rich_html/menu.rb +16 -12
  46. data/mod/standard/set/all/rich_html/toolbar.rb +140 -130
  47. data/mod/standard/set/all/rich_html/wrapper.rb +11 -1
  48. data/mod/standard/set/rstar/rules_editor.rb +2 -34
  49. data/mod/standard/set/self/search.rb +1 -1
  50. data/mod/standard/set/type/set.rb +4 -4
  51. data/mod/standard/spec/set/type/email_template/email_config_spec.rb +218 -0
  52. data/mod/standard/spec/set/type/email_template_spec.rb +3 -185
  53. data/spec/lib/card/cache_spec.rb +0 -1
  54. data/spec/lib/card/format/render_spec.rb +19 -0
  55. data/spec/lib/card/stage_director_spec.rb +1 -1
  56. data/tmpsets/set/mod001-core/all/actify.rb +5 -6
  57. data/tmpsets/set/mod001-core/all/fetch.rb +14 -12
  58. data/tmpsets/set/mod001-core/all/name.rb +1 -1
  59. data/tmpsets/set/mod001-core/all/permissions.rb +12 -22
  60. data/tmpsets/set/mod001-core/all/tracked_attributes.rb +76 -0
  61. data/tmpsets/set/mod001-core/all/utils.rb +40 -3
  62. data/tmpsets/set/mod002-history/all/history.rb +1 -2
  63. data/tmpsets/set/mod008-solid_cache/abstract/solid_cache.rb +1 -1
  64. data/tmpsets/set/mod013-carrierwave/abstract/attachment.rb +282 -0
  65. data/tmpsets/set/mod013-carrierwave/type/file.rb +155 -0
  66. data/tmpsets/set/mod013-carrierwave/type/image.rb +96 -0
  67. data/tmpsets/set/mod014-admin/self/admin.rb +113 -0
  68. data/tmpsets/set/mod014-admin/self/admin_info.rb +110 -0
  69. data/tmpsets/set/mod014-admin/self/version.rb +15 -0
  70. data/tmpsets/set/mod015-developer/all/event_viz.rb +59 -0
  71. data/tmpsets/set/mod015-developer/all/view_viz.rb +30 -0
  72. data/tmpsets/set/mod015-developer/right/debug.rb +96 -0
  73. metadata +15 -2
@@ -62,7 +62,6 @@ describe Card::Cache do
62
62
  @cache.write("foo", "bar")
63
63
  expect(@cache.read("foo")).to eq("bar")
64
64
 
65
-
66
65
  # reset
67
66
  @cache.reset
68
67
  expect(@cache.hard.prefix).to match(/^mydb\//)
@@ -0,0 +1,19 @@
1
+ describe Card::Format::Render do
2
+ describe "view cache" do
3
+ before { Cardio.config.view_cache = true }
4
+
5
+ let(:cache_key) do
6
+ "a-Card::Format::HtmlFormat-normal-home_view:content;"\
7
+ "nest_name:A;nest_syntax:A|content;view:contentcontent:show"
8
+ end
9
+
10
+ subject { Card::Cache[Card::View] }
11
+
12
+ it "can be changed with nest option" do
13
+ is_expected.to receive(:fetch).with cache_key
14
+ render_content "{{A|content}}"
15
+ is_expected.not_to receive(:fetch)
16
+ render_content "{{A|cache:never}}"
17
+ end
18
+ end
19
+ end
@@ -127,7 +127,7 @@ describe Card::ActManager::StageDirector do
127
127
  event_called "i"
128
128
  end
129
129
  Card["A"].delete!
130
- expect(@called_events).to eq ["ptv"]
130
+ expect(@called_events).to eq []
131
131
  end
132
132
  end
133
133
 
@@ -52,12 +52,12 @@ def valid?(*)
52
52
  act { super }
53
53
  end
54
54
 
55
- def update_attributes *args
56
- act(*args) { super }
55
+ def update_attributes(*)
56
+ act { super }
57
57
  end
58
58
 
59
- def update_attributes! *args
60
- act(*args) { super }
59
+ def update_attributes!(*)
60
+ act { super }
61
61
  end
62
62
 
63
63
  def abortable
@@ -88,8 +88,7 @@ def with_transaction_returning_status
88
88
  end
89
89
 
90
90
  event :notable_exception_raised do
91
- error = Card::Error.current
92
- Rails.logger.debug "#{error.message}\n#{error.backtrace * "\n "}"
91
+ Rails.logger.debug "BT: #{Card::Error.current.backtrace * "\n "}"
93
92
  end
94
93
 
95
94
  def success
@@ -11,18 +11,20 @@ module ClassMethods
11
11
  # * database
12
12
  # * virtual cards
13
13
  #
14
- # @param args [Integer, String, Card::Name, Symbol]
15
- # one or more of the three unique identifiers
16
- # 1. a numeric id (Integer)
17
- # 2. a name/key (String or Card::Name)
18
- # 3. a codename (Symbol)
19
- # If you pass more then one mark they get joined with a '+'.
20
- # The final argument can be a hash to set the following options
21
- # :skip_virtual Real cards only
22
- # :skip_modules Don't load Set modules
23
- # :look_in_trash Return trashed card objects
24
- # :local_only Use only local cache for lookup and storing
25
- # new: { opts for Card#new } Return a new card when not found
14
+ # @param mark [Integer, String, Card::Name, Symbol, Array]
15
+ # one of three unique identifiers
16
+ # 1. a numeric id (Integer)
17
+ # 2. a name/key (String or Card::Name)
18
+ # 3. a codename (Symbol)
19
+ # or any combination of those. If you pass more then one mark they get
20
+ # joined with a '+'
21
+ # @param options [Hash]
22
+ # Options:
23
+ # :skip_virtual Real cards only
24
+ # :skip_modules Don't load Set modules
25
+ # :look_in_trash Return trashed card objects
26
+ # :local_only Use only local cache for lookup and storing
27
+ # new: { opts for Card#new } Return a new card when not found
26
28
  #
27
29
  # @return [Card]
28
30
  def fetch *args
@@ -56,11 +56,11 @@ def update_subcard_names cardname
56
56
  # and self is a subcard as well that changed from +B to A+B then
57
57
  # +C should change to A+B+C. #replace_part doesn't work in this case
58
58
  # because the old name +B is not a part of +C
59
+ # name_to_replace =
59
60
  name_to_replace =
60
61
  if subcard.cardname.junction? &&
61
62
  subcard.cardname.parts.first.empty? &&
62
63
  cardname.parts.first.present?
63
- # replace the empty part
64
64
  "".to_name
65
65
  else
66
66
  name
@@ -187,38 +187,28 @@ event :set_field_read_rules,
187
187
  end
188
188
  end
189
189
 
190
- # currently doing a brute force search for every card that may be impacted.
191
- # may want to optimize(?)
192
- def update_field_read_rules
190
+ def update_read_rule
191
+ Card.record_timestamps = false
192
+ reset_patterns # why is this needed?
193
+ rcard_id, rclass = permission_rule_id_and_class :read
194
+ # these two are just to make sure vals are correct on current object
195
+ self.read_rule_id = rcard_id
196
+ self.read_rule_class = rclass
197
+ Card.where(id: id).update_all read_rule_id: rcard_id, read_rule_class: rclass
198
+ expire_hard
199
+
200
+ # currently doing a brute force search for every card that may be impacted.
201
+ # may want to optimize(?)
193
202
  Auth.as_bot do
194
203
  fields.each do |field|
195
204
  field.update_read_rule if field.rule(:read) == "_left"
196
205
  end
197
206
  end
198
- end
199
207
 
200
- def without_timestamps
201
- Card.record_timestamps = false
202
- yield
203
208
  ensure
204
209
  Card.record_timestamps = true
205
210
  end
206
211
 
207
- event :update_read_rule do
208
- without_timestamps do
209
- reset_patterns # why is this needed?
210
- rcard_id, rclass = permission_rule_id_and_class :read
211
- # these two are just to make sure vals are correct on current object
212
- self.read_rule_id = rcard_id
213
- self.read_rule_class = rclass
214
- Card.where(id: id).update_all read_rule_id: rcard_id,
215
- read_rule_class: rclass
216
- expire_hard
217
-
218
- update_field_read_rules
219
- end
220
- end
221
-
222
212
  def add_to_read_rule_update_queue updates
223
213
  @read_rule_update_queue = Array.wrap(@read_rule_update_queue).concat updates
224
214
  end
@@ -73,6 +73,82 @@ event :set_content, :store, on: :save do
73
73
  reset_patterns_if_rule true
74
74
  end
75
75
 
76
+ # FIXME: the following don't really belong here, but they have to come after
77
+ # the reference stuff. we need to organize a bit!
78
+
79
+ event :update_ruled_cards, :finalize do
80
+ if is_rule?
81
+ # warn "updating ruled cards for #{name}"
82
+ self.class.clear_rule_cache
83
+ set = rule_set
84
+
85
+ if right_id == Card::ReadID && (name_changed? || trash_changed?)
86
+ update_read_ruled_cards set
87
+ end
88
+ end
89
+ end
90
+
91
+ def update_read_rules_of_set_members_not_governed_by_narrower_rules set
92
+ return {} if trash || !set || !(set_class = set.tag) ||
93
+ !(class_id = set_class.id)
94
+ in_set = {}
95
+ rule_class_ids = set_patterns.map(&:pattern_id)
96
+ Auth.as_bot do
97
+ cur_index = rule_class_ids.index Card[read_rule_class].id
98
+ if (rule_class_index = rule_class_ids.index(class_id))
99
+ set.item_cards(limit: 0).each do |item_card|
100
+ in_set[item_card.key] = true
101
+ next if cur_index < rule_class_index
102
+ item_card.update_read_rule if cur_index >= rule_class_index
103
+ end
104
+ else
105
+ warn "No current rule index #{class_id}, #{rule_class_ids.inspect}"
106
+ end
107
+ end
108
+ in_set
109
+ end
110
+
111
+ def update_read_ruled_cards set
112
+ self.class.clear_read_rule_cache
113
+ Card.cache.reset # maybe be more surgical, just Auth.user related
114
+ expire # probably shouldn't be necessary,
115
+ # but was sometimes getting cached version when card should be in the
116
+ # trash. could be related to other bugs?
117
+
118
+ updated = update_read_rules_of_set_members_not_governed_by_narrower_rules set
119
+
120
+ # then find all cards with me as read_rule_id that were not just updated
121
+ # and regenerate their read_rules
122
+ return if new_card?
123
+ Card.search(read_rule_id: id) do |card|
124
+ card.update_read_rule unless updated[card.key]
125
+ end
126
+ end
127
+
128
+ event :process_read_rule_update_queue, :finalize do
129
+ Array.wrap(@read_rule_update_queue).each(&:update_read_rule)
130
+ @read_rule_update_queue = []
131
+ end
132
+
133
+ event :expire_related, :finalize do
134
+ subcards.keys.each do |key|
135
+ Card.cache.soft.delete key
136
+ end
137
+ expire # FIXME: where do we put this. Here it deletes @stage
138
+ reset_patterns
139
+ if is_structure?
140
+ structuree_names.each do |name|
141
+ Card.expire name
142
+ end
143
+ end
144
+ end
145
+
146
+ event :expire_related_names, before: :expire_related, changed: :name do
147
+ # FIXME: look for opportunities to avoid instantiating the following
148
+ descendants.each(&:expire)
149
+ name_referers.each(&:expire)
150
+ end
151
+
76
152
 
77
153
  # ~~ below autogenerated; above pulled from /Users/ethan/dev/wagn/gem/card/mod/core/set/all/tracked_attributes.rb ~~
78
154
  end;end;end;end;
@@ -8,6 +8,7 @@ module ClassMethods
8
8
  Card::Action.delete_cardless
9
9
  Card::Reference.unmap_if_referee_missing
10
10
  Card::Reference.delete_if_referer_missing
11
+ Card.delete_tmp_files_of_cached_uploads
11
12
  end
12
13
 
13
14
  # deletes any file not associated with a real card.
@@ -43,6 +44,20 @@ module ClassMethods
43
44
  sql_results.map(&:values).flatten.map(&:to_i)
44
45
  end
45
46
 
47
+ def delete_tmp_files_of_cached_uploads
48
+ actions = Card::Action.find_by_sql "SELECT * FROM card_actions
49
+ INNER JOIN cards ON card_actions.card_id = cards.id
50
+ WHERE cards.type_id IN (#{Card::FileID}, #{Card::ImageID})
51
+ AND card_actions.draft = true"
52
+ actions.each do |action|
53
+ # we don't want to delete uploads in progress
54
+ if older_than_five_days?(action.created_at) && (card = action.card)
55
+ # we don't want to delete uploads in progress
56
+ card.delete_files_for_action action
57
+ end
58
+ end
59
+ end
60
+
46
61
  def merge_list attribs, opts={}
47
62
  unmerged = []
48
63
  attribs.each do |row|
@@ -72,7 +87,7 @@ module ClassMethods
72
87
  end
73
88
 
74
89
  def merge name, attribs={}, opts={}
75
- puts "merging #{name}"
90
+ # puts "merging #{name}"
76
91
  card = fetch name, new: {}
77
92
  [:image, :file].each do |attach|
78
93
  next unless attribs[attach] && attribs[attach].is_a?(String)
@@ -85,11 +100,33 @@ module ClassMethods
85
100
  end
86
101
  end
87
102
 
88
- def seed_test_db
89
- system "env RAILS_ENV=test bundle exec rake db:fixtures:load"
103
+ def older_than_five_days? time
104
+ Time.now - time > 432_000
90
105
  end
91
106
  end
92
107
 
108
+ def debug_type
109
+ "#{type_code || ''}:#{type_id}"
110
+ end
111
+
112
+ def to_s
113
+ "#<#{self.class.name}[#{debug_type}]#{attributes['name']}>"
114
+ end
115
+
116
+ def inspect
117
+ tags = []
118
+ tags << "trash" if trash
119
+ tags << "new" if new_card?
120
+ tags << "frozen" if frozen?
121
+ tags << "readonly" if readonly?
122
+ tags << "virtual" if @virtual
123
+ tags << "set_mods_loaded" if @set_mods_loaded
124
+
125
+ error_messages = errors.any? ? "<E*#{errors.full_messages * ', '}*>" : ""
126
+
127
+ "#<Card##{id}[#{debug_type}](#{name})#{error_messages}{#{tags * ','}}"
128
+ end
129
+
93
130
 
94
131
  # ~~ below autogenerated; above pulled from /Users/ethan/dev/wagn/gem/card/mod/core/set/all/utils.rb ~~
95
132
  end;end;end;end;
@@ -85,8 +85,7 @@ event :rollback_actions, :prepare_to_validate,
85
85
  Env.params["action_ids"] = nil
86
86
  update_attributes! revision
87
87
  rollback_actions.each do |action|
88
- # rollback file and image cards
89
- action.card.try :rollback_to, action
88
+ action.card.try :symlink_to, action.id
90
89
  end
91
90
  clear_drafts
92
91
  abort :success
@@ -31,7 +31,7 @@ module ClassMethods
31
31
  # cards whose solid caches are expired because of the update.
32
32
  # @param set_of_changed_card [set constant] a set of cards that triggers
33
33
  # a cache update
34
- # @param args [Hash]
34
+ # @params args [Hash]
35
35
  # @option args [Symbol, Array of symbols] :on the action(s)
36
36
  # (:create, :update, or :delete) on which the cache update
37
37
  # should be triggered. Default is all actions.
@@ -0,0 +1,282 @@
1
+ # -*- encoding : utf-8 -*-
2
+ class Card; module Set; class Abstract; module Attachment; extend Card::Set
3
+ # ~~ above autogenerated; below pulled from /Users/ethan/dev/wagn/gem/card/mod/carrierwave/set/abstract/attachment.rb ~~
4
+ require "carrier_wave/cardmount"
5
+
6
+ def self.included host_class
7
+ host_class.extend CarrierWave::CardMount
8
+ end
9
+
10
+ event :select_file_revision, after: :select_action do
11
+ attachment.retrieve_from_store!(attachment.identifier)
12
+ end
13
+
14
+ event :upload_attachment, :prepare_to_validate,
15
+ on: :save, when: proc { |c| c.preliminary_upload? } do
16
+ save_original_filename # save original filename as comment in action
17
+ write_identifier # set db_content
18
+ # (needs original filename to determine extension)
19
+ store_attachment!
20
+ finalize_action # create Card::Change entry for db_content
21
+
22
+ card_id = new_card? ? upload_cache_card.id : id
23
+ @current_action.update_attributes! draft: true, card_id: card_id
24
+ success << {
25
+ target: (new_card? ? upload_cache_card : self),
26
+ type: type_name,
27
+ view: "preview_editor",
28
+ rev_id: current_action.id
29
+ }
30
+ abort :success
31
+ end
32
+
33
+ event :assign_attachment_on_create, :initialize,
34
+ after: :assign_action, on: :create,
35
+ when: proc { |c| c.save_preliminary_upload? } do
36
+ if (action = Card::Action.fetch(@action_id_of_cached_upload))
37
+ upload_cache_card.selected_action_id = action.id
38
+ upload_cache_card.select_file_revision
39
+ assign_attachment upload_cache_card.attachment.file, action.comment
40
+ end
41
+ end
42
+
43
+ event :assign_attachment_on_update, :initialize,
44
+ after: :assign_action, on: :update,
45
+ when: proc { |c| c.save_preliminary_upload? } do
46
+ if (action = Card::Action.fetch(@action_id_of_cached_upload))
47
+ uploaded_file =
48
+ with_selected_action_id(action.id) do
49
+ attachment.file
50
+ end
51
+ assign_attachment uploaded_file, action.comment
52
+ end
53
+ end
54
+
55
+ def assign_attachment file, original_filename
56
+ send "#{attachment_name}=", file
57
+ write_identifier
58
+ @current_action.update_attributes! comment: original_filename
59
+ end
60
+
61
+ # we need a card id for the path so we have to update db_content when we have
62
+ # an id
63
+ event :correct_identifier, :finalize, on: :create do
64
+ update_column(:db_content, attachment.db_content(mod: load_from_mod))
65
+ expire
66
+ end
67
+
68
+ def file_ready_to_save?
69
+ attachment.file.present? &&
70
+ !preliminary_upload? &&
71
+ !save_preliminary_upload? &&
72
+ attachment_changed?
73
+ end
74
+
75
+ event :save_original_filename, :prepare_to_store,
76
+ when: proc { |c| c.file_ready_to_save? } do
77
+ return unless @current_action
78
+ @current_action.update_attributes! comment: original_filename
79
+ end
80
+
81
+ event :delete_cached_upload_file_on_create, :integrate,
82
+ on: :create, when: proc { |c| c.save_preliminary_upload? } do
83
+ if (action = Card::Action.fetch(@action_id_of_cached_upload))
84
+ upload_cache_card.delete_files_for_action action
85
+ action.delete
86
+ end
87
+ clear_upload_cache_dir_for_new_cards
88
+ end
89
+
90
+ event :delete_cached_upload_file_on_update, :integrate,
91
+ on: :update, when: proc { |c| c.save_preliminary_upload? } do
92
+ if (action = Card::Action.fetch(@action_id_of_cached_upload))
93
+ delete_files_for_action action
94
+ action.delete
95
+ end
96
+ end
97
+
98
+ event :validate_file_exist, :validate, on: :create do
99
+ unless attachment.file.present? || empty_ok?
100
+ errors.add attachment_name, "is missing"
101
+ end
102
+ end
103
+
104
+ event :write_identifier, after: :save_original_filename do
105
+ self.content = attachment.db_content(mod: load_from_mod)
106
+ end
107
+
108
+ def item_names _args={} # needed for flexmail attachments. hacky.
109
+ [cardname]
110
+ end
111
+
112
+ def original_filename
113
+ attachment.original_filename
114
+ end
115
+
116
+ def unfilled?
117
+ !attachment.present? && !save_preliminary_upload? && !subcards.present?
118
+ end
119
+
120
+ def preliminary_upload?
121
+ Card::Env && Card::Env.params[:attachment_upload]
122
+ end
123
+
124
+ def save_preliminary_upload?
125
+ @action_id_of_cached_upload.present?
126
+ end
127
+
128
+ def attachment_changed?
129
+ send "#{attachment_name}_changed?"
130
+ end
131
+
132
+ def create_versions?
133
+ true
134
+ end
135
+
136
+ # used for uploads for new cards until the new card is created
137
+ def upload_cache_card
138
+ @upload_cache_card ||= Card["new_#{attachment_name}".to_sym]
139
+ end
140
+
141
+ # action id of the cached upload
142
+ attr_writer :action_id_of_cached_upload
143
+
144
+ attr_reader :action_id_of_cached_upload
145
+
146
+ attr_writer :empty_ok
147
+
148
+ def empty_ok?
149
+ @empty_ok
150
+ end
151
+
152
+ def load_from_mod= value
153
+ @mod = value
154
+ write_identifier
155
+ @store_in_mod = true if value
156
+ end
157
+
158
+ def load_from_mod
159
+ @mod
160
+ end
161
+
162
+ def store_dir
163
+ if @store_in_mod
164
+ mod_dir
165
+ else
166
+ upload_dir
167
+ end
168
+ end
169
+
170
+ def retrieve_dir
171
+ if mod_file?
172
+ mod_dir
173
+ else
174
+ upload_dir
175
+ end
176
+ end
177
+
178
+ # place for files of regular file cards
179
+ def upload_dir
180
+ if id
181
+ "#{Card.paths['files'].existent.first}/#{id}"
182
+ else
183
+ tmp_upload_dir
184
+ end
185
+ end
186
+
187
+ # place for files if card doesn't have an id yet
188
+ def tmp_upload_dir _action_id=nil
189
+ "#{Card.paths['files'].existent.first}/#{upload_cache_card.id}"
190
+ end
191
+
192
+ # place for files of mod file cards
193
+ def mod_dir
194
+ mod = @mod || mod_file?
195
+ Card.paths["mod"].to_a.each do |mod_path|
196
+ dir = File.join(mod_path, mod, "file", codename)
197
+ return dir if Dir.exist? dir
198
+ end
199
+ end
200
+
201
+ def mod_file?
202
+ return @mod if @store_in_mod
203
+ # when db_content was changed assume that it's no longer a mod file
204
+ return if db_content_changed? || !content.present?
205
+ case content
206
+ when %r{^:[^/]+/([^.]+)} then Regexp.last_match(1) # current mod_file format
207
+ when /^\~/ then false # current id file format
208
+ else
209
+ if (lines = content.split("\n")) && (lines.size == 4)
210
+ # old format, still used in card_changes.
211
+ lines.last
212
+ end
213
+ end
214
+ end
215
+
216
+ def assign_set_specific_attributes
217
+ # reset content if we really have something to upload
218
+ if @set_specific.present? && @set_specific[attachment_name.to_s].present?
219
+ self.content = nil
220
+ end
221
+ super
222
+ end
223
+
224
+ def clear_upload_cache_dir_for_new_cards
225
+ Dir.entries(tmp_upload_dir).each do |filename|
226
+ if filename =~ /^\d+/
227
+ path = File.join(tmp_upload_dir, filename)
228
+ FileUtils.rm path if Card.older_than_five_days? File.ctime(path)
229
+ end
230
+ end
231
+ end
232
+
233
+ def delete_files_for_action action
234
+ with_selected_action_id(action.id) do
235
+ FileUtils.rm attachment.file.path
236
+ attachment.versions.each_value do |version|
237
+ FileUtils.rm version.path
238
+ end
239
+ end
240
+ end
241
+
242
+ # create filesystem links to files from prior action
243
+ def symlink_to prior_action_id
244
+ return unless prior_action_id != last_action_id
245
+ save_action_id = selected_action_id
246
+ links = {}
247
+
248
+ self.selected_action_id = prior_action_id
249
+ attachment.versions.each do |name, version|
250
+ links[name] = version.store_path
251
+ end
252
+ original = attachment.store_path
253
+
254
+ self.selected_action_id = last_action_id
255
+ attachment.versions.each do |name, version|
256
+ ::File.symlink links[name], version.store_path
257
+ end
258
+ ::File.symlink original, attachment.store_path
259
+
260
+ self.selected_action_id = save_action_id
261
+ end
262
+
263
+ def attachment_format ext
264
+ if ext.present? && attachment && (original_ext = attachment.extension)
265
+ if ["file", original_ext].member? ext
266
+ original_ext
267
+ elsif (exts = MIME::Types[attachment.content_type])
268
+ if exts.find { |mt| mt.extensions.member? ext }
269
+ ext
270
+ else
271
+ exts[0].extensions[0]
272
+ end
273
+ end
274
+ end
275
+ rescue => e
276
+ Rails.logger.info "attachment_format issue: #{e.message}"
277
+ nil
278
+ end
279
+
280
+
281
+ # ~~ below autogenerated; above pulled from /Users/ethan/dev/wagn/gem/card/mod/carrierwave/set/abstract/attachment.rb ~~
282
+ end;end;end;end;