curation_concerns 1.0.0.beta1 → 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/actors/curation_concerns/actors/abstract_actor.rb +30 -0
- data/app/actors/curation_concerns/actors/actor_stack.rb +29 -0
- data/app/actors/curation_concerns/actors/add_to_collection_actor.rb +40 -0
- data/app/actors/curation_concerns/actors/apply_order_actor.rb +26 -0
- data/app/actors/curation_concerns/actors/assign_identifier_actor.rb +9 -0
- data/app/actors/curation_concerns/actors/assign_representative_actor.rb +20 -0
- data/app/actors/curation_concerns/actors/attach_files_actor.rb +41 -0
- data/app/actors/curation_concerns/actors/base_actor.rb +78 -0
- data/app/actors/curation_concerns/actors/embargo_actor.rb +21 -0
- data/app/actors/curation_concerns/actors/file_actor.rb +81 -0
- data/app/actors/curation_concerns/actors/file_set_actor.rb +159 -0
- data/app/actors/curation_concerns/actors/interpret_visibility_actor.rb +125 -0
- data/app/actors/curation_concerns/actors/lease_actor.rb +21 -0
- data/app/actors/curation_concerns/actors/root_actor.rb +19 -0
- data/app/actors/curation_concerns/actors/work_actor_behavior.rb +12 -0
- data/app/actors/curation_concerns/actors.rb +18 -0
- data/app/controllers/concerns/curation_concerns/embargoes_controller_behavior.rb +2 -2
- data/app/controllers/concerns/curation_concerns/file_sets_controller_behavior.rb +21 -15
- data/app/controllers/concerns/curation_concerns/leases_controller_behavior.rb +2 -2
- data/app/forms/curation_concerns/forms/collection_edit_form.rb +1 -1
- data/app/forms/curation_concerns/forms/file_set_edit_form.rb +2 -2
- data/app/forms/curation_concerns/forms/work_form.rb +1 -1
- data/app/forms/curation_concerns/forms.rb +14 -0
- data/app/helpers/curation_concerns/collections_helper.rb +10 -8
- data/app/jobs/audit_job.rb +0 -2
- data/app/jobs/characterize_job.rb +1 -1
- data/app/jobs/create_derivatives_job.rb +1 -1
- data/app/jobs/import_url_job.rb +2 -2
- data/app/jobs/ingest_file_job.rb +1 -1
- data/app/jobs/ingest_local_file_job.rb +2 -2
- data/app/jobs/resolrize_job.rb +0 -2
- data/app/jobs/visibility_copy_job.rb +0 -2
- data/app/models/concerns/curation_concerns/basic_metadata.rb +1 -1
- data/app/models/concerns/curation_concerns/characterization.rb +41 -0
- data/app/models/concerns/curation_concerns/collection.rb +0 -1
- data/app/models/concerns/curation_concerns/file_set_behavior.rb +1 -1
- data/app/models/concerns/curation_concerns/solr_document_behavior.rb +6 -5
- data/app/presenters/curation_concerns/collection_presenter.rb +1 -1
- data/app/presenters/curation_concerns/file_set_presenter.rb +1 -1
- data/app/presenters/curation_concerns/presents_attributes.rb +1 -1
- data/app/renderers/curation_concerns/renderers/attribute_renderer.rb +100 -0
- data/app/renderers/curation_concerns/renderers/configured_microdata.rb +42 -0
- data/app/renderers/renderers.rb +11 -0
- data/app/services/curation_concerns/actors/actor_factory.rb +26 -0
- data/app/services/curation_concerns/curation_concern.rb +1 -1
- data/app/services/curation_concerns/thumbnail_path_service.rb +1 -1
- data/app/views/collections/show.html.erb +7 -3
- data/app/views/curation_concerns/base/_representative_media.html.erb +1 -1
- data/app/views/curation_concerns/file_sets/show.html.erb +1 -1
- data/config/locales/curation_concerns.en.yml +4 -4
- data/curation_concerns.gemspec +1 -4
- data/lib/curation_concerns/configuration.rb +7 -0
- data/lib/curation_concerns/version.rb +1 -1
- data/lib/generators/curation_concerns/templates/catalog_controller.rb +4 -4
- data/lib/generators/curation_concerns/work/templates/actor.rb.erb +2 -2
- data/lib/generators/curation_concerns/work/templates/actor_spec.rb.erb +1 -1
- data/lib/generators/curation_concerns/work/work_generator.rb +2 -2
- data/spec/actors/curation_concerns/add_to_collections_actor_spec.rb +6 -6
- data/spec/actors/curation_concerns/embargo_actor_spec.rb +1 -1
- data/spec/actors/curation_concerns/file_actor_spec.rb +1 -1
- data/spec/actors/curation_concerns/file_set_actor_spec.rb +11 -10
- data/spec/actors/curation_concerns/interpret_visibility_actor_spec.rb +6 -6
- data/spec/actors/curation_concerns/lease_actor_spec.rb +1 -1
- data/spec/actors/curation_concerns/work_actor_spec.rb +1 -1
- data/spec/controllers/curation_concerns/collections_controller_spec.rb +3 -3
- data/spec/controllers/curation_concerns/file_sets_controller_spec.rb +4 -4
- data/spec/controllers/embargoes_controller_spec.rb +1 -1
- data/spec/controllers/leases_controller_spec.rb +1 -1
- data/spec/factories/generic_works.rb +1 -1
- data/spec/features/add_file_spec.rb +1 -1
- data/spec/features/work_generator_spec.rb +1 -1
- data/spec/forms/collection_edit_form_spec.rb +2 -2
- data/spec/forms/file_set_edit_form_spec.rb +1 -1
- data/spec/forms/work_form_spec.rb +2 -2
- data/spec/indexers/file_set_indexer_spec.rb +12 -9
- data/spec/jobs/import_url_job_spec.rb +2 -2
- data/spec/jobs/ingest_local_file_job_spec.rb +1 -1
- data/spec/models/curation_concerns/collection_behavior_spec.rb +12 -3
- data/spec/models/file_set_spec.rb +25 -19
- data/spec/presenters/curation_concerns/collection_presenter_spec.rb +1 -1
- data/spec/presenters/curation_concerns/file_set_presenter_spec.rb +1 -1
- data/spec/presenters/curation_concerns/work_show_presenter_spec.rb +2 -2
- data/spec/renderers/curation_concerns/{attribute_renderer_spec.rb → renderers/attribute_renderer_spec.rb} +2 -2
- data/spec/services/curation_concern_spec.rb +1 -1
- data/spec/services/thumbnail_path_service_spec.rb +13 -9
- data/spec/support/curation_concerns/factory_helpers.rb +18 -0
- metadata +28 -50
- data/app/actors/curation_concerns/abstract_actor.rb +0 -28
- data/app/actors/curation_concerns/actor_stack.rb +0 -27
- data/app/actors/curation_concerns/add_to_collection_actor.rb +0 -38
- data/app/actors/curation_concerns/apply_order_actor.rb +0 -24
- data/app/actors/curation_concerns/assign_identifier_actor.rb +0 -7
- data/app/actors/curation_concerns/assign_representative_actor.rb +0 -18
- data/app/actors/curation_concerns/attach_files_actor.rb +0 -39
- data/app/actors/curation_concerns/base_actor.rb +0 -76
- data/app/actors/curation_concerns/embargo_actor.rb +0 -19
- data/app/actors/curation_concerns/file_actor.rb +0 -79
- data/app/actors/curation_concerns/file_set_actor.rb +0 -157
- data/app/actors/curation_concerns/interpret_visibility_actor.rb +0 -123
- data/app/actors/curation_concerns/lease_actor.rb +0 -19
- data/app/actors/curation_concerns/root_actor.rb +0 -17
- data/app/actors/curation_concerns/work_actor_behavior.rb +0 -8
- data/app/renderers/curation_concerns/attribute_renderer.rb +0 -98
- data/app/renderers/curation_concerns/configured_microdata.rb +0 -40
- data/app/services/curation_concerns/actor_factory.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96756d166c6631a6999f710b4d914c1633bf87b6
|
4
|
+
data.tar.gz: 3a0dee7f354c71afc0b72844e3eb6a37cbc48b3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 616e0cec3bf1225238270505c6966c83639857dd54b5fbcb500c9b677341d6e775ea6abec678dfe2a8e113644c51379fca9f9fc5df0bf61716c46a707df0b67a
|
7
|
+
data.tar.gz: 09ca08d9a68ef729446a05e92f4e604ce8b2b764e35a73dcb0b12c8876fc2c68b2cb2711a3c7580d6e2fdc6730f85c150bc49ad4b20a32a6f9438bb6f069a4cf
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
# The CurationConcern Abstract actor responds to two primary actions:
|
4
|
+
# * #create
|
5
|
+
# * #update
|
6
|
+
#
|
7
|
+
# and the following attributes
|
8
|
+
#
|
9
|
+
# * next_actor
|
10
|
+
# * curation_concern
|
11
|
+
# * user
|
12
|
+
#
|
13
|
+
# it must instantiate the next actor in the chain and instantiate it.
|
14
|
+
# it should respond to curation_concern, user and attributes.
|
15
|
+
# it ha to next_actor
|
16
|
+
class AbstractActor
|
17
|
+
attr_reader :next_actor
|
18
|
+
|
19
|
+
def initialize(_curation_concern, _user, next_actor)
|
20
|
+
@next_actor = next_actor
|
21
|
+
end
|
22
|
+
|
23
|
+
delegate :curation_concern, :user, to: :next_actor
|
24
|
+
|
25
|
+
delegate :create, to: :next_actor
|
26
|
+
|
27
|
+
delegate :update, to: :next_actor
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
class ActorStack
|
4
|
+
attr_reader :curation_concern, :user, :first_actor_class, :more_actors
|
5
|
+
def initialize(curation_concern, user, more_actors)
|
6
|
+
@curation_concern = curation_concern
|
7
|
+
@user = user
|
8
|
+
@more_actors = more_actors
|
9
|
+
@first_actor_class = @more_actors.shift || RootActor
|
10
|
+
end
|
11
|
+
|
12
|
+
def inner_stack
|
13
|
+
Actors::ActorStack.new(curation_concern, user, more_actors)
|
14
|
+
end
|
15
|
+
|
16
|
+
def actor
|
17
|
+
first_actor_class.new(curation_concern, user, inner_stack)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create(attributes)
|
21
|
+
actor.create(attributes.with_indifferent_access)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update(attributes)
|
25
|
+
actor.update(attributes.with_indifferent_access)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
class AddToCollectionActor < AbstractActor
|
4
|
+
def create(attributes)
|
5
|
+
collection_ids = attributes.delete(:collection_ids)
|
6
|
+
next_actor.create(attributes) && add_to_collections(collection_ids)
|
7
|
+
end
|
8
|
+
|
9
|
+
def update(attributes)
|
10
|
+
collection_ids = attributes.delete(:collection_ids)
|
11
|
+
add_to_collections(collection_ids) && next_actor.update(attributes)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# The default behavior of active_fedora's aggregates association,
|
17
|
+
# when assigning the id accessor (e.g. collection_ids = ['foo:1']) is to add
|
18
|
+
# to new collections, but not remove from old collections.
|
19
|
+
# This method ensures it's removed from the old collections.
|
20
|
+
def add_to_collections(new_collection_ids)
|
21
|
+
return true unless new_collection_ids
|
22
|
+
# remove from old collections
|
23
|
+
# TODO: Implement in_collection_ids https://github.com/projecthydra-labs/hydra-pcdm/issues/157
|
24
|
+
(curation_concern.in_collections.map(&:id) - new_collection_ids).each do |old_id|
|
25
|
+
collection = ::Collection.find(old_id)
|
26
|
+
collection.members.delete(curation_concern)
|
27
|
+
collection.save
|
28
|
+
end
|
29
|
+
|
30
|
+
# add to new
|
31
|
+
new_collection_ids.each do |coll_id|
|
32
|
+
collection = ::Collection.find(coll_id)
|
33
|
+
collection.members << curation_concern
|
34
|
+
collection.save
|
35
|
+
end
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
class ApplyOrderActor < AbstractActor
|
4
|
+
def update(attributes)
|
5
|
+
ordered_member_ids = attributes.delete(:ordered_member_ids)
|
6
|
+
apply_order(ordered_member_ids) && next_actor.update(attributes)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def apply_order(new_order)
|
12
|
+
return true unless new_order
|
13
|
+
curation_concern.ordered_member_proxies.each_with_index do |proxy, index|
|
14
|
+
unless new_order[index]
|
15
|
+
proxy.prev.next = curation_concern.ordered_member_proxies.last.next
|
16
|
+
break
|
17
|
+
end
|
18
|
+
proxy.proxy_for = ActiveFedora::Base.id_to_uri(new_order[index])
|
19
|
+
proxy.target = nil
|
20
|
+
end
|
21
|
+
curation_concern.list_source.order_will_change!
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
class AssignRepresentativeActor < AbstractActor
|
4
|
+
def create(attributes)
|
5
|
+
next_actor.create(attributes) && assign_representative
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def assign_representative
|
11
|
+
unless curation_concern.representative_id
|
12
|
+
# TODO: Possible optimization here. Does this cause a fetch of ordered_members if they're already loaded?
|
13
|
+
representative = nil # curation_concern.ordered_members.association.reader.first.target
|
14
|
+
curation_concern.representative = representative if representative
|
15
|
+
end
|
16
|
+
curation_concern.save
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
class AttachFilesActor < AbstractActor
|
4
|
+
def create(attributes)
|
5
|
+
files = [attributes.delete(:files)].flatten.compact
|
6
|
+
attach_files(files, visibility_attributes(attributes)) &&
|
7
|
+
next_actor.create(attributes)
|
8
|
+
end
|
9
|
+
|
10
|
+
def update(attributes)
|
11
|
+
files = [attributes.delete(:files)].flatten.compact
|
12
|
+
next_actor.update(attributes) &&
|
13
|
+
attach_files(files, visibility_attributes(attributes))
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def attach_files(files, visibility_attr)
|
19
|
+
files.all? do |file|
|
20
|
+
attach_file(file, visibility_attr)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def attach_file(file, visibility_attr)
|
25
|
+
file_set = ::FileSet.new
|
26
|
+
file_set_actor = CurationConcerns::Actors::FileSetActor.new(file_set, user)
|
27
|
+
file_set_actor.create_metadata(curation_concern, visibility_attr)
|
28
|
+
file_set_actor.create_content(file)
|
29
|
+
end
|
30
|
+
|
31
|
+
# The attributes used for visibility - used to send as initial params to
|
32
|
+
# created FileSets.
|
33
|
+
def visibility_attributes(attributes)
|
34
|
+
attributes.slice(:visibility, :visibility_during_lease,
|
35
|
+
:visibility_after_lease, :lease_expiration_date,
|
36
|
+
:embargo_release_date, :visibility_during_embargo,
|
37
|
+
:visibility_after_embargo)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
# The CurationConcern base actor responds to two primary actions:
|
4
|
+
# * #create
|
5
|
+
# * #update
|
6
|
+
# it must instantiate the next actor in the chain and instantiate it.
|
7
|
+
# it should respond to curation_concern, user and attributes.
|
8
|
+
class BaseActor < AbstractActor
|
9
|
+
attr_reader :cloud_resources
|
10
|
+
|
11
|
+
def create(attributes)
|
12
|
+
@cloud_resources = attributes.delete(:cloud_resources.to_s)
|
13
|
+
apply_creation_data_to_curation_concern
|
14
|
+
apply_save_data_to_curation_concern(attributes)
|
15
|
+
next_actor.create(attributes) && save && run_callbacks(:after_create_concern)
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(attributes)
|
19
|
+
apply_update_data_to_curation_concern
|
20
|
+
apply_save_data_to_curation_concern(attributes)
|
21
|
+
next_actor.update(attributes) && save && run_callbacks(:after_update_metadata)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def run_callbacks(hook)
|
27
|
+
CurationConcerns.config.callback.run(hook, curation_concern, user)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply_creation_data_to_curation_concern
|
32
|
+
apply_depositor_metadata
|
33
|
+
apply_deposit_date
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply_update_data_to_curation_concern
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def apply_depositor_metadata
|
41
|
+
curation_concern.apply_depositor_metadata(user.user_key)
|
42
|
+
curation_concern.edit_users += [user.user_key]
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply_deposit_date
|
46
|
+
curation_concern.date_uploaded = CurationConcerns::TimeService.time_in_utc
|
47
|
+
end
|
48
|
+
|
49
|
+
def save
|
50
|
+
curation_concern.save
|
51
|
+
end
|
52
|
+
|
53
|
+
def apply_save_data_to_curation_concern(attributes)
|
54
|
+
attributes[:rights] = Array(attributes[:rights]) if attributes.key? :rights
|
55
|
+
remove_blank_attributes!(attributes)
|
56
|
+
curation_concern.attributes = attributes.symbolize_keys
|
57
|
+
curation_concern.date_modified = CurationConcerns::TimeService.time_in_utc
|
58
|
+
end
|
59
|
+
|
60
|
+
# If any attributes are blank remove them
|
61
|
+
# e.g.:
|
62
|
+
# self.attributes = { 'title' => ['first', 'second', ''] }
|
63
|
+
# remove_blank_attributes!
|
64
|
+
# self.attributes
|
65
|
+
# => { 'title' => ['first', 'second'] }
|
66
|
+
def remove_blank_attributes!(attributes)
|
67
|
+
multivalued_form_attributes(attributes).each_with_object(attributes) do |(k, v), h|
|
68
|
+
h[k] = v.instance_of?(Array) ? v.select(&:present?) : v
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return the hash of attributes that are multivalued and not uploaded files
|
73
|
+
def multivalued_form_attributes(attributes)
|
74
|
+
attributes.select { |_, v| v.respond_to?(:select) && !v.respond_to?(:read) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
class EmbargoActor
|
4
|
+
attr_reader :work
|
5
|
+
|
6
|
+
# @param [Hydra::Works::Work] work
|
7
|
+
def initialize(work)
|
8
|
+
@work = work
|
9
|
+
end
|
10
|
+
|
11
|
+
# Update the visibility of the work to match the correct state of the embargo, then clear the embargo date, etc.
|
12
|
+
# Saves the embargo and the work
|
13
|
+
def destroy
|
14
|
+
work.embargo_visibility! # If the embargo has lapsed, update the current visibility.
|
15
|
+
work.deactivate_embargo!
|
16
|
+
work.embargo.save!
|
17
|
+
work.save!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
# actions for a file identified by file_set and relation (maps to use predicate)
|
4
|
+
class FileActor
|
5
|
+
attr_reader :file_set, :relation, :user
|
6
|
+
|
7
|
+
# @param [FileSet] file_set the parent FileSet
|
8
|
+
# @param [String] relation the type/use for the file.
|
9
|
+
# @param [User] user the user to record as the Agent acting upon the file
|
10
|
+
def initialize(file_set, relation, user)
|
11
|
+
@file_set = file_set
|
12
|
+
@relation = relation
|
13
|
+
@user = user
|
14
|
+
end
|
15
|
+
|
16
|
+
# Puts the uploaded content into a staging directory. Then kicks off a
|
17
|
+
# job to characterize and create derivatives with this on disk variant.
|
18
|
+
# Simultaneously moving a preservation copy to the repostiory.
|
19
|
+
# TODO: create a job to monitor this directory and prune old files that
|
20
|
+
# have made it to the repo
|
21
|
+
# @param [File, ActionDigest::HTTP::UploadedFile, Tempfile] file the file to save in the repository
|
22
|
+
def ingest_file(file)
|
23
|
+
working_file = copy_file_to_working_directory(file, file_set.id)
|
24
|
+
mime_type = file.respond_to?(:content_type) ? file.content_type : nil
|
25
|
+
IngestFileJob.perform_later(file_set, working_file, mime_type, user, relation)
|
26
|
+
make_derivative(file_set, working_file)
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def revert_to(revision_id)
|
31
|
+
repository_file = file_set.send(relation.to_sym)
|
32
|
+
repository_file.restore_version(revision_id)
|
33
|
+
|
34
|
+
return false unless file_set.save
|
35
|
+
|
36
|
+
CurationConcerns::VersioningService.create(repository_file, user)
|
37
|
+
|
38
|
+
# Retrieve a copy of the orginal file from the repository
|
39
|
+
working_file = copy_repository_resource_to_working_directory(repository_file)
|
40
|
+
make_derivative(file_set, working_file)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def make_derivative(file_set, working_file)
|
47
|
+
CharacterizeJob.perform_later(file_set, working_file)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [File, ActionDispatch::Http::UploadedFile] file
|
51
|
+
# @param [String] id the identifer of the FileSet
|
52
|
+
# @return [String] path of the working file
|
53
|
+
def copy_file_to_working_directory(file, id)
|
54
|
+
file_name = file.respond_to?(:original_filename) ? file.original_filename : ::File.basename(file)
|
55
|
+
copy_stream_to_working_directory(id, file_name, file)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [ActiveFedora::File] file the resource in the repo
|
59
|
+
# @return [String] path of the working file
|
60
|
+
def copy_repository_resource_to_working_directory(file)
|
61
|
+
copy_stream_to_working_directory(file_set.id, file.original_name, StringIO.new(file.content))
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param [String] id the identifer
|
65
|
+
# @param [String] name the file name
|
66
|
+
# @param [#read] stream the stream to copy to the working directory
|
67
|
+
# @return [String] path of the working file
|
68
|
+
def copy_stream_to_working_directory(id, name, stream)
|
69
|
+
working_path = full_filename(id, name)
|
70
|
+
FileUtils.mkdir_p(File.dirname(working_path))
|
71
|
+
IO.copy_stream(stream, working_path)
|
72
|
+
working_path
|
73
|
+
end
|
74
|
+
|
75
|
+
def full_filename(id, original_name)
|
76
|
+
pair = id.scan(/..?/).first(4)
|
77
|
+
File.join(CurationConcerns.config.working_path, *pair, original_name)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
# Actions are decoupled from controller logic so that they may be called from a controller or a background job.
|
4
|
+
class FileSetActor
|
5
|
+
include CurationConcerns::Lockable
|
6
|
+
|
7
|
+
attr_reader :file_set, :user, :attributes
|
8
|
+
|
9
|
+
def initialize(file_set, user)
|
10
|
+
@file_set = file_set
|
11
|
+
@user = user
|
12
|
+
end
|
13
|
+
|
14
|
+
# Adds the appropriate metadata, visibility and relationships to file_set
|
15
|
+
#
|
16
|
+
# *Note*: In past versions of Sufia this method did not perform a save because it is mainly used in conjunction with
|
17
|
+
# create_content, which also performs a save. However, due to the relationship between Hydra::PCDM objects,
|
18
|
+
# we have to save both the parent work and the file_set in order to record the "metadata" relationship
|
19
|
+
# between them.
|
20
|
+
# @param [ActiveFedora::Base] work the parent work that will contain the file_set.
|
21
|
+
# @param [Hash] file_set specifying the visibility, lease and/or embargo of the file set. If you don't provide at least one of visibility, embargo_release_date or lease_expiration_date, visibility will be copied from the parent.
|
22
|
+
|
23
|
+
def create_metadata(work, file_set_params = {})
|
24
|
+
file_set.apply_depositor_metadata(user)
|
25
|
+
now = CurationConcerns::TimeService.time_in_utc
|
26
|
+
file_set.date_uploaded = now
|
27
|
+
file_set.date_modified = now
|
28
|
+
file_set.creator = [user.user_key]
|
29
|
+
|
30
|
+
Actors::ActorStack.new(file_set, user, [InterpretVisibilityActor]).create(file_set_params) if assign_visibility?(file_set_params)
|
31
|
+
attach_file_to_work(work, file_set, file_set_params) if work
|
32
|
+
yield(file_set) if block_given?
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [File, ActionDigest::HTTP::UploadedFile, Tempfile] file the file uploaded by the user.
|
36
|
+
# @param [String] relation ('original_file')
|
37
|
+
def create_content(file, relation = 'original_file')
|
38
|
+
# If the file set doesn't have a title or label assigned, set a default.
|
39
|
+
file_set.label ||= file.respond_to?(:original_filename) ? file.original_filename : ::File.basename(file)
|
40
|
+
file_set.title = [file_set.label] if file_set.title.blank?
|
41
|
+
|
42
|
+
# Need to save the file_set in order to get an id
|
43
|
+
return false unless file_set.save
|
44
|
+
|
45
|
+
file_actor_class.new(file_set, relation, user).ingest_file(file)
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param [String] revision_id the revision to revert to
|
50
|
+
# @param [String] relation ('original_file')
|
51
|
+
def revert_content(revision_id, relation = 'original_file')
|
52
|
+
file_actor = file_actor_class.new(file_set, relation, user)
|
53
|
+
if file_actor.revert_to(revision_id)
|
54
|
+
CurationConcerns.config.callback.run(:after_revert_content, file_set, user, revision_id)
|
55
|
+
true
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [File, ActionDigest::HTTP::UploadedFile, Tempfile] file the file uploaded by the user.
|
62
|
+
# @param [String] relation ('original_file')
|
63
|
+
def update_content(file, relation = 'original_file')
|
64
|
+
file_actor_class.new(file_set, relation, user).ingest_file(file)
|
65
|
+
CurationConcerns.config.callback.run(:after_update_content, file_set, user)
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_metadata(attributes)
|
70
|
+
stack = Actors::ActorStack.new(file_set,
|
71
|
+
user,
|
72
|
+
[InterpretVisibilityActor, BaseActor])
|
73
|
+
stack.update(attributes)
|
74
|
+
end
|
75
|
+
|
76
|
+
def destroy
|
77
|
+
unlink_from_work
|
78
|
+
file_set.destroy
|
79
|
+
CurationConcerns.config.callback.run(:after_destroy, file_set.id, user)
|
80
|
+
end
|
81
|
+
|
82
|
+
def file_actor_class
|
83
|
+
CurationConcerns::Actors::FileActor
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Takes an optional block and executes the block if the save was successful.
|
89
|
+
# returns false if the save was unsuccessful
|
90
|
+
def save
|
91
|
+
save_tries = 0
|
92
|
+
begin
|
93
|
+
return false unless file_set.save
|
94
|
+
rescue RSolr::Error::Http => error
|
95
|
+
ActiveFedora::Base.logger.warn "CurationConcerns::Actors::FileSetActor#save Caught RSOLR error #{error.inspect}"
|
96
|
+
save_tries += 1
|
97
|
+
# fail for good if the tries is greater than 3
|
98
|
+
raise error if save_tries >= 3
|
99
|
+
sleep 0.01
|
100
|
+
retry
|
101
|
+
end
|
102
|
+
yield if block_given?
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
# Adds a FileSet to the work using ore:Aggregations.
|
107
|
+
# Locks to ensure that only one process is operating on
|
108
|
+
# the list at a time.
|
109
|
+
def attach_file_to_work(work, file_set, file_set_params)
|
110
|
+
acquire_lock_for(work.id) do
|
111
|
+
# Ensure we have an up-to-date copy of the members association, so
|
112
|
+
# that we append to the end of the list.
|
113
|
+
work.reload unless work.new_record?
|
114
|
+
unless assign_visibility?(file_set_params)
|
115
|
+
copy_visibility(work, file_set)
|
116
|
+
end
|
117
|
+
work.ordered_members << file_set
|
118
|
+
set_representative(work, file_set)
|
119
|
+
set_thumbnail(work, file_set)
|
120
|
+
|
121
|
+
# Save the work so the association between the work and the file_set is persisted (head_id)
|
122
|
+
work.save
|
123
|
+
end
|
124
|
+
CurationConcerns.config.callback.run(:after_create_fileset, file_set, user)
|
125
|
+
end
|
126
|
+
|
127
|
+
def assign_visibility?(file_set_params = {})
|
128
|
+
!((file_set_params || {}).keys & %w(visibility embargo_release_date lease_expiration_date)).empty?
|
129
|
+
end
|
130
|
+
|
131
|
+
# copy visibility from source_concern to destination_concern
|
132
|
+
def copy_visibility(source_concern, destination_concern)
|
133
|
+
destination_concern.visibility = source_concern.visibility
|
134
|
+
end
|
135
|
+
|
136
|
+
def set_representative(work, file_set)
|
137
|
+
return unless work.representative_id.blank?
|
138
|
+
work.representative = file_set
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_thumbnail(work, file_set)
|
142
|
+
return unless work.thumbnail_id.blank?
|
143
|
+
work.thumbnail = file_set
|
144
|
+
end
|
145
|
+
|
146
|
+
def unlink_from_work
|
147
|
+
work = file_set.parent
|
148
|
+
return unless work && (work.thumbnail_id == file_set.id || work.representative_id == file_set.id)
|
149
|
+
# This is required to clear the thumbnail_id and representative_id
|
150
|
+
# fields on the work and force it to be re-solrized. Although
|
151
|
+
# ActiveFedora clears the children nodes it leaves the work's
|
152
|
+
# thumbnail_id and representative_id fields in Solr populated.
|
153
|
+
work.thumbnail = nil if work.thumbnail_id == file_set.id
|
154
|
+
work.representative = nil if work.representative_id == file_set.id
|
155
|
+
work.save!
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module CurationConcerns
|
2
|
+
module Actors
|
3
|
+
class InterpretVisibilityActor < AbstractActor
|
4
|
+
class Intention
|
5
|
+
def initialize(attributes)
|
6
|
+
@attributes = attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
# returns a copy of attributes with the necessary params removed
|
10
|
+
# If the lease or embargo is valid, or if they selected something besides lease
|
11
|
+
# or embargo, remove all the params.
|
12
|
+
def sanitize_params
|
13
|
+
if valid_lease?
|
14
|
+
@attributes.except(:visibility,
|
15
|
+
:embargo_release_date,
|
16
|
+
:visibility_during_embargo,
|
17
|
+
:visibility_after_embargo)
|
18
|
+
elsif valid_embargo?
|
19
|
+
@attributes.except(:visibility,
|
20
|
+
:lease_expiration_date,
|
21
|
+
:visibility_during_lease,
|
22
|
+
:visibility_after_lease)
|
23
|
+
elsif !wants_lease? && !wants_embargo?
|
24
|
+
@attributes.except(:lease_expiration_date,
|
25
|
+
:visibility_during_lease,
|
26
|
+
:visibility_after_lease,
|
27
|
+
:embargo_release_date,
|
28
|
+
:visibility_during_embargo,
|
29
|
+
:visibility_after_embargo)
|
30
|
+
else
|
31
|
+
@attributes
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def wants_lease?
|
36
|
+
visibility == Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_LEASE
|
37
|
+
end
|
38
|
+
|
39
|
+
def wants_embargo?
|
40
|
+
visibility == Hydra::AccessControls::AccessRight::VISIBILITY_TEXT_VALUE_EMBARGO
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid_lease?
|
44
|
+
wants_lease? && @attributes[:lease_expiration_date].present?
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_embargo?
|
48
|
+
wants_embargo? && @attributes[:embargo_release_date].present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def lease_params
|
52
|
+
[:lease_expiration_date,
|
53
|
+
:visibility_during_lease,
|
54
|
+
:visibility_after_lease].map { |key| @attributes[key] }
|
55
|
+
end
|
56
|
+
|
57
|
+
def embargo_params
|
58
|
+
[:embargo_release_date,
|
59
|
+
:visibility_during_embargo,
|
60
|
+
:visibility_after_embargo].map { |key| @attributes[key] }
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def visibility
|
66
|
+
@attributes[:visibility]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def create(attributes)
|
71
|
+
@intention = Intention.new(attributes)
|
72
|
+
attributes = @intention.sanitize_params
|
73
|
+
validate && apply_visibility(attributes) && next_actor.create(attributes)
|
74
|
+
end
|
75
|
+
|
76
|
+
def update(attributes)
|
77
|
+
@intention = Intention.new(attributes)
|
78
|
+
attributes = @intention.sanitize_params
|
79
|
+
validate && apply_visibility(attributes) && next_actor.update(attributes)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def validate
|
85
|
+
validate_lease && validate_embargo
|
86
|
+
end
|
87
|
+
|
88
|
+
def apply_visibility(attributes)
|
89
|
+
result = apply_lease && apply_embargo
|
90
|
+
if attributes[:visibility]
|
91
|
+
curation_concern.visibility = attributes[:visibility]
|
92
|
+
end
|
93
|
+
result
|
94
|
+
end
|
95
|
+
|
96
|
+
def validate_lease
|
97
|
+
return true unless @intention.wants_lease? && !@intention.valid_lease?
|
98
|
+
curation_concern.errors.add(:visibility, 'When setting visibility to "lease" you must also specify lease expiration date.')
|
99
|
+
false
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_embargo
|
103
|
+
return true unless @intention.wants_embargo? && !@intention.valid_embargo?
|
104
|
+
curation_concern.errors.add(:visibility, 'When setting visibility to "embargo" you must also specify embargo release date.')
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
# If they want a lease, we can assume it's valid
|
109
|
+
def apply_lease
|
110
|
+
return true unless @intention.wants_lease?
|
111
|
+
curation_concern.apply_lease(*@intention.lease_params)
|
112
|
+
return unless curation_concern.lease
|
113
|
+
curation_concern.lease.save # see https://github.com/projecthydra/hydra-head/issues/226
|
114
|
+
end
|
115
|
+
|
116
|
+
# If they want an embargo, we can assume it's valid
|
117
|
+
def apply_embargo
|
118
|
+
return true unless @intention.wants_embargo?
|
119
|
+
curation_concern.apply_embargo(*@intention.embargo_params)
|
120
|
+
return unless curation_concern.embargo
|
121
|
+
curation_concern.embargo.save # see https://github.com/projecthydra/hydra-head/issues/226
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|