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,11 @@
1
+ require "sufia/models/version"
2
+ require "sufia/models/engine"
3
+
4
+ module Sufia
5
+ module Models
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :Resque, 'models/queue/resque'
9
+ autoload :User, 'models/user'
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveFedora
2
+ class UnsavedDigitalObject
3
+ def assign_pid
4
+ @pid ||= Sufia::IdService.mint
5
+ end
6
+ end
7
+
8
+ class Base
9
+ def stream
10
+ Nest.new(self.class.name, $redis)[to_param]
11
+ rescue
12
+ nil
13
+ end
14
+
15
+ def self.stream
16
+ Nest.new(name, $redis)
17
+ rescue
18
+ nil
19
+ end
20
+
21
+ def events(size=-1)
22
+ stream[:event].lrange(0, size).map do |event_id|
23
+ {
24
+ action: $redis.hget("events:#{event_id}", "action"),
25
+ timestamp: $redis.hget("events:#{event_id}", "timestamp")
26
+ }
27
+ end
28
+ rescue
29
+ []
30
+ end
31
+
32
+ def create_event(action, timestamp)
33
+ event_id = $redis.incr("events:latest_id")
34
+ $redis.hmset("events:#{event_id}", "action", action, "timestamp", timestamp)
35
+ event_id
36
+ rescue
37
+ nil
38
+ end
39
+
40
+ def log_event(event_id)
41
+ stream[:event].lpush(event_id)
42
+ rescue
43
+ nil
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+
@@ -0,0 +1,56 @@
1
+ ActiveRecord::Base.class_eval do
2
+ def stream
3
+ Nest.new(self.class.name, $redis)[to_param]
4
+ rescue
5
+ nil
6
+ end
7
+
8
+ def self.stream
9
+ Nest.new(name, $redis)
10
+ rescue
11
+ nil
12
+ end
13
+
14
+ def events(size=-1)
15
+ stream[:event].lrange(0, size).map do |event_id|
16
+ {
17
+ action: $redis.hget("events:#{event_id}", "action"),
18
+ timestamp: $redis.hget("events:#{event_id}", "timestamp")
19
+ }
20
+ end
21
+ rescue
22
+ []
23
+ end
24
+
25
+ def profile_events(size=-1)
26
+ stream[:event][:profile].lrange(0, size).map do |event_id|
27
+ {
28
+ action: $redis.hget("events:#{event_id}", "action"),
29
+ timestamp: $redis.hget("events:#{event_id}", "timestamp")
30
+ }
31
+ end
32
+ rescue
33
+ []
34
+ end
35
+
36
+ def create_event(action, timestamp)
37
+ event_id = $redis.incr("events:latest_id")
38
+ $redis.hmset("events:#{event_id}", "action", action, "timestamp", timestamp)
39
+ event_id
40
+ rescue
41
+ nil
42
+ end
43
+
44
+ def log_event(event_id)
45
+ stream[:event].lpush(event_id)
46
+ rescue
47
+ nil
48
+ end
49
+
50
+ def log_profile_event(event_id)
51
+ stream[:event][:profile].lpush(event_id)
52
+ rescue
53
+ nil
54
+ end
55
+ end
56
+
@@ -0,0 +1,34 @@
1
+ module Curate
2
+ module Models
3
+ class Engine < ::Rails::Engine
4
+ config.autoload_paths += %W(
5
+ #{config.root}/lib/sufia/models/jobs
6
+ )
7
+ initializer "Patch active_fedora" do
8
+ require 'sufia/models/active_fedora/redis'
9
+ end
10
+
11
+ initializer "Patch active_record" do
12
+ require 'sufia/models/active_record/redis'
13
+ end
14
+
15
+ initializer 'requires' do
16
+ require 'sufia/models/model_methods'
17
+ require 'sufia/models/noid'
18
+ require 'sufia/models/file_content'
19
+ require 'sufia/models/file_content/extract_metadata'
20
+ require 'sufia/models/file_content/versions'
21
+ require 'sufia/models/generic_file/actions'
22
+ require 'sufia/models/generic_file/audit'
23
+ require 'sufia/models/generic_file/characterization'
24
+ require 'sufia/models/generic_file/export'
25
+ require 'sufia/models/generic_file/permissions'
26
+ require 'sufia/models/generic_file/thumbnail'
27
+ require 'sufia/models/generic_file'
28
+ require 'sufia/models/user'
29
+ require 'sufia/models/id_service'
30
+ require 'sufia/models/solr_document_behavior'
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ module Sufia
2
+ module FileContent
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :ExtractMetadata, 'sufia/models/file_content/extract_metadata'
6
+ autoload :Versions, 'sufia/models/file_content/versions'
7
+
8
+ end
9
+ end
@@ -0,0 +1,60 @@
1
+ require 'open3'
2
+ module Sufia
3
+ module FileContent
4
+ module ExtractMetadata
5
+ include Open3
6
+
7
+ def extract_metadata
8
+ out = nil
9
+ to_tempfile do |f|
10
+ out = run_fits!(f.path)
11
+ end
12
+ out
13
+ end
14
+
15
+ def to_tempfile &block
16
+ return unless has_content?
17
+ tmp_base = Sufia::Engine.config.temp_file_base
18
+ f = Tempfile.new("#{pid}-#{dsVersionID}")
19
+ f.binmode
20
+ if content.respond_to? :read
21
+ f.write(content.read)
22
+ else
23
+ f.write(content)
24
+ end
25
+ f.close
26
+ content.rewind if content.respond_to? :rewind
27
+ yield(f)
28
+ f.unlink
29
+ end
30
+
31
+ # Return true if the content is present
32
+ # You can override this method if your content is an external datastream
33
+ def has_content?
34
+ !content.nil?
35
+ end
36
+
37
+ private
38
+
39
+
40
+ def run_fits!(file_path)
41
+ command = "#{fits_path} -i \"#{file_path}\""
42
+ stdin, stdout, stderr, wait_thr = popen3(command)
43
+ stdin.close
44
+ out = stdout.read
45
+ stdout.close
46
+ err = stderr.read
47
+ stderr.close
48
+ exit_status = wait_thr.value
49
+ raise "Unable to execute command \"#{command}\"\n#{err}" unless exit_status.success?
50
+ out
51
+ end
52
+
53
+
54
+ def fits_path
55
+ Sufia::Engine.config.fits_path
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,23 @@
1
+ module Sufia
2
+ module FileContent
3
+ module Versions
4
+ def get_version(version_id)
5
+ self.versions.select { |v| v.versionID == version_id}.first
6
+ end
7
+
8
+ def latest_version
9
+ self.versions.first
10
+ end
11
+
12
+ def version_committer(version)
13
+ vc = VersionCommitter.where(:obj_id => version.pid,
14
+ :datastream_id => version.dsid,
15
+ :version_id => version.versionID)
16
+ return vc.empty? ? nil : vc.first.committer_login
17
+ end
18
+
19
+
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,183 @@
1
+ require 'datastreams/generic_file_rdf_datastream'
2
+ require 'datastreams/properties_datastream'
3
+ require 'datastreams/file_content_datastream'
4
+
5
+ module Sufia
6
+ module GenericFile
7
+ extend ActiveSupport::Concern
8
+ extend ActiveSupport::Autoload
9
+ include Sufia::ModelMethods
10
+ include Sufia::Noid
11
+ include Sufia::GenericFile::Thumbnail
12
+ include Sufia::GenericFile::Export
13
+ include Sufia::GenericFile::Characterization
14
+ include Sufia::GenericFile::Audit
15
+ include Sufia::GenericFile::Permissions
16
+
17
+ included do
18
+ has_metadata :name => "descMetadata", :type => GenericFileRdfDatastream
19
+ has_metadata :name => "properties", :type => PropertiesDatastream
20
+ has_file_datastream :name => "content", :type => FileContentDatastream
21
+ has_file_datastream :name => "thumbnail"
22
+
23
+ belongs_to :batch, :property => :is_part_of
24
+
25
+ delegate_to :properties, [:relative_path, :depositor], :unique => true
26
+ delegate_to :descMetadata, [:date_uploaded, :date_modified], :unique => true
27
+ delegate_to :descMetadata, [:related_url, :based_near, :part_of, :creator,
28
+ :contributor, :title, :tag, :description, :rights,
29
+ :publisher, :date_created, :subject,
30
+ :resource_type, :identifier, :language]
31
+
32
+ around_save :characterize_if_changed, :retry_warming
33
+ before_save :remove_blank_assertions
34
+ before_destroy :cleanup_trophies
35
+ end
36
+
37
+ def remove_blank_assertions
38
+ terms_for_editing.each do |key|
39
+ self[key] = nil if self[key] == ['']
40
+ end
41
+ end
42
+
43
+
44
+ def record_version_committer(user)
45
+ version = content.latest_version
46
+ # content datastream not (yet?) present
47
+ return if version.nil?
48
+ VersionCommitter.create(:obj_id => version.pid,
49
+ :datastream_id => version.dsid,
50
+ :version_id => version.versionID,
51
+ :committer_login => user.user_key)
52
+ end
53
+
54
+ def pdf?
55
+ ['application/pdf'].include? self.mime_type
56
+ end
57
+
58
+ def image?
59
+ ['image/png','image/jpeg', 'image/jpg', 'image/jp2', 'image/bmp', 'image/gif'].include? self.mime_type
60
+ end
61
+
62
+ def video?
63
+ ['video/mpeg', 'video/mp4', 'video/webm', 'video/x-msvideo', 'video/avi', 'video/quicktime', 'application/mxf'].include? self.mime_type
64
+ end
65
+
66
+ def audio?
67
+ # audio/x-wave is the mime type that fits 0.6.0 returns for a wav file.
68
+ # audio/mpeg is the mime type that fits 0.6.0 returns for an mp3 file.
69
+ ['audio/mp3', 'audio/mpeg', 'audio/x-wave', 'audio/x-wav', 'audio/ogg'].include? self.mime_type
70
+ end
71
+
72
+ def persistent_url
73
+ "#{Sufia::Engine.config.persistent_hostpath}#{noid}"
74
+ end
75
+
76
+ def retry_warming
77
+ save_tries = 0
78
+ conflict_tries = 0
79
+ begin
80
+ yield
81
+ rescue RSolr::Error::Http => error
82
+ save_tries += 1
83
+ logger.warn "Retry Solr caught RSOLR error on #{self.pid}: #{error.inspect}"
84
+ # fail for good if the tries is greater than 3
85
+ raise if save_tries >=3
86
+ sleep 0.01
87
+ retry
88
+ rescue ActiveResource::ResourceConflict => error
89
+ conflict_tries += 1
90
+ logger.warn "Retry caught Active Resource Conflict #{self.pid}: #{error.inspect}"
91
+ raise if conflict_tries >=10
92
+ sleep 0.01
93
+ retry
94
+ rescue =>error
95
+ if (error.to_s.downcase.include? "conflict")
96
+ conflict_tries += 1
97
+ logger.warn "Retry caught Active Resource Conflict #{self.pid}: #{error.inspect}"
98
+ raise if conflict_tries >=10
99
+ sleep 0.01
100
+ retry
101
+ else
102
+ raise
103
+ end
104
+ end
105
+ end
106
+
107
+ def cleanup_trophies
108
+ Trophy.destroy_all(generic_file_id: self.noid)
109
+ end
110
+
111
+ def related_files
112
+ relateds = begin
113
+ self.batch.generic_files
114
+ rescue NoMethodError => e
115
+ #batch is nil - When would this ever happen?
116
+ batch_id = self.object_relations["isPartOf"].first || self.object_relations[:is_part_of].first
117
+ return [] if batch_id.nil?
118
+ self.class.find(Solrizer.solr_name('is_part_of', :symbol) => batch_id)
119
+ end
120
+ relateds.reject { |gf| gf.pid == self.pid }
121
+ end
122
+
123
+ # Unstemmed, searchable, stored
124
+ def self.noid_indexer
125
+ @noid_indexer ||= Solrizer::Descriptor.new(:text, :indexed, :stored)
126
+ end
127
+
128
+ def to_solr(solr_doc={}, opts={})
129
+ super(solr_doc, opts)
130
+ solr_doc[Solrizer.solr_name('label')] = self.label
131
+ solr_doc[Solrizer.solr_name('noid', Sufia::GenericFile.noid_indexer)] = noid
132
+ solr_doc[Solrizer.solr_name('file_format')] = file_format
133
+ solr_doc[Solrizer.solr_name('file_format', :facetable)] = file_format
134
+ return solr_doc
135
+ end
136
+
137
+ def file_format
138
+ return nil if self.mime_type.blank? and self.format_label.blank?
139
+ return self.mime_type.split('/')[1]+ " ("+self.format_label.join(", ")+")" unless self.mime_type.blank? or self.format_label.blank?
140
+ return self.mime_type.split('/')[1] unless self.mime_type.blank?
141
+ return self.format_label
142
+ end
143
+
144
+ # Redefine this for more intuitive keys in Redis
145
+ def to_param
146
+ noid
147
+ end
148
+
149
+ def label=(new_label)
150
+ @inner_object.label = new_label
151
+ if self.title.empty?
152
+ self.title = new_label
153
+ end
154
+ end
155
+
156
+ def to_jq_upload
157
+ return {
158
+ "name" => self.title,
159
+ "size" => self.file_size,
160
+ "url" => "/files/#{noid}",
161
+ "thumbnail_url" => self.pid,
162
+ "delete_url" => "deleteme", # generic_file_path(:id => id),
163
+ "delete_type" => "DELETE"
164
+ }
165
+ end
166
+
167
+ def terms_for_editing
168
+ terms_for_display -
169
+ [:part_of, :date_modified, :date_uploaded, :format] #, :resource_type]
170
+ end
171
+
172
+ def terms_for_display
173
+ self.descMetadata.class.config.keys
174
+ end
175
+
176
+ # Is this file in the middle of being processed by a batch?
177
+ def processing?
178
+ return false if self.batch.blank?
179
+ return false if !self.batch.methods.include? :status
180
+ return (!self.batch.status.empty?) && (self.batch.status.count == 1) && (self.batch.status[0] == "processing")
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,39 @@
1
+ module Sufia::GenericFile
2
+ # Actions are decoupled from controller logic so that they may be called from a controller or a background job.
3
+ module Actions
4
+ def self.create_metadata(generic_file, user, batch_id)
5
+
6
+ generic_file.apply_depositor_metadata(user.user_key)
7
+ generic_file.date_uploaded = Date.today
8
+ generic_file.date_modified = Date.today
9
+ generic_file.creator = user.name
10
+
11
+ if batch_id
12
+ generic_file.add_relationship("isPartOf", "info:fedora/#{Sufia::Noid.namespaceize(batch_id)}")
13
+ else
14
+ logger.warn "unable to find batch to attach to"
15
+ end
16
+ generic_file.save!
17
+ end
18
+
19
+ def self.create_content(generic_file, file, file_name, dsid, user)
20
+ generic_file.add_file(file, dsid, file_name)
21
+
22
+ save_tries = 0
23
+ begin
24
+ generic_file.save!
25
+ rescue RSolr::Error::Http => error
26
+ logger.warn "GenericFilesController::create_and_save_generic_file Caught RSOLR error #{error.inspect}"
27
+ save_tries+=1
28
+ # fail for good if the tries is greater than 3
29
+ raise error if save_tries >=3
30
+ sleep 0.01
31
+ retry
32
+ end
33
+
34
+ generic_file.record_version_committer(user)
35
+ Sufia.queue.push(UnzipJob.new(generic_file.pid)) if generic_file.content.mimeType == 'application/zip'
36
+ Sufia.queue.push(ContentDepositEventJob.new(generic_file.pid, user.user_key))
37
+ end
38
+ end
39
+ end