sufia-models 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.md +177 -0
  5. data/README.md +29 -0
  6. data/Rakefile +1 -0
  7. data/app/models/batch.rb +46 -0
  8. data/app/models/checksum_audit_log.rb +35 -0
  9. data/app/models/contact_form.rb +42 -0
  10. data/app/models/datastreams/batch_rdf_datastream.rb +23 -0
  11. data/app/models/datastreams/file_content_datastream.rb +18 -0
  12. data/app/models/datastreams/fits_datastream.rb +188 -0
  13. data/app/models/datastreams/generic_file_rdf_datastream.rb +75 -0
  14. data/app/models/datastreams/paranoid_rights_datastream.rb +37 -0
  15. data/app/models/datastreams/properties_datastream.rb +33 -0
  16. data/app/models/domain_term.rb +18 -0
  17. data/app/models/follow.rb +28 -0
  18. data/app/models/generic_file.rb +16 -0
  19. data/app/models/geo_names_resource.rb +34 -0
  20. data/app/models/group.rb +8 -0
  21. data/app/models/local_authority.rb +93 -0
  22. data/app/models/local_authority_entry.rb +18 -0
  23. data/app/models/single_use_link.rb +26 -0
  24. data/app/models/subject_local_authority_entry.rb +16 -0
  25. data/app/models/trophy.rb +12 -0
  26. data/app/models/version_committer.rb +17 -0
  27. data/lib/sufia/models.rb +11 -0
  28. data/lib/sufia/models/active_fedora/redis.rb +49 -0
  29. data/lib/sufia/models/active_record/redis.rb +56 -0
  30. data/lib/sufia/models/engine.rb +34 -0
  31. data/lib/sufia/models/file_content.rb +9 -0
  32. data/lib/sufia/models/file_content/extract_metadata.rb +60 -0
  33. data/lib/sufia/models/file_content/versions.rb +23 -0
  34. data/lib/sufia/models/generic_file.rb +183 -0
  35. data/lib/sufia/models/generic_file/actions.rb +39 -0
  36. data/lib/sufia/models/generic_file/audit.rb +119 -0
  37. data/lib/sufia/models/generic_file/characterization.rb +81 -0
  38. data/lib/sufia/models/generic_file/export.rb +339 -0
  39. data/lib/sufia/models/generic_file/permissions.rb +64 -0
  40. data/lib/sufia/models/generic_file/thumbnail.rb +91 -0
  41. data/lib/sufia/models/id_service.rb +57 -0
  42. data/lib/sufia/models/jobs/audit_job.rb +65 -0
  43. data/lib/sufia/models/jobs/batch_update_job.rb +86 -0
  44. data/lib/sufia/models/jobs/characterize_job.rb +43 -0
  45. data/lib/sufia/models/jobs/content_delete_event_job.rb +31 -0
  46. data/lib/sufia/models/jobs/content_deposit_event_job.rb +32 -0
  47. data/lib/sufia/models/jobs/content_new_version_event_job.rb +32 -0
  48. data/lib/sufia/models/jobs/content_restored_version_event_job.rb +40 -0
  49. data/lib/sufia/models/jobs/content_update_event_job.rb +32 -0
  50. data/lib/sufia/models/jobs/event_job.rb +33 -0
  51. data/lib/sufia/models/jobs/ffmpeg_transcode_job.rb +61 -0
  52. data/lib/sufia/models/jobs/resolrize_job.rb +23 -0
  53. data/lib/sufia/models/jobs/transcode_audio_job.rb +40 -0
  54. data/lib/sufia/models/jobs/transcode_video_job.rb +39 -0
  55. data/lib/sufia/models/jobs/unzip_job.rb +54 -0
  56. data/lib/sufia/models/jobs/user_edit_profile_event_job.rb +35 -0
  57. data/lib/sufia/models/jobs/user_follow_event_job.rb +37 -0
  58. data/lib/sufia/models/jobs/user_unfollow_event_job.rb +38 -0
  59. data/lib/sufia/models/model_methods.rb +39 -0
  60. data/lib/sufia/models/noid.rb +42 -0
  61. data/lib/sufia/models/solr_document_behavior.rb +125 -0
  62. data/lib/sufia/models/user.rb +126 -0
  63. data/lib/sufia/models/utils.rb +36 -0
  64. data/lib/sufia/models/version.rb +5 -0
  65. data/lib/tasks/sufia-models_tasks.rake +4 -0
  66. data/sufia-models.gemspec +28 -0
  67. metadata +151 -0
