ddr-models 2.0.1 → 2.1.0.rc1

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -17
  3. data/app/models/collection.rb +1 -35
  4. data/ddr-models.gemspec +2 -1
  5. data/lib/ddr/actions.rb +1 -0
  6. data/lib/ddr/actions/virus_check.rb +28 -0
  7. data/lib/ddr/auth.rb +4 -0
  8. data/lib/ddr/auth/ability_definitions/datastream_ability_definitions.rb +7 -5
  9. data/lib/ddr/auth/grouper_gateway.rb +9 -1
  10. data/lib/ddr/auth/permissions.rb +2 -1
  11. data/lib/ddr/auth/role_based_access_controls_enforcement.rb +5 -5
  12. data/lib/ddr/auth/roles/role_types.rb +2 -1
  13. data/lib/ddr/datastreams.rb +2 -2
  14. data/lib/ddr/datastreams/administrative_metadata_datastream.rb +27 -14
  15. data/lib/ddr/datastreams/datastream_behavior.rb +13 -0
  16. data/lib/ddr/datastreams/fits_datastream.rb +88 -0
  17. data/lib/ddr/derivatives/png_generator.rb +2 -0
  18. data/lib/ddr/derivatives/ptif_generator.rb +2 -0
  19. data/lib/ddr/events/fixity_check_event.rb +2 -2
  20. data/lib/ddr/events/virus_check_event.rb +2 -14
  21. data/lib/ddr/index.rb +29 -0
  22. data/lib/ddr/index/abstract_query_result.rb +23 -0
  23. data/lib/ddr/index/connection.rb +17 -0
  24. data/lib/ddr/index/csv_query_result.rb +61 -0
  25. data/lib/ddr/index/document_builder.rb +9 -0
  26. data/lib/ddr/index/field.rb +23 -0
  27. data/lib/ddr/index/fields.rb +83 -0
  28. data/lib/ddr/index/filter.rb +48 -0
  29. data/lib/ddr/index/filters.rb +19 -0
  30. data/lib/ddr/index/legacy_license_fields.rb +14 -0
  31. data/lib/ddr/index/query.rb +35 -0
  32. data/lib/ddr/index/query_builder.rb +74 -0
  33. data/lib/ddr/index/query_clause.rb +52 -0
  34. data/lib/ddr/index/query_result.rb +70 -0
  35. data/lib/ddr/index/query_value.rb +16 -0
  36. data/lib/ddr/index/response.rb +13 -0
  37. data/lib/ddr/index/unique_key_field.rb +12 -0
  38. data/lib/ddr/index_fields.rb +7 -53
  39. data/lib/ddr/jobs.rb +1 -1
  40. data/lib/ddr/jobs/fits_file_characterization.rb +51 -0
  41. data/lib/ddr/managers.rb +1 -0
  42. data/lib/ddr/managers/technical_metadata_manager.rb +104 -0
  43. data/lib/ddr/models.rb +39 -23
  44. data/lib/ddr/models/base.rb +0 -2
  45. data/lib/ddr/models/describable.rb +1 -1
  46. data/lib/ddr/models/effective_license.rb +9 -0
  47. data/lib/ddr/models/engine.rb +13 -0
  48. data/lib/ddr/models/file_management.rb +157 -160
  49. data/lib/ddr/models/governable.rb +0 -4
  50. data/lib/ddr/models/has_admin_metadata.rb +80 -72
  51. data/lib/ddr/models/has_children.rb +1 -1
  52. data/lib/ddr/models/has_content.rb +18 -0
  53. data/lib/ddr/models/has_struct_metadata.rb +5 -1
  54. data/lib/ddr/models/indexing.rb +32 -20
  55. data/lib/ddr/models/inherited_license.rb +13 -0
  56. data/lib/ddr/models/license.rb +38 -0
  57. data/lib/ddr/models/solr_document.rb +195 -211
  58. data/lib/ddr/models/version.rb +1 -1
  59. data/lib/ddr/models/year_facet.rb +154 -0
  60. data/lib/ddr/utils.rb +13 -1
  61. data/lib/ddr/vocab/roles.rb +0 -10
  62. data/spec/controllers/including_role_based_access_controls_enforcement_spec.rb +4 -4
  63. data/spec/datastreams/fits_datastream_spec.rb +84 -0
  64. data/spec/fixtures/fits/document.xml +65 -0
  65. data/spec/fixtures/fits/image.xml +59 -0
  66. data/spec/index/filter_spec.rb +47 -0
  67. data/spec/index/filters_spec.rb +17 -0
  68. data/spec/index/query_spec.rb +19 -0
  69. data/spec/jobs/fits_file_characterization_spec.rb +52 -0
  70. data/spec/managers/technical_metadata_manager_spec.rb +140 -0
  71. data/spec/models/active_fedora_datastream_spec.rb +44 -0
  72. data/spec/models/collection_spec.rb +7 -12
  73. data/spec/models/component_spec.rb +3 -6
  74. data/spec/models/effective_license_spec.rb +49 -0
  75. data/spec/models/has_admin_metadata_spec.rb +143 -194
  76. data/spec/models/has_struct_metadata_spec.rb +2 -2
  77. data/spec/models/indexing_spec.rb +40 -0
  78. data/spec/models/solr_document_spec.rb +96 -37
  79. data/spec/models/year_facet_spec.rb +65 -0
  80. data/spec/spec_helper.rb +1 -7
  81. data/spec/support/shared_examples_for_ddr_models.rb +0 -2
  82. data/spec/support/shared_examples_for_has_content.rb +37 -3
  83. metadata +79 -32
  84. data/lib/ddr/datastreams/properties_datastream.rb +0 -25
  85. data/lib/ddr/jobs/migrate_legacy_authorization.rb +0 -23
  86. data/lib/ddr/models/has_properties.rb +0 -15
  87. data/lib/ddr/models/licensable.rb +0 -28
  88. data/spec/auth/legacy_authorization_spec.rb +0 -94
  89. data/spec/auth/legacy_roles_spec.rb +0 -32
  90. data/spec/jobs/migrate_legacy_authorization_spec.rb +0 -43
  91. data/spec/support/shared_examples_for_has_properties.rb +0 -5
  92. data/spec/support/shared_examples_for_licensable.rb +0 -15
