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,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