@@ -0,0 +1,64 @@
1
+ require 'datastreams/paranoid_rights_datastream'
2
+ module Sufia
3
+ module GenericFile
4
+ module Permissions
5
+ extend ActiveSupport::Concern
6
+ #we're overriding the permissions= method which is in RightsMetadata
7
+ include Hydra::ModelMixins::RightsMetadata
8
+ included do
9
+ has_metadata :name => "rightsMetadata", :type => ParanoidRightsDatastream
10
+ validate :paranoid_permissions
11
+ end
12
+
13
+ def set_visibility(visibility)
14
+ # only set explicit permissions
15
+ case visibility
16
+ when "open"
17
+ self.datastreams["rightsMetadata"].permissions({:group=>"public"}, "read")
18
+ when "psu"
19
+ self.datastreams["rightsMetadata"].permissions({:group=>"registered"}, "read")
20
+ self.datastreams["rightsMetadata"].permissions({:group=>"public"}, "none")
21
+ when "restricted"
22
+ self.datastreams["rightsMetadata"].permissions({:group=>"registered"}, "none")
23
+ self.datastreams["rightsMetadata"].permissions({:group=>"public"}, "none")
24
+ end
25
+ end
26
+
27
+
28
+ def paranoid_permissions
29
+ # let the rightsMetadata ds make this determination
30
+ # - the object instance is passed in for easier access to the props ds
31
+ rightsMetadata.validate(self)
32
+ end
33
+
34
+ ## Updates those permissions that are provided to it. Does not replace any permissions unless they are provided
35
+ def permissions=(params)
36
+ perm_hash = permission_hash
37
+ params[:new_user_name].each { |name, access| perm_hash['person'][name] = access } if params[:new_user_name].present?
38
+ params[:new_group_name].each { |name, access| perm_hash['group'][name] = access } if params[:new_group_name].present?
39
+
40
+ params[:user].each { |name, access| perm_hash['person'][name] = access} if params[:user]
41
+ params[:group].each { |name, access| perm_hash['group'][name] = access} if params[:group]
42
+ rightsMetadata.update_permissions(perm_hash)
43
+ end
44
+
45
+ private
46
+
47
+ def permission_hash
48
+ old_perms = self.permissions
49
+ user_perms = {}
50
+ old_perms.select{|r| r[:type] == 'user'}.each do |r|
51
+ user_perms[r[:name]] = r[:access]
52
+ end
53
+ user_perms
54
+ group_perms = {}
55
+ old_perms.select{|r| r[:type] == 'group'}.each do |r|
56
+ group_perms[r[:name]] = r[:access]
57
+ end
58
+ {'person'=>user_perms, 'group'=>group_perms}
59
+ end
60
+
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,91 @@
1
+ module Sufia
2
+ module GenericFile
3
+ module Thumbnail
4
+ # Create thumbnail requires that the characterization has already been run (so mime_type, width and height is available)
5
+ # and that the object is already has a pid set
6
+ def create_thumbnail
7
+ return unless self.content.has_content?
8
+ if pdf?
9
+ create_pdf_thumbnail
10
+ elsif image?
11
+ create_image_thumbnail
12
+ elsif video?
13
+ create_video_thumbnail
14
+ end
15
+ end
16
+
17
+ protected
18
+ def create_video_thumbnail
19
+ return unless Sufia::Engine.config.enable_ffmpeg
20
+
21
+ output_file = Dir::Tmpname.create(['sufia', ".png"], Sufia::Engine.config.temp_file_base){}
22
+ content.to_tempfile do |f|
23
+ # we could use something like this in order to find a frame in the middle.
24
+ #ffprobe -show_files video.avi 2> /dev/null | grep duration | cut -d= -f2 53.399999
25
+ command = "#{Sufia::Engine.config.ffmpeg_path} -i \"#{f.path}\" -loglevel quiet -vf \"scale=338:-1\" -r 1 -t 1 #{output_file}"
26
+ system(command)
27
+ raise "Unable to execute command \"#{command}\"" unless $?.success?
28
+ end
29
+
30
+ self.thumbnail.content = File.open(output_file, 'rb').read
31
+ self.thumbnail.mimeType = 'image/png'
32
+ self.save
33
+ end
34
+
35
+ def create_pdf_thumbnail
36
+ retryCnt = 0
37
+ stat = false;
38
+ for retryCnt in 1..3
39
+ begin
40
+ pdf = load_image_transformer
41
+ first = pdf.to_a[0]
42
+ first.format = "PNG"
43
+ thumb = first.scale(338, 493)
44
+ self.thumbnail.content = thumb.to_blob { self.format = "PNG" }
45
+ self.thumbnail.mimeType = 'image/png'
46
+ self.save
47
+ break
48
+ rescue => e
49
+ logger.warn "Rescued an error #{e.inspect} retry count = #{retryCnt}"
50
+ sleep 1
51
+ end
52
+ end
53
+ return stat
54
+ end
55
+
56
+ def create_image_thumbnail
57
+ self.thumbnail.content = scale_image.to_blob { self.format = "PNG" }
58
+ self.thumbnail.mimeType = 'image/png'
59
+ #logger.debug "Has the content before saving? #{self.content.changed?}"
60
+ self.save
61
+ end
62
+
63
+ def scale_image
64
+ img = load_image_transformer
65
+ height = Float(self.height.first.to_i)
66
+ width = Float(self.width.first.to_i)
67
+ if width > height && width > 150 && height > 105
68
+ # horizontal img
69
+ scale = 150 / width
70
+ img.scale(150, height * scale)
71
+ elsif height >= width && width > 150 && height > 200
72
+ # vertical or square
73
+ scale = 200 / height
74
+ img.scale(width*scale, 200)
75
+ else
76
+ # Too small to worry about resizing
77
+ img
78
+ end
79
+ end
80
+
81
+ # Override this method if you want a different transformer, or need to load the
82
+ # raw image from a different source (e.g. external datastream)
83
+ def load_image_transformer
84
+ xformer = Magick::ImageList.new
85
+ xformer.from_blob(content.content)
86
+ xformer
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright © 2012 The Pennsylvania State University
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'noid'
16
+
17
+ module Sufia
18
+ module IdService
19
+ @minter = ::Noid::Minter.new(:template => '.reeddeeddk')
20
+ @pid = $$
21
+ @namespace = Sufia::Engine.config.id_namespace
22
+ @semaphore = Mutex.new
23
+ def self.valid?(identifier)
24
+ # remove the fedora namespace since it's not part of the noid
25
+ noid = identifier.split(":").last
26
+ return @minter.valid? noid
27
+ end
28
+ def self.mint
29
+ @semaphore.synchronize do
30
+ while true
31
+ pid = self.next_id
32
+ return pid unless ActiveFedora::Base.exists?(pid)
33
+ end
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def self.next_id
40
+ pid = ''
41
+ File.open("tmp/minter-state", File::RDWR|File::CREAT, 0644) {|f|
42
+ f.flock(File::LOCK_EX)
43
+ yaml = YAML::load(f.read)
44
+ yaml = {:template => '.reeddeeddk'} unless yaml
45
+ minter = ::Noid::Minter.new(yaml)
46
+ pid = "#{@namespace}:#{minter.mint}"
47
+ f.rewind
48
+ yaml = YAML::dump(minter.dump)
49
+ f.write yaml
50
+ f.flush
51
+ f.truncate(f.pos)
52
+ }
53
+ return pid
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,65 @@
1
+ # Copyright © 2012 The Pennsylvania State University
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class AuditJob
16
+ def queue_name
17
+ :audit
18
+ end
19
+
20
+ PASS = 'Passing Audit Run'
21
+ FAIL = 'Failing Audit Run'
22
+
23
+ attr_accessor :generic_file_id, :datastream_id, :version_id
24
+
25
+ def initialize(generic_file_id, datastream_id, version_id)
26
+ self.generic_file_id = generic_file_id
27
+ self.datastream_id = datastream_id
28
+ self.version_id = version_id
29
+ end
30
+
31
+ def run
32
+ generic_file = GenericFile.find(generic_file_id)
33
+ #logger.info "GF is #{generic_file.pid}"
34
+ if generic_file
35
+ datastream = generic_file.datastreams[datastream_id]
36
+ #logger.info "DS is #{datastream.inspect}"
37
+ if datastream
38
+ #logger.info "Datastream for audit = #{datastream.inspect}"
39
+ version = datastream.versions.select { |v| v.versionID == version_id}.first
40
+ log = GenericFile.run_audit(version)
41
+
42
+ # look up the user for sending the message to
43
+ login = generic_file.depositor
44
+ #logger.info "User login is #{login}"`
45
+ #logger.info "All users = #{User.all}"
46
+ if login
47
+ user = User.find_by_user_key(login)
48
+ logger.warn "User '#{login}' not found" unless user
49
+ #logger.info "ZZZ user = #{user.inspect}"
50
+ job_user = User.audituser()
51
+ #send the user a message about the failing audit
52
+ unless (log.pass == 1)
53
+ message = "The audit run at #{log.created_at} for #{log.pid}:#{log.dsid}:#{log.version} was #{log.pass == 1 ? 'passing' : 'failing'}."
54
+ subject = (log.pass == 1 ? PASS : FAIL)
55
+ job_user.send_message(user, message, subject)
56
+ end
57
+ end
58
+ else
59
+ logger.warn "No datastream for audit!!!!! pid: #{generic_file_id} dsid: #{datastream_id}"
60
+ end
61
+ else
62
+ logger.warn "No generic file for data stream audit!!!!! pid: #{generic_file_id} dsid: #{datastream_id}"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,86 @@
1
+ # Copyright © 2012 The Pennsylvania State University
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class BatchUpdateJob
16
+ include Hydra::PermissionsQuery
17
+ include GenericFileHelper
18
+ include Rails.application.routes.url_helpers
19
+
20
+ def queue_name
21
+ :batch_update
22
+ end
23
+
24
+ attr_accessor :login, :title, :file_attributes, :batch_id, :visibility
25
+
26
+ def initialize(login, params)
27
+ self.login = login
28
+ self.title = params[:title]
29
+ self.file_attributes = params[:generic_file]
30
+ self.visibility = params[:visibility]
31
+ self.batch_id = params[:id]
32
+
33
+ end
34
+
35
+ def run
36
+ batch = Batch.find_or_create(self.batch_id)
37
+ user = User.find_by_user_key(self.login)
38
+ @saved = []
39
+ @denied = []
40
+
41
+ batch.generic_files.each do |gf|
42
+ update_file(gf, user)
43
+ end
44
+ batch.update_attributes({status:["Complete"]})
45
+
46
+ job_user = User.batchuser()
47
+
48
+ message = '<a class="batchid ui-helper-hidden">ss-'+batch.noid+'</a>The file(s) '+ file_list(@saved)+ " have been saved." unless @saved.empty?
49
+ job_user.send_message(user, message, 'Batch upload complete') unless @saved.empty?
50
+
51
+ message = '<a class="batchid ui-helper-hidden">'+batch.noid+'</a>The file(s) '+ file_list(@denied)+" could not be updated. You do not have sufficient privileges to edit it." unless @denied.empty?
52
+ job_user.send_message(user, message, 'Batch upload permission denied') unless @denied.empty?
53
+
54
+ end
55
+
56
+ def update_file(gf, user)
57
+ unless user.can? :edit, gf
58
+ logger.error "User #{user.user_key} DENIED access to #{gf.pid}!"
59
+ @denied << gf
60
+ return
61
+ end
62
+ gf.title = title[gf.pid] if title[gf.pid] rescue gf.label
63
+ gf.attributes=file_attributes
64
+ gf.set_visibility(visibility)
65
+
66
+ save_tries = 0
67
+ begin
68
+ gf.save!
69
+ rescue RSolr::Error::Http => error
70
+ save_tries += 1
71
+ logger.warn "BatchUpdateJob caught RSOLR error on #{gf.pid}: #{error.inspect}"
72
+ # fail for good if the tries is greater than 3
73
+ raise error if save_tries >=3
74
+ sleep 0.01
75
+ retry
76
+ end #
77
+ Sufia.queue.push(ContentUpdateEventJob.new(gf.pid, login))
78
+ @saved << gf
79
+ end
80
+
81
+ def file_list ( files)
82
+ return files.map {|gf| '<a href="'+Sufia::Engine.routes.url_helpers.generic_files_path+'/'+gf.noid+'">'+display_title(gf)+'</a>'}.join(', ')
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,43 @@
1
+ # Copyright © 2012 The Pennsylvania State University
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class CharacterizeJob
16
+
17
+ def queue_name
18
+ :characterize
19
+ end
20
+
21
+ attr_accessor :generic_file_id, :generic_file
22
+
23
+ def initialize(generic_file_id)
24
+ self.generic_file_id = generic_file_id
25
+ end
26
+
27
+ def run
28
+ self.generic_file = GenericFile.find(generic_file_id)
29
+ generic_file.characterize
30
+ after_characterize
31
+ end
32
+
33
+ def after_characterize
34
+ if generic_file.pdf? || generic_file.image? || generic_file.video?
35
+ generic_file.create_thumbnail
36
+ end
37
+ if generic_file.video?
38
+ Sufia.queue.push(TranscodeVideoJob.new(generic_file_id, 'content'))
39
+ elsif generic_file.audio?
40
+ Sufia.queue.push(TranscodeAudioJob.new(generic_file_id, 'content'))
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright © 2012 The Pennsylvania State University
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class ContentDeleteEventJob < EventJob
16
+
17
+
18
+ def run
19
+ action = "User #{link_to_profile depositor_id} has deleted file '#{generic_file_id}'"
20
+ timestamp = Time.now.to_i
21
+ depositor = User.find_by_user_key(depositor_id)
22
+ # Create the event
23
+ event = depositor.create_event(action, timestamp)
24
+ # Log the event to the depositor's profile stream
25
+ depositor.log_profile_event(event)
26
+ # Fan out the event to all followers
27
+ depositor.followers.each do |follower|
28
+ follower.log_event(event)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright © 2012 The Pennsylvania State University
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class ContentDepositEventJob < EventJob
16
+ def run
17
+ gf = GenericFile.find(generic_file_id)
18
+ action = "User #{link_to_profile depositor_id} has deposited #{link_to gf.title.first, Sufia::Engine.routes.url_helpers.generic_file_path(gf.noid)}"
19
+ timestamp = Time.now.to_i
20
+ depositor = User.find_by_user_key(depositor_id)
21
+ # Create the event
22
+ event = depositor.create_event(action, timestamp)
23
+ # Log the event to the depositor's profile stream
24
+ depositor.log_profile_event(event)
25
+ # Log the event to the GF's stream
26
+ gf.log_event(event)
27
+ # Fan out the event to all followers who have access
28
+ depositor.followers.select { |user| user.can? :read, gf }.each do |follower|
29
+ follower.log_event(event)
30
+ end
31
+ end
32
+ end