card 1.20.0 → 1.20.1

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.
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;