@@ -0,0 +1,52 @@
1
+ module Ddr::Index
2
+ class QueryClause
3
+
4
+ PRESENT = "[* TO *]"
5
+ TERM = "{!term f=%s}%s"
6
+ BEFORE_DAYS = "[* TO NOW-%sDAYS]"
7
+
8
+ class << self
9
+ # Standard Solr query, no escaping applied
10
+ def build(field, value)
11
+ [field, value].join(":")
12
+ end
13
+
14
+ def unique_key(value)
15
+ term(UniqueKeyField.instance, value)
16
+ end
17
+ alias_method :id, :unique_key
18
+ alias_method :pid, :unique_key
19
+
20
+ def negative(field, value)
21
+ build "-#{field}", value
22
+ end
23
+
24
+ def present(field)
25
+ build field, PRESENT
26
+ end
27
+
28
+ def absent(field)
29
+ negative field, PRESENT
30
+ end
31
+
32
+ def or_values(field, *values)
33
+ build field, QueryValue.or_values(*values)
34
+ end
35
+
36
+ def before(field, date_time)
37
+ value = "[* TO %s]" % Ddr::Utils.solr_date(date_time)
38
+ build field, value
39
+ end
40
+
41
+ def before_days(field, days)
42
+ value = BEFORE_DAYS % days.to_i
43
+ build field, value
44
+ end
45
+
46
+ def term(field, value)
47
+ TERM % [field, value.gsub(/"/, '\"')]
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,70 @@
1
+ module Ddr::Index
2
+ class QueryResult < AbstractQueryResult
3
+
4
+ PAGE_SIZE = 1000
5
+
6
+ delegate :csv, to: :query
7
+
8
+ def each(&block)
9
+ if params[:rows]
10
+ each_unpaginated(&block)
11
+ else
12
+ each_paginated(&block)
13
+ end
14
+ end
15
+
16
+ def each_unpaginated(&block)
17
+ conn.select(params).docs.each(&block)
18
+ end
19
+
20
+ def each_paginated(&block)
21
+ pages.each { |pg| pg.each(&block) }
22
+ end
23
+
24
+ def pids
25
+ Enumerator.new do |e|
26
+ each do |doc|
27
+ e << doc[Fields::PID]
28
+ end
29
+ end
30
+ end
31
+
32
+ def each_pid(&block)
33
+ pids.each(&block)
34
+ end
35
+
36
+ def docs
37
+ Enumerator.new do |e|
38
+ each do |doc|
39
+ e << DocumentBuilder.build(doc)
40
+ end
41
+ end
42
+ end
43
+
44
+ def all
45
+ to_a
46
+ end
47
+
48
+ def pages
49
+ num = 1
50
+ Enumerator.new do |e|
51
+ loop do
52
+ pg = page(num)
53
+ e << pg
54
+ unless pg.has_next?
55
+ break
56
+ end
57
+ num += 1
58
+ end
59
+ end
60
+ end
61
+
62
+ def page(num)
63
+ page_params = params.dup
64
+ page_size = page_params.delete(:rows) || PAGE_SIZE
65
+ response = conn.page num, page_size, "select", params: page_params
66
+ response.docs
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,16 @@
1
+ module Ddr::Index
2
+ class QueryValue
3
+
4
+ class << self
5
+ def build(value)
6
+ RSolr.solr_escape(value)
7
+ end
8
+
9
+ def or_values(*values)
10
+ value = values.map { |v| build(v) }.join(" OR ")
11
+ "(#{value})"
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Ddr::Index
2
+ class Response < SimpleDelegator
3
+
4
+ def docs
5
+ self["response"]["docs"]
6
+ end
7
+
8
+ def num_found
9
+ self["response"]["numFound"].to_i
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ require 'singleton'
2
+
3
+ module Ddr::Index
4
+ class UniqueKeyField < Field
5
+ include Singleton
6
+
7
+ def initialize
8
+ super SOLR_DOCUMENT_ID
9
+ end
10
+
11
+ end
12
+ end
@@ -1,60 +1,14 @@
1
1
  module Ddr
2
2
  module IndexFields
3
3
 
4
- def self.solr_name(*args)
5
- ActiveFedora::SolrService.solr_name(*args)
6
- end
4
+ warn "[DEPRECATION] `Ddr::IndexFields` is deprecated and will be removed in ddr-models 3.0." \
5
+ " Use `Ddr::Index::Fields` instead."
6
+
7
+ include Ddr::Index::Fields
7
8
 
8
- ACCESS_ROLE = solr_name :access_role, :stored_sortable
9
- ACTIVE_FEDORA_MODEL = solr_name :active_fedora_model, :stored_sortable
10
- ADMIN_SET = solr_name :admin_set, :stored_sortable
11
- ADMIN_SET_FACET = solr_name :admin_set_facet, :facetable
12
- COLLECTION_FACET = solr_name :collection_facet, :facetable
13
- COLLECTION_URI = solr_name :collection_uri, :symbol
14
- CONTENT_CONTROL_GROUP = solr_name :content_control_group, :searchable, type: :string
15
- CONTENT_SIZE = solr_name :content_size, :stored_sortable, type: :integer
16
- CONTENT_SIZE_HUMAN = solr_name :content_size_human, :symbol
17
- CREATOR_FACET = solr_name :creator_facet, :facetable
18
- DATE_FACET = solr_name :date_facet, :facetable
19
- DATE_SORT = solr_name :date_sort, :sortable
20
- DEFAULT_LICENSE_DESCRIPTION = solr_name :default_license_description, type: :string
21
- DEFAULT_LICENSE_TITLE = solr_name :default_license_title, type: :string
22
- DEFAULT_LICENSE_URL = solr_name :default_license_url, type: :string
23
- DISPLAY_FORMAT = solr_name :display_format, :stored_sortable
24
- EXTRACTED_TEXT = solr_name :extracted_text, :searchable, type: :text
25
- HAS_MODEL = solr_name :has_model, :symbol
26
- IDENTIFIER_ALL = solr_name :identifier_all, :symbol
27
- INTERNAL_URI = solr_name :internal_uri, :stored_sortable
28
- IS_ATTACHED_TO = solr_name :is_attached_to, :symbol
29
- IS_EXTERNAL_TARGET_FOR = solr_name :is_external_target_for, :symbol
30
- IS_GOVERNED_BY = solr_name :is_governed_by, :symbol
31
- IS_MEMBER_OF = solr_name :is_member_of, :symbol
32
- IS_MEMBER_OF_COLLECTION = solr_name :is_member_of_collection, :symbol
33
- IS_PART_OF = solr_name :is_part_of, :symbol
34
- LAST_FIXITY_CHECK_ON = solr_name :last_fixity_check_on, :stored_sortable, type: :date
35
- LAST_FIXITY_CHECK_OUTCOME = solr_name :last_fixity_check_outcome, :symbol
36
- LAST_VIRUS_CHECK_ON = solr_name :last_virus_check_on, :stored_sortable, type: :date
37
- LAST_VIRUS_CHECK_OUTCOME = solr_name :last_virus_check_outcome, :symbol
38
- LICENSE_DESCRIPTION = solr_name :license_description, type: :string
39
- LICENSE_TITLE = solr_name :license_title, type: :string
40
- LICENSE_URL = solr_name :license_url, type: :string
41
- LOCAL_ID = solr_name :local_id, :stored_sortable
42
- MEDIA_SUB_TYPE = solr_name :content_media_sub_type, :facetable
43
- MEDIA_MAJOR_TYPE = solr_name :content_media_major_type, :facetable
44
- MEDIA_TYPE = solr_name :content_media_type, :symbol
45
- MULTIRES_IMAGE_FILE_PATH = solr_name :multires_image_file_path, :stored_sortable
46
- OBJECT_PROFILE = solr_name :object_profile, :displayable
47
- OBJECT_STATE = solr_name :object_state, :stored_sortable
48
- OBJECT_CREATE_DATE = solr_name :system_create, :stored_sortable, type: :date
49
- OBJECT_MODIFIED_DATE = solr_name :system_modified, :stored_sortable, type: :date
50
- PERMANENT_ID = solr_name :permanent_id, :stored_sortable, type: :string
51
- PERMANENT_URL = solr_name :permanent_url, :stored_sortable, type: :string
52
- POLICY_ROLE = solr_name :policy_role, :symbol
53
- RESEARCH_HELP_CONTACT = solr_name :research_help_contact, :stored_sortable
54
- RESOURCE_ROLE = solr_name :resource_role, :symbol
55
- STRUCT_MAPS = solr_name :struct_maps, :stored_sortable
56
- TITLE = solr_name :title, :stored_sortable
57
- WORKFLOW_STATE = solr_name :workflow_state, :stored_sortable
9
+ def self.get(name)
10
+ Ddr::Index::Fields.get(name)
11
+ end
58
12
 
59
13
  end
60
14
  end
@@ -3,7 +3,7 @@ module Ddr
3
3
  extend ActiveSupport::Autoload
4
4
 
5
5
  autoload :PermanentId
6
- autoload :MigrateLegacyAuthorization
6
+ autoload :FitsFileCharacterization
7
7
 
8
8
  autoload_at 'ddr/jobs/permanent_id' do
9
9
  autoload :MakeUnavailable
@@ -0,0 +1,51 @@
1
+ require 'open3'
2
+
3
+ module Ddr::Jobs
4
+ class FitsFileCharacterization
5
+
6
+ @queue = :file_characterization
7
+
8
+ EVENT_SUMMARY = 'FITS characterization of content file'.freeze
9
+
10
+ def self.perform(pid)
11
+ obj = ActiveFedora::Base.find(pid)
12
+ tmp_filename = Ddr::Utils::sanitize_filename(obj.original_filename) || obj.content.default_file_name
13
+ Dir.mktmpdir do |dir|
14
+ infile = create_temp_infile(dir, tmp_filename, obj.content.content)
15
+ fits_output, err, status = Open3.capture3(fits_command, '-i', infile)
16
+ if status.success? && fits_output.present?
17
+ obj.reload
18
+ obj.fits.content = fits_output
19
+ obj.save!
20
+ end
21
+ notify_event(pid, err, status)
22
+ end
23
+ end
24
+
25
+ def self.fits_command
26
+ File.join(Ddr::Models.fits_home, 'fits.sh')
27
+ end
28
+
29
+ def self.fits_version
30
+ `#{fits_command} -v`.strip
31
+ end
32
+
33
+ private
34
+
35
+ def self.create_temp_infile(dir, tmp_filename, content)
36
+ temp_infile = File.join(dir, tmp_filename)
37
+ File.open(temp_infile, 'wb') do |f|
38
+ f.write content
39
+ end
40
+ temp_infile
41
+ end
42
+
43
+ def self.notify_event(pid, err, status)
44
+ details = status.success? ? nil : err
45
+ event_args = { pid: pid, summary: EVENT_SUMMARY, software: "fits #{fits_version}", detail: details }
46
+ event_args[:outcome] = status.success? ? Ddr::Events::Event::SUCCESS : Ddr::Events::Event::FAILURE
47
+ Ddr::Notifications.notify_event(:update, event_args)
48
+ end
49
+
50
+ end
51
+ end
@@ -5,6 +5,7 @@ module Ddr
5
5
  autoload :Manager
6
6
  autoload :DerivativesManager
7
7
  autoload :PermanentIdManager
8
+ autoload :TechnicalMetadataManager
8
9
  autoload :WorkflowManager
9
10
 
10
11
  end
@@ -0,0 +1,104 @@
1
+ module Ddr::Managers
2
+ class TechnicalMetadataManager < Manager
3
+
4
+ FITS_TIMESTAMP_FORMAT = "%D %l:%M %p" # Ex. 7/3/15 8:29 PM
5
+
6
+ delegate :content, :fits, to: :object
7
+
8
+ delegate :valid, :well_formed,
9
+ :media_type, :format_label, :format_version, :pronom_identifier,
10
+ :created, :modified, :creating_application, :extent,
11
+ :image_width, :image_height, :color_space,
12
+ to: :fits
13
+
14
+ alias_method :file_size, :extent
15
+ alias_method :last_modified, :modified
16
+
17
+ def fits?
18
+ !fits_version.nil?
19
+ end
20
+
21
+ def fits_version
22
+ fits.version.first
23
+ end
24
+
25
+ def fits_datetime
26
+ if fits_timestamp = fits.timestamp.first
27
+ no_zone = DateTime.strptime(fits_timestamp, FITS_TIMESTAMP_FORMAT)
28
+ no_zone.change(offset: no_zone.to_time.zone)
29
+ end
30
+ end
31
+
32
+ def file_size
33
+ extent.map(&:to_i)
34
+ end
35
+
36
+ def file_human_size
37
+ file_size.map do |fs|
38
+ "%s (%s bytes)" % [ ActiveSupport::NumberHelper.number_to_human_size(fs),
39
+ ActiveSupport::NumberHelper.number_to_delimited(fs) ]
40
+ end
41
+ end
42
+
43
+ def checksum_digest
44
+ content.checksumType
45
+ end
46
+
47
+ def checksum_value
48
+ content.checksum
49
+ end
50
+
51
+ def invalid?
52
+ valid.any? { |v| v == "false" }
53
+ end
54
+
55
+ def valid?
56
+ !invalid?
57
+ end
58
+
59
+ def ill_formed?
60
+ well_formed.any? { |v| v == "false" }
61
+ end
62
+
63
+ def well_formed?
64
+ !ill_formed?
65
+ end
66
+
67
+ def creation_time
68
+ created.map { |datestr| coerce_to_time(datestr) }.compact
69
+ end
70
+
71
+ def modification_time
72
+ modified.map { |datestr| coerce_to_time(datestr) }.compact
73
+ end
74
+
75
+ def index_fields
76
+ { Ddr::Index::Fields::TECHMD_FITS_VERSION => fits_version,
77
+ Ddr::Index::Fields::TECHMD_FITS_DATETIME => Ddr::Utils.solr_date(fits_datetime),
78
+ Ddr::Index::Fields::TECHMD_CREATION_TIME => Ddr::Utils.solr_dates(creation_time),
79
+ Ddr::Index::Fields::TECHMD_MODIFICATION_TIME => Ddr::Utils.solr_dates(modification_time),
80
+ Ddr::Index::Fields::TECHMD_COLOR_SPACE => color_space,
81
+ Ddr::Index::Fields::TECHMD_CREATING_APPLICATION => creating_application,
82
+ Ddr::Index::Fields::TECHMD_FILE_SIZE => file_size,
83
+ Ddr::Index::Fields::TECHMD_FORMAT_LABEL => format_label,
84
+ Ddr::Index::Fields::TECHMD_FORMAT_VERSION => format_version,
85
+ Ddr::Index::Fields::TECHMD_IMAGE_HEIGHT => image_height,
86
+ Ddr::Index::Fields::TECHMD_IMAGE_WIDTH => image_width,
87
+ Ddr::Index::Fields::TECHMD_MEDIA_TYPE => media_type,
88
+ Ddr::Index::Fields::TECHMD_PRONOM_IDENTIFIER => pronom_identifier,
89
+ Ddr::Index::Fields::TECHMD_VALID => valid,
90
+ Ddr::Index::Fields::TECHMD_WELL_FORMED => well_formed,
91
+ }
92
+ end
93
+
94
+ private
95
+
96
+ def coerce_to_time(datestr)
97
+ datestr.sub! /\A(\d+):(\d+):(\d+)/, '\1-\2-\3'
98
+ DateTime.parse(datestr).to_time
99
+ rescue ArgumentError
100
+ nil
101
+ end
102
+
103
+ end
104
+ end
@@ -9,32 +9,37 @@ require 'active_record'
9
9
  require 'hydra-core'
10
10
  require 'hydra/validations'
11
11
 
12
- require 'ddr/actions'
13
- require 'ddr/auth'
14
- require 'ddr/contacts'
15
- require 'ddr/datastreams'
16
- require 'ddr/derivatives'
17
- require 'ddr/events'
18
- require 'ddr/index_fields'
19
- require 'ddr/jobs'
20
- require 'ddr/managers'
21
- require 'ddr/metadata'
22
- require 'ddr/notifications'
23
- require 'ddr/utils'
24
- require 'ddr/vocab'
25
-
26
12
  module Ddr
13
+ extend ActiveSupport::Autoload
14
+
15
+ autoload :Actions
16
+ autoload :Auth
17
+ autoload :Contacts
18
+ autoload :Datastreams
19
+ autoload :Derivatives
20
+ autoload :Events
21
+ autoload :Index
22
+ autoload :IndexFields
23
+ autoload :Jobs
24
+ autoload :Managers
25
+ autoload :Metadata
26
+ autoload :Notifications
27
+ autoload :Utils
28
+ autoload :Vocab
29
+
27
30
  module Models
28
31
  extend ActiveSupport::Autoload
29
32
 
30
- autoload :Base
31
33
  autoload :AccessControllable
32
- autoload :Describable
33
- autoload :EventLoggable
34
- autoload :Error
34
+ autoload :Base
35
35
  autoload :ChecksumInvalid, 'ddr/models/error'
36
- autoload :DerivativeGenerationFailure, 'ddr/models/error'
37
36
  autoload :ContentModelError, 'ddr/models/error'
37
+ autoload :DerivativeGenerationFailure, 'ddr/models/error'
38
+ autoload :Describable
39
+ autoload :EffectiveLicense
40
+ autoload :Error
41
+ autoload :EventLoggable
42
+ autoload :FileManagement
38
43
  autoload :FixityCheckable
39
44
  autoload :Governable
40
45
  autoload :HasAdminMetadata
@@ -42,15 +47,15 @@ module Ddr
42
47
  autoload :HasChildren
43
48
  autoload :HasContent
44
49
  autoload :HasMultiresImage
45
- autoload :HasProperties
46
50
  autoload :HasStructMetadata
47
51
  autoload :HasThumbnail
48
52
  autoload :Indexing
49
- autoload :FileManagement
50
- autoload :Licensable
53
+ autoload :InheritedLicense
54
+ autoload :License
51
55
  autoload :SolrDocument
52
- autoload :Structure
53
56
  autoload :StructDiv
57
+ autoload :Structure
58
+ autoload :YearFacet
54
59
 
55
60
  # Base directory of default external file store
56
61
  mattr_accessor :external_file_store
@@ -71,6 +76,17 @@ module Ddr
71
76
  "https://repository.lib.duke.edu/id/"
72
77
  end
73
78
 
79
+ # Home directory for FITS
80
+ mattr_accessor :fits_home
81
+
82
+ # Run file characterization or not?
83
+ mattr_accessor :characterize_files do
84
+ false
85
+ end
86
+ class << self
87
+ alias :characterize_files? :characterize_files
88
+ end
89
+
74
90
  # Yields an object with module configuration accessors
75
91
  def self.configure
76
92
  yield self