talia_core 0.5.4 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION.yml +2 -2
- data/config/talia_core.yml.example +37 -35
- data/generators/talia_admin/templates/app/models/fake_source.rb +93 -0
- data/generators/talia_admin/templates/app/models/talia_collection.rb +13 -37
- data/generators/talia_base/talia_base_generator.rb +0 -1
- data/generators/talia_base/templates/app/controllers/custom_templates_controller.rb +2 -1
- data/generators/talia_base/templates/app/controllers/sources_controller.rb +1 -1
- data/generators/talia_base/templates/script/configure_talia +56 -73
- data/generators/talia_swicky/talia_swicky_generator.rb +18 -0
- data/generators/talia_swicky/templates/app/controllers/swicky_notebooks_controller.rb +111 -0
- data/generators/talia_swicky/templates/app/helpers/swicky_notebooks_helper.rb +29 -0
- data/generators/talia_swicky/templates/app/views/swicky_notebooks/index.builder +6 -0
- data/generators/talia_swicky/templates/app/views/swicky_notebooks/index.html.erb +10 -0
- data/generators/talia_swicky/templates/app/views/swicky_notebooks/show.html.erb +11 -0
- data/generators/talia_swicky/templates/test/fixtures/notebook.rdf +862 -0
- data/generators/talia_swicky/templates/test/functional/swicky_notebooks_controller_test.rb +44 -0
- data/lib/core_ext/boolean.rb +23 -0
- data/lib/core_ext/jdbc_rake_monkeypatch.rb +22 -0
- data/lib/core_ext/nil_class.rb +11 -0
- data/lib/core_ext/object.rb +34 -0
- data/lib/core_ext/string.rb +15 -0
- data/lib/custom_template.rb +3 -1
- data/lib/loader_helper.rb +16 -3
- data/lib/mysql.rb +7 -7
- data/lib/progressbar.rb +2 -2
- data/lib/swicky/exhibit_json/item.rb +129 -0
- data/lib/swicky/exhibit_json/item_collection.rb +129 -0
- data/lib/swicky/fragment.rb +0 -0
- data/lib/swicky/note.rb +7 -0
- data/lib/swicky/notebook.rb +78 -12
- data/lib/talia_core/active_source.rb +45 -13
- data/lib/talia_core/active_source_parts/class_methods.rb +154 -26
- data/lib/talia_core/active_source_parts/finders.rb +49 -26
- data/lib/talia_core/active_source_parts/predicate_handler.rb +71 -23
- data/lib/talia_core/active_source_parts/rdf/ntriples_reader.rb +13 -0
- data/lib/talia_core/active_source_parts/rdf/rdf_reader.rb +99 -0
- data/lib/talia_core/active_source_parts/rdf/rdfxml_reader.rb +12 -0
- data/lib/talia_core/active_source_parts/{rdf.rb → rdf_handler.rb} +52 -19
- data/lib/talia_core/active_source_parts/xml/generic_reader.rb +151 -260
- data/lib/talia_core/active_source_parts/xml/generic_reader_add_statements.rb +97 -0
- data/lib/talia_core/active_source_parts/xml/generic_reader_helpers.rb +88 -0
- data/lib/talia_core/active_source_parts/xml/generic_reader_import_statements.rb +239 -0
- data/lib/talia_core/active_source_parts/xml/rdf_builder.rb +14 -7
- data/lib/talia_core/active_source_parts/xml/source_builder.rb +7 -3
- data/lib/talia_core/active_source_parts/xml/source_reader.rb +17 -2
- data/lib/talia_core/collection.rb +192 -1
- data/lib/talia_core/data_types/data_loader.rb +88 -18
- data/lib/talia_core/data_types/data_record.rb +24 -2
- data/lib/talia_core/data_types/delayed_copier.rb +13 -3
- data/lib/talia_core/data_types/file_record.rb +24 -13
- data/lib/talia_core/data_types/file_store.rb +111 -94
- data/lib/talia_core/data_types/iip_data.rb +104 -23
- data/lib/talia_core/data_types/iip_loader.rb +102 -56
- data/lib/talia_core/data_types/image_data.rb +3 -1
- data/lib/talia_core/data_types/media_link.rb +4 -1
- data/lib/talia_core/data_types/mime_mapping.rb +65 -38
- data/lib/talia_core/data_types/path_helpers.rb +23 -17
- data/lib/talia_core/data_types/pdf_data.rb +9 -6
- data/lib/talia_core/data_types/simple_text.rb +5 -4
- data/lib/talia_core/data_types/xml_data.rb +53 -25
- data/lib/talia_core/dummy_handler.rb +3 -2
- data/lib/talia_core/errors.rb +13 -27
- data/lib/talia_core/initializer.rb +44 -4
- data/lib/talia_core/oai/active_source_model.rb +13 -6
- data/lib/talia_core/oai/active_source_oai_adapter.rb +13 -12
- data/lib/talia_core/rdf_import.rb +1 -1
- data/lib/talia_core/rdf_resource.rb +2 -1
- data/lib/talia_core/semantic_collection_wrapper.rb +143 -151
- data/lib/talia_core/semantic_property.rb +4 -0
- data/lib/talia_core/semantic_relation.rb +84 -33
- data/lib/talia_core/source.rb +45 -25
- data/lib/talia_core/source_fragment.rb +7 -0
- data/lib/talia_core/source_transfer_object.rb +3 -1
- data/lib/talia_core/source_types/agent.rb +16 -0
- data/lib/talia_core/source_types/dc_resource.rb +3 -3
- data/lib/talia_core/source_types/marcont_resource.rb +15 -0
- data/lib/talia_core/source_types/skos_concept.rb +17 -0
- data/lib/talia_dependencies.rb +1 -1
- data/lib/talia_util.rb +1 -1
- data/lib/talia_util/bar_progressor.rb +1 -1
- data/lib/talia_util/image_conversions.rb +8 -2
- data/lib/talia_util/import_job_helper.rb +40 -3
- data/lib/talia_util/io_helper.rb +15 -4
- data/lib/talia_util/progressable.rb +50 -1
- data/lib/talia_util/rake_tasks.rb +3 -21
- data/lib/talia_util/test_helpers.rb +6 -1
- data/lib/talia_util/util.rb +108 -27
- data/lib/talia_util/xml/base_builder.rb +28 -1
- data/lib/talia_util/xml/rdf_builder.rb +81 -5
- data/lib/tasks/talia_core_tasks.rake +2 -0
- data/test/core_ext/boolean_test.rb +26 -0
- data/test/core_ext/nil_class_test.rb +14 -0
- data/test/core_ext/object_test.rb +26 -0
- data/test/core_ext/string_test.rb +11 -0
- data/test/swicky/json_encoder_test.rb +51 -42
- data/test/swicky/notebook_test.rb +13 -6
- data/test/talia_core/active_source_finder_interface_test.rb +30 -0
- data/test/talia_core/active_source_test.rb +445 -34
- data/test/talia_core/collection_test.rb +332 -0
- data/test/talia_core/data_types/file_record_test.rb +2 -23
- data/test/talia_core/ntriples_reader_test.rb +49 -0
- data/test/talia_core/rdfxml_reader_test.rb +51 -0
- data/test/talia_core/source_test.rb +12 -0
- data/test/talia_util/import_job_helper_test.rb +19 -12
- metadata +190 -90
- data/config/database.yml +0 -19
- data/config/rdfstore.yml +0 -13
- data/config/talia_core.yml +0 -24
- data/generators/talia_base/templates/migrations/bj_migration.rb +0 -10
- data/lib/JXslt/jxslt.rb +0 -60
- data/lib/swicky/json_encoder.rb +0 -179
- data/lib/talia_core/agent.rb +0 -14
- data/lib/talia_core/background_jobs/job.rb +0 -82
- data/lib/talia_core/background_jobs/progress_job.rb +0 -68
- data/lib/talia_core/data_types/temp_file_handling.rb +0 -85
- data/lib/talia_core/ordered_source.rb +0 -228
- data/lib/talia_core/semantic_collection_item.rb +0 -94
- data/lib/talia_core/source_types/collection.rb +0 -15
- data/lib/talia_util/progressbar.rb +0 -236
- data/tasks/talia_core_tasks.rake +0 -2
- data/test/talia_core/ordered_source_test.rb +0 -394
- data/test/talia_core/semantic_collection_item_test.rb +0 -125
@@ -21,7 +21,15 @@ module TaliaCore
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
# This writes the "cp" command to the output script. It will also
|
25
|
+
# add a "mkdir" command to create the directory for the target file,
|
26
|
+
# if necessary.
|
27
|
+
#
|
28
|
+
# At the moment,
|
29
|
+
# this will always use UNIX-style "cp" and "mkdir" commands.
|
24
30
|
def self.cp(source, target)
|
31
|
+
# We use the << in-place string concenation, 'cause if there
|
32
|
+
# are a lot of files, it really makes a speed difference
|
25
33
|
unless(dir_seen?(File.expand_path(target)))
|
26
34
|
mkdir_string = 'mkdir -vp "'
|
27
35
|
mkdir_string << File.dirname(File.expand_path(target))
|
@@ -47,7 +55,8 @@ module TaliaCore
|
|
47
55
|
|
48
56
|
private
|
49
57
|
|
50
|
-
|
58
|
+
# Returns true if the directory has already been seen by
|
59
|
+
# the copier before.
|
51
60
|
def self.dir_seen?(directory)
|
52
61
|
@seen_dirs = {}
|
53
62
|
return true if(@seen_dirs[directory])
|
@@ -55,12 +64,13 @@ module TaliaCore
|
|
55
64
|
false
|
56
65
|
end
|
57
66
|
|
58
|
-
# The file name for the delayed copy
|
67
|
+
# The file name for the delayed copy (the file where the
|
68
|
+
# commands are written out)
|
59
69
|
def self.delay_file_name
|
60
70
|
File.join(RAILS_ROOT, 'delayed_copy.sh')
|
61
71
|
end
|
62
72
|
|
63
|
-
# Backs up an existing file if necessary
|
73
|
+
# Backs up an existing file with delayed commands, if necessary
|
64
74
|
def self.backup_file
|
65
75
|
round = 1
|
66
76
|
file_name = 'nil'
|
@@ -1,54 +1,65 @@
|
|
1
1
|
module TaliaCore
|
2
2
|
module DataTypes
|
3
3
|
|
4
|
-
# Base class for all data records that use a plain file for data storage
|
4
|
+
# Base class for all data records that use a plain file for data storage. This
|
5
|
+
# implements the DataRecord API so that all byte methods work on a file in the
|
6
|
+
# File system.
|
7
|
+
#
|
8
|
+
# Most of the operations are defined in the FileStore module, see there on how
|
9
|
+
# to create and work with file records.
|
10
|
+
#
|
11
|
+
# See the DataLoader and MimeMapping modules to see new file records are
|
12
|
+
# created automatically, depending on the MIME type.
|
13
|
+
#
|
14
|
+
# The data paths are set automatically by the class, see PathHelpers
|
15
|
+
#
|
16
|
+
# There is also an IipLoader module, that contains the loader mechanism for
|
17
|
+
# creating Iip images - you can also use that as an example to create
|
18
|
+
# new loaders for other file types.
|
5
19
|
class FileRecord < DataRecord
|
6
20
|
include FileStore
|
7
|
-
extend FileStore::ClassMethods
|
8
21
|
|
9
22
|
include PathHelpers
|
10
23
|
extend PathHelpers::ClassMethods
|
11
24
|
|
12
|
-
include TempFileHandling
|
13
|
-
extend TempFileHandling::ClassMethods
|
14
|
-
|
15
25
|
include DataLoader
|
16
26
|
extend DataLoader::ClassMethods
|
17
27
|
extend IipLoader
|
18
28
|
extend TaliaUtil::IoHelper # Data IO for class methods
|
19
29
|
|
20
|
-
after_save :
|
30
|
+
after_save :write_file_after_save
|
21
31
|
|
22
|
-
before_destroy :
|
32
|
+
before_destroy :destroy_file
|
23
33
|
|
24
34
|
# Returns and, if necessary, creates the file for "delayed" copy operations
|
25
35
|
|
26
|
-
#
|
36
|
+
# Return all bytes from the file as a byte array.
|
27
37
|
def all_bytes
|
28
38
|
read_all_bytes
|
29
39
|
end
|
30
40
|
|
31
|
-
#
|
41
|
+
# Returns the next byte from the file (at the position of the
|
42
|
+
# read cursor), or EOS if the end of the file has been reached.
|
32
43
|
def get_byte(close_after_single_read=false)
|
33
44
|
next_byte(close_after_single_read)
|
34
45
|
end
|
35
46
|
|
36
|
-
#
|
47
|
+
# Returns the current position of the read cursor
|
37
48
|
def position
|
38
49
|
return (@position != nil) ? @position : 0
|
39
50
|
end
|
40
51
|
|
41
|
-
#
|
52
|
+
# Reset the cursor to the beginning of the file
|
42
53
|
def reset
|
43
54
|
set_position(0)
|
44
55
|
end
|
45
56
|
|
46
|
-
#
|
57
|
+
# Set a new position for the read cursor
|
47
58
|
def seek(new_position)
|
48
59
|
set_position(new_position)
|
49
60
|
end
|
50
61
|
|
51
|
-
#
|
62
|
+
# Returns the file size in bytes
|
52
63
|
def size
|
53
64
|
data_size
|
54
65
|
end
|
@@ -2,36 +2,72 @@ require 'fileutils'
|
|
2
2
|
|
3
3
|
module TaliaCore
|
4
4
|
module DataTypes
|
5
|
+
|
6
|
+
# The "hevy lifting" for FileRecords, handling the actual creation and
|
7
|
+
# manipulation of the files.
|
8
|
+
#
|
9
|
+
# A record can be created with create_from_file (passing a filename) or
|
10
|
+
# with create_from_data, passing a byte array.
|
11
|
+
#
|
12
|
+
# While a "location" can be passed to the record (which will be save
|
13
|
+
# in the database), the actual file name will be determined by the system.
|
14
|
+
# Also see the PathHelpers for info
|
15
|
+
#
|
16
|
+
# The base directory for file storage can be configured in the
|
17
|
+
# talia_core.yml file as "data_directory_location". Otherwise,
|
18
|
+
# RAILS_ROOT/data. Inside, each record will be stored at
|
19
|
+
# "<data class of the record>/<last three numbers of the id>/<record id>"
|
20
|
+
#
|
21
|
+
# So, for example, a "XmlData" record with the id "123456" would be stored
|
22
|
+
# as "DATA_ROOT/XmlData/456/123456"
|
23
|
+
#
|
24
|
+
# = Creating new records
|
25
|
+
#
|
26
|
+
# When creating a new record using create_from_data, the data will be cached
|
27
|
+
# inside the object and only be saved when the record itself is saved. In
|
28
|
+
# this case, the file data is simply written to file during the save operation.
|
29
|
+
#
|
30
|
+
# When the record is created using create_from_file, the behaviour depens on
|
31
|
+
# the parameters passed and the system settings.
|
32
|
+
#
|
33
|
+
# * In case the delete_original flag is set, the system will try to move the
|
34
|
+
# file to the new location. If both are on the same file system, this will
|
35
|
+
# be quicker than a copy operation.
|
36
|
+
# * Currently, the move operation uses the "mv" command. This is does not work
|
37
|
+
# on windows, but is a workaround to stability problems with the file handling
|
38
|
+
# in JRuby
|
39
|
+
# * If the delete_original flag is not set, the system will attempt to copy the
|
40
|
+
# files:
|
41
|
+
# * If the "delay_file_copies" options is set in the _environment_, no copy
|
42
|
+
# operation will be done. Instead, the system will create a "delayed_copies.sh"
|
43
|
+
# script that can be executed from a UNIX shell to do the actual copying.
|
44
|
+
# This is extremely fast and stable, as no actual copying is done.
|
45
|
+
# * Otherwise Talia will attempt to copy the file by itself. If the "fast_copies"
|
46
|
+
# flag is set in _environment_, it will use the internal copy routine
|
47
|
+
# which will work on any system. Otherwise, it will call the system's "cp"
|
48
|
+
# command, which can sometimes be more stable with jruby.
|
49
|
+
#
|
50
|
+
# Also see the DataLoader module to see how the creation of records automatically
|
51
|
+
# selects the record type and loader, depending on the MIME type of the data.
|
52
|
+
#
|
53
|
+
# *Note*: The above behaviour means that for files that are treated through "copy"
|
54
|
+
# or "move", the original file must not be touched by external processes until
|
55
|
+
# the record is saved
|
5
56
|
module FileStore
|
6
57
|
|
7
|
-
#
|
58
|
+
# The file handle of the current record
|
8
59
|
@file_handle = nil
|
9
|
-
#
|
60
|
+
# Read cursor within the current record
|
10
61
|
@position = 0
|
11
62
|
|
12
|
-
# Class
|
63
|
+
# Class used to represent data paths in file records. This type is used for
|
64
|
+
# strings that contain a path to a file, to distinguish them from "normal"
|
65
|
+
# string, which may contain plain data.
|
13
66
|
class DataPath < String ; end
|
14
|
-
|
15
|
-
module ClassMethods
|
16
|
-
|
17
|
-
# Find or create a record for the given location and source_id, then it saves the given file.
|
18
|
-
def find_or_create_and_assign_file(params)
|
19
|
-
data_record = self.find_or_create_by_location_and_source_id(extract_filename(params[:file]), params[:source_id])
|
20
|
-
data_record.file = params[:file]
|
21
|
-
data_record.save # force attachment save and it also saves type attribute.
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
67
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# The original file must not be touched by external processes until the
|
31
|
-
# record is saved.
|
32
|
-
#
|
33
|
-
# If the delete_original flag is set, the original file will be removed
|
34
|
-
# on save
|
68
|
+
# Creates a new record from an existing file on the file system.
|
69
|
+
# The file will be copied or moved when the new record is saved - see
|
70
|
+
# the module documentation to see how this works in detail.
|
35
71
|
def create_from_file(location, file_path, delete_original = false)
|
36
72
|
close_file
|
37
73
|
self.location = location
|
@@ -39,13 +75,15 @@ module TaliaCore
|
|
39
75
|
@delete_original_file = delete_original
|
40
76
|
end
|
41
77
|
|
42
|
-
#
|
43
|
-
|
78
|
+
# Creates a new record from the given data (binary array). See
|
79
|
+
# the module documentation for the details; the data will be cached
|
80
|
+
# and written to disk once the new record is saved.
|
81
|
+
def create_from_data(location, data, options = {})
|
44
82
|
# close file if opened
|
45
83
|
close_file
|
46
84
|
|
47
85
|
# Set the location for the record
|
48
|
-
self.location =
|
86
|
+
self.location = location
|
49
87
|
|
50
88
|
if(data.respond_to?(:read))
|
51
89
|
@file_data_to_write = data.read
|
@@ -55,38 +93,21 @@ module TaliaCore
|
|
55
93
|
|
56
94
|
end
|
57
95
|
|
58
|
-
#
|
96
|
+
# Returns the contents of the file as a text string.
|
59
97
|
def all_text
|
60
98
|
if(!is_file_open?)
|
61
99
|
open_file
|
62
100
|
end
|
63
101
|
@file_handle.read(self.size)
|
64
102
|
end
|
65
|
-
|
66
|
-
# This is a placeholder in case file is used in a form.
|
67
|
-
def file() nil; end
|
68
|
-
|
69
|
-
# Assign the file data (<tt>StringIO</tt> or <tt>File</tt>).
|
70
|
-
def file=(file_data)
|
71
|
-
return nil if file_data.nil? || file_data.size == 0
|
72
|
-
self.assign_type file_data.content_type
|
73
|
-
self.location = file_data.original_filename
|
74
|
-
if file_data.is_a?(StringIO)
|
75
|
-
file_data.rewind
|
76
|
-
self.temp_data = file_data.read
|
77
|
-
else
|
78
|
-
self.temp_path = file_data.path
|
79
|
-
end
|
80
|
-
@save_attachment = true
|
81
|
-
end
|
82
103
|
|
104
|
+
# Callback for writing the data from create_from_data or create_from_file. If there is
|
105
|
+
# a problem saving this file, only an internal assertion is thrown so that it won't crash
|
106
|
+
# production environments.
|
83
107
|
def write_file_after_save
|
84
108
|
# check if there are data to write
|
85
109
|
return unless(@file_data_to_write)
|
86
110
|
|
87
|
-
# check if file already exists
|
88
|
-
# raise(RuntimeError, "File already exists: #{file_path}") if(File.exists?(file_path))
|
89
|
-
|
90
111
|
begin
|
91
112
|
self.class.benchmark("\033[36m\033[1m\033[4mFileStore\033[0m Saving file for #{self.id}") do
|
92
113
|
# create data directory path
|
@@ -106,7 +127,7 @@ module TaliaCore
|
|
106
127
|
|
107
128
|
end
|
108
129
|
|
109
|
-
# Return true if the
|
130
|
+
# Return true if the data file is open
|
110
131
|
def is_file_open?
|
111
132
|
(@file_handle != nil)
|
112
133
|
end
|
@@ -117,11 +138,10 @@ module TaliaCore
|
|
117
138
|
self.type = MimeMapping.class_type_from(content_type).name
|
118
139
|
end
|
119
140
|
|
120
|
-
|
121
|
-
# private methods ==================================================================
|
122
141
|
private
|
123
142
|
|
124
|
-
# This saves the cached data from
|
143
|
+
# This saves the cached data from create_from_data. Simply writes the
|
144
|
+
# data to disk.
|
125
145
|
def save_cached_data
|
126
146
|
# open file for writing
|
127
147
|
@file_handle = File.open(file_path, 'w')
|
@@ -135,7 +155,7 @@ module TaliaCore
|
|
135
155
|
end
|
136
156
|
|
137
157
|
# This copies the data file with which this object was created to the
|
138
|
-
# actual storage lcoation
|
158
|
+
# actual storage lcoation. This is for records created with create_from_file
|
139
159
|
def copy_data_file
|
140
160
|
copy_or_move(@file_data_to_write, file_path)
|
141
161
|
end
|
@@ -172,7 +192,7 @@ module TaliaCore
|
|
172
192
|
end
|
173
193
|
end
|
174
194
|
|
175
|
-
# Read all bytes from
|
195
|
+
# Read all bytes from the file
|
176
196
|
def read_all_bytes
|
177
197
|
# 1. Open file with option "r" (reading) and "b" (binary, useful for window system)
|
178
198
|
open_file
|
@@ -191,7 +211,7 @@ module TaliaCore
|
|
191
211
|
end
|
192
212
|
end
|
193
213
|
|
194
|
-
#
|
214
|
+
# Gets the next byte of the file
|
195
215
|
def next_byte(close)
|
196
216
|
if !is_file_open?
|
197
217
|
open_file
|
@@ -216,7 +236,9 @@ module TaliaCore
|
|
216
236
|
|
217
237
|
end
|
218
238
|
|
219
|
-
# Copy or move the source file to the target
|
239
|
+
# Copy or move the source file to the target depending on the
|
240
|
+
# <tt>delete_original</tt> setting in #create_from_file.
|
241
|
+
# Working around all the
|
220
242
|
# things that suck in JRuby. This will honour two environment settings:
|
221
243
|
#
|
222
244
|
# * delay_file_copies - will not copy the files, but create a batch file
|
@@ -227,35 +249,50 @@ module TaliaCore
|
|
227
249
|
# JRuby only.
|
228
250
|
def copy_or_move(original, target)
|
229
251
|
if(@delete_original_file)
|
230
|
-
|
252
|
+
begin
|
253
|
+
FileUtils.move(original, target)
|
254
|
+
rescue Errno::EACCES
|
255
|
+
# Workaround for File.rename bug with JRuby (jira.codehaus.org/browse/JRUBY-3381),
|
256
|
+
# based on the code from Lenny Marks 03/Jun/10.
|
257
|
+
safe_copy original, target
|
258
|
+
FileUtils.rm original
|
259
|
+
end
|
231
260
|
else
|
232
261
|
# Delay can be enabled through enviroment
|
233
262
|
if(delay_copies)
|
234
263
|
DelayedCopier.cp(original, target)
|
235
|
-
elsif(fast_copies)
|
236
|
-
FileUtils.copy(original, target)
|
237
264
|
else
|
238
|
-
|
239
|
-
# crashes that occurred using the builtin copy
|
240
|
-
from_file = File.expand_path(original)
|
241
|
-
to_file = File.expand_path(target)
|
242
|
-
system_success = system("cp '#{from_file}' '#{to_file}'")
|
243
|
-
raise(IOError, "copy error '#{from_file}' '#{to_file}'") unless system_success
|
265
|
+
safe_copy original, target
|
244
266
|
end
|
245
267
|
end
|
246
268
|
end
|
269
|
+
|
270
|
+
# Copies the file using some workarounds for jruby if necessary.
|
271
|
+
# See also #copy_or_move.
|
272
|
+
def safe_copy(original, target)
|
273
|
+
if(fast_copies)
|
274
|
+
FileUtils.copy(original, target)
|
275
|
+
else
|
276
|
+
# Call the copy as an external command. This is to work around the
|
277
|
+
# crashes that occurred using the builtin copy
|
278
|
+
from_file = File.expand_path(original)
|
279
|
+
to_file = File.expand_path(target)
|
280
|
+
system_success = system("cp '#{from_file}' '#{to_file}'")
|
281
|
+
raise(IOError, "copy error '#{from_file}' '#{to_file}'") unless system_success
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
247
285
|
|
248
|
-
|
249
|
-
# Returns true if the 'delayed write' is enabled in the environment
|
286
|
+
# Returns true if the 'delay_file_copies' option is set in the environment
|
250
287
|
def delay_copies
|
251
|
-
ENV['delay_file_copies']
|
288
|
+
ENV['delay_file_copies'].yes?
|
252
289
|
end
|
253
290
|
|
254
|
-
# Returns true if the '
|
291
|
+
# Returns true if the 'fast_copies' option is enabled in the environment.
|
255
292
|
# Otherwise the class will use a workaround that is less likely to
|
256
293
|
# crash the whole system using JRuby.
|
257
294
|
def fast_copies
|
258
|
-
ENV['fast_copies']
|
295
|
+
ENV['fast_copies'].yes?
|
259
296
|
end
|
260
297
|
|
261
298
|
# Return the data size
|
@@ -263,7 +300,7 @@ module TaliaCore
|
|
263
300
|
File.size(file_path)
|
264
301
|
end
|
265
302
|
|
266
|
-
#
|
303
|
+
# Sets the position of the reading cursor
|
267
304
|
def set_position(position)
|
268
305
|
if (position != nil and position =~ /\A\d+\Z/)
|
269
306
|
if (position < size)
|
@@ -275,32 +312,12 @@ module TaliaCore
|
|
275
312
|
raise(IOError, 'Position not valid. It must be an integer')
|
276
313
|
end
|
277
314
|
end
|
278
|
-
|
279
|
-
#
|
280
|
-
def
|
281
|
-
@save_attachment
|
282
|
-
end
|
283
|
-
|
284
|
-
# Save the attachment, copying the file from the temp_path to the data_path.
|
285
|
-
def save_attachment
|
286
|
-
return unless save_attachment?
|
287
|
-
save_file
|
288
|
-
@save_attachment = false
|
289
|
-
true
|
290
|
-
end
|
291
|
-
|
292
|
-
# Destroy the attachment
|
293
|
-
def destroy_attachment
|
315
|
+
|
316
|
+
# Delete the file connected to this record
|
317
|
+
def destroy_file
|
294
318
|
FileUtils.rm(full_filename) if File.exists?(full_filename)
|
295
319
|
end
|
296
320
|
|
297
|
-
# Save the attachment on the data_path directory.
|
298
|
-
def save_file
|
299
|
-
FileUtils.mkdir_p(File.dirname(full_filename))
|
300
|
-
FileUtils.cp(temp_path, full_filename)
|
301
|
-
FileUtils.chmod(0644, full_filename)
|
302
|
-
end
|
303
|
-
|
304
321
|
end
|
305
322
|
end
|
306
|
-
end
|
323
|
+
end
|
@@ -1,87 +1,162 @@
|
|
1
1
|
module TaliaCore
|
2
2
|
module DataTypes
|
3
3
|
|
4
|
-
# Class to manage IIP Image data type.
|
4
|
+
# Class to manage IIP Image data type. This differs from the "normal"
|
5
|
+
# file record in various ways:
|
6
|
+
#
|
7
|
+
# * The record itself only contains a thumbnail version of the image as
|
8
|
+
# data (#get_thumbnail will return the file data for the thumbnail)
|
9
|
+
# * The location of the record contains the path to the image on the
|
10
|
+
# IIP server. This is also returned by #iip_server_path
|
11
|
+
# * The records should be created using the IipLoader - see DataLoader
|
12
|
+
# to find out how to configure Talia to do that
|
13
|
+
#
|
14
|
+
# = What is IIP?
|
15
|
+
#
|
16
|
+
# IIP is a protocol to use "pyramidal images" - which are images which
|
17
|
+
# contain a tiled version of the original image in various resolutions.
|
18
|
+
# It is used to serve high-resolution images in a way that a client can
|
19
|
+
# request only the portion of the image that is currently needed.
|
20
|
+
#
|
21
|
+
# = How does IIP work with Talia?
|
22
|
+
#
|
23
|
+
# To use IIP, an IIP server is needed.
|
24
|
+
# The IIP client will connect to the server and request an image from using
|
25
|
+
# HTTP requests.
|
26
|
+
#
|
27
|
+
# The IIP server is a separate piece of sofware, which is not part of
|
28
|
+
# Talia. The one commonly used is http://iipimage.sourceforge.net/ which
|
29
|
+
# runs as a fastcgi module in Apache (or another web server).
|
30
|
+
#
|
31
|
+
# For talia this means:
|
32
|
+
#
|
33
|
+
# * The pyramidal images have to be put in a place where the IIP server can
|
34
|
+
# access them - they do *not* go in the normal data directory.
|
35
|
+
# * The location of the directory for the pyramidal images can be configured
|
36
|
+
# as <tt>iip_root_directory_location</tt> in the <tt>talia_core.yml</tt>
|
37
|
+
# file.
|
38
|
+
# * The <tt>location</tt> field of each IipData record contains the _relative_
|
39
|
+
# path for accessing the file on the IIP server.
|
40
|
+
# * The URI of the server itself can be set in <tt>talia_core.yml</tt> as
|
41
|
+
# <tt>iip_server_uri</tt>
|
42
|
+
#
|
43
|
+
# = How can the IIP images be shown in Talia?
|
44
|
+
#
|
45
|
+
# There are several clients or "viewers" for IIP images available, and one
|
46
|
+
# is also included in the "muruca_widgets" gem that can be installed in
|
47
|
+
# addition to talia.
|
48
|
+
#
|
49
|
+
# Generally you'll have to include the client in your view templates, and
|
50
|
+
# use the #iip_server_uri and #iip_server_path to point it to the image
|
51
|
+
# on the server.
|
52
|
+
#
|
53
|
+
# Obviously you'll also need a running IIP server.
|
54
|
+
#
|
55
|
+
# = What are the requirements for IIP?
|
56
|
+
#
|
57
|
+
# Apart from the IIP server itself, you'll need some software to actually
|
58
|
+
# create the pyramidal images - see the TaliaUtil::ImageConversions class for details.
|
5
59
|
class IipData < FileRecord
|
6
60
|
|
7
|
-
# Returns the IIP server configured for the application
|
61
|
+
# Returns the IIP server configured for the application. This is usually the
|
62
|
+
# value configured as the <tt>iip_server_uri</tt> in the <tt>talia_core.yml</tt>
|
8
63
|
def self.iip_server_uri
|
9
64
|
TaliaCore::CONFIG['iip_server_uri'] ||= 'http://localhost/fcgi-bin/iipsrv.fcgi'
|
10
65
|
end
|
11
66
|
|
12
|
-
# This is the mime type for the thumbnail
|
67
|
+
# This is the mime type for the thumbnail. Always gif.
|
13
68
|
def set_mime_type
|
14
69
|
self.mime = 'image/gif'
|
15
70
|
end
|
16
71
|
|
17
72
|
alias :get_thumbnail :all_bytes
|
18
73
|
|
19
|
-
# Create from existing thumb and
|
74
|
+
# Create a new record from an existing thumb and pyramidal image.
|
75
|
+
# This is used to make a record from pre-existing "prepared" images
|
76
|
+
# and does not do any image conversion.
|
20
77
|
def create_from_existing(thumb, pyramid, delete_originals = false)
|
21
78
|
@file_data_to_write = [thumb, pyramid]
|
22
79
|
@delete_original_file = delete_originals
|
23
80
|
self.location = ''
|
24
81
|
end
|
25
82
|
|
26
|
-
#
|
83
|
+
# The relative path to the image on the IIP server
|
27
84
|
def iip_server_path
|
28
85
|
self.location
|
29
86
|
end
|
30
87
|
|
88
|
+
# Callback to write the data when the record is saved, which is more involved than
|
89
|
+
# the same method on the superclass.
|
90
|
+
#
|
91
|
+
# * If the thumb and pyramid file are already available, they are used directly
|
92
|
+
# with #direct_write!
|
93
|
+
# * Otherwise it will take the original file and pass it through the TaliaUtil::ImageConversions
|
94
|
+
# to create the thumbnail and pyramidal image.
|
95
|
+
# * Any temporary files created in the process are cleaned up afterwards
|
31
96
|
def write_file_after_save
|
32
97
|
return unless(@file_data_to_write)
|
33
98
|
|
34
|
-
# Check if we have the images already
|
35
|
-
# them and call the super method
|
99
|
+
# Check if we have the converted images already. In this case we write
|
100
|
+
# them to the appropriate directories directly and call the super method
|
36
101
|
return super if(direct_write!)
|
37
102
|
|
38
|
-
#
|
103
|
+
# "Prepare" the original file for conversion, indicate if a temp file is being used
|
39
104
|
original_file_path, orig_is_temp = prepare_original_file
|
105
|
+
# Check if we need to delete the original/temp file
|
40
106
|
will_delete_source = orig_is_temp || @delete_original_file
|
107
|
+
# Path to the temporary thumbnail file
|
41
108
|
destination_thumbnail_file_path = File.join(Dir.tmpdir, "thumbnail_#{random_tempfile_filename}.gif")
|
42
109
|
|
43
|
-
begin
|
110
|
+
begin
|
44
111
|
self.class.benchmark("\033[36mIipData\033[0m Making thumb and pyramid for #{self.id}", Logger::INFO) do
|
45
112
|
|
113
|
+
# Create the thumbnail at the temporary location
|
46
114
|
TaliaUtil::ImageConversions::create_thumb(original_file_path, destination_thumbnail_file_path)
|
115
|
+
# Create the pyramidal image from the original
|
47
116
|
create_pyramid(original_file_path)
|
48
117
|
|
49
|
-
# Run the super implementation for the thumbnail
|
50
|
-
# We will simply tell the system that we have to move the newly create
|
51
|
-
# thumb file
|
118
|
+
# Run the super implementation for the thumbnail, by using the temporary thumb file as the "data"
|
52
119
|
@file_data_to_write = DataPath.new(destination_thumbnail_file_path)
|
120
|
+
# The temp thumb file needs to be deleted by the superclass
|
53
121
|
@delete_original_file = true
|
54
122
|
|
55
123
|
end # end benchmarking
|
56
124
|
super
|
57
125
|
|
58
126
|
ensure
|
59
|
-
#
|
127
|
+
# Delete the temporary "original" file, if necessary
|
60
128
|
File.delete original_file_path if(File.exists?(original_file_path) && will_delete_source)
|
61
129
|
end
|
62
130
|
end
|
63
131
|
|
64
132
|
|
65
|
-
# Checks if
|
66
|
-
#
|
133
|
+
# Checks if there if the thumb and pyramidal file already exist and can simply be moved
|
134
|
+
# to the correct location.
|
135
|
+
#
|
136
|
+
# If yes, the method will move or copy the files to the correct locations, and return true
|
137
|
+
# Otherwise, the method will do nothing and return false.
|
67
138
|
def direct_write!
|
139
|
+
# If we have an array of data files, we can assume that these are
|
140
|
+
# pre-prepared thumb and pyramid images
|
68
141
|
return false unless(@file_data_to_write.kind_of?(Array))
|
69
142
|
|
70
143
|
thumb, pyramid = @file_data_to_write
|
71
144
|
self.class.benchmark("\033[36mIipData\033[0m Direct write for #{self.id}", Logger::INFO) do
|
72
|
-
prepare_for_pyramid
|
73
|
-
|
145
|
+
prepare_for_pyramid # Setup the record for the new image
|
146
|
+
# Copy or move the pyramid file to the correct location
|
74
147
|
copy_or_move(pyramid, get_iip_root_file_path)
|
75
148
|
|
76
149
|
end # end benchmark
|
77
|
-
|
150
|
+
|
151
|
+
# Set the thumb file as the data file for the current FileRecord (which
|
152
|
+
# is automatically handled by the superclass)
|
78
153
|
@file_data_to_write = DataPath.new(thumb)
|
79
154
|
|
80
155
|
true
|
81
156
|
end
|
82
157
|
|
83
158
|
# This prepares the original file that needs to be converted. This will
|
84
|
-
# see if the data to be written is binary data or a file path. If
|
159
|
+
# see if the data to be written is binary data or a file path. If it
|
85
160
|
# is binary data, it will create a temporary file on the disk.
|
86
161
|
#
|
87
162
|
# This returns an array with two elements: The name of the file to
|
@@ -105,7 +180,8 @@ module TaliaCore
|
|
105
180
|
end
|
106
181
|
end
|
107
182
|
|
108
|
-
# Prepare for copying or creating the pyramid image
|
183
|
+
# Prepare for copying or creating the pyramid image. Sets the <tt>location</tt>
|
184
|
+
# of the record and creates the directory to store the IIP images
|
109
185
|
def prepare_for_pyramid
|
110
186
|
# set location
|
111
187
|
self.location = get_iip_root_file_path(true)
|
@@ -116,7 +192,7 @@ module TaliaCore
|
|
116
192
|
|
117
193
|
# Creates the pyramid image for IIP by running the configured system
|
118
194
|
# command. This automatically creates the file in the correct location
|
119
|
-
# (IIP root)
|
195
|
+
# (IIP root). The conversion is done using the TaliaUtil::ImageConversions
|
120
196
|
def create_pyramid(source)
|
121
197
|
# check if file already exists
|
122
198
|
raise(IOError, "File already exists: #{get_iip_root_file_path}") if(File.exists?(get_iip_root_file_path))
|
@@ -126,8 +202,13 @@ module TaliaCore
|
|
126
202
|
TaliaUtil::ImageConversions::create_pyramid(source, get_iip_root_file_path)
|
127
203
|
end
|
128
204
|
|
129
|
-
# Return the iip root directory for a specific iip image file
|
205
|
+
# Return the iip root directory for a specific iip image file. This is the
|
206
|
+
# directory where the pyramid file will be stored on disk.
|
207
|
+
#
|
208
|
+
# If the <tt>relative</tt> flag is set, it will return a relative path.
|
130
209
|
def iip_root_directory(relative = false)
|
210
|
+
# TODO: The relative paths are (also?) used for web access, is it o.k. to
|
211
|
+
# use File.join ?
|
131
212
|
if relative == false
|
132
213
|
File.join(TaliaCore::CONFIG["iip_root_directory_location"], ("00" + self.id.to_s)[-3..-1])
|
133
214
|
else
|
@@ -135,7 +216,7 @@ module TaliaCore
|
|
135
216
|
end
|
136
217
|
end
|
137
218
|
|
138
|
-
# Return the full file path
|
219
|
+
# Return the full file path for the IIP image. See #iip_root_directory
|
139
220
|
def get_iip_root_file_path(relative = false)
|
140
221
|
File.join(iip_root_directory(relative), self.id.to_s + '.tif')
|
141
222
|
end
|