sufia-models 0.0.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 (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