Class: Safrano::Media::Static

Inherits:
Handler
  • Object
show all
Defined in:
lib/odata/collection_media.rb

Overview

Simple static File/Directory based media store handler similar to Rack::Static with a flat directory structure

Direct Known Subclasses

StaticTree

Instance Method Summary collapse

Methods inherited from Handler

#check_before_create

Constructor Details

#initialize(root: nil, mediaklass:) ⇒ Static

Returns a new instance of Static.



22
23
24
25
26
27
28
29
30
31
# File 'lib/odata/collection_media.rb', line 22

def initialize(root: nil, mediaklass:)
  super()
  @root = Pathname(File.absolute_path(root || Dir.pwd))
  @files_class = ::Rack.release[0..2] == '2.0' ? ::Rack::File : ::Rack::Files
  @media_class = mediaklass
  @media_dir_name = Pathname(mediaklass.to_s)
  @semaphore = Thread::Mutex.new

  register
end

Instance Method Details

#abs_filename(entity) ⇒ Object

absolute filename



98
99
100
# File 'lib/odata/collection_media.rb', line 98

def abs_filename(entity)
  @root + filename(entity)
end

#abs_media_directory(entity) ⇒ Object

the same as above but absolute



110
111
112
# File 'lib/odata/collection_media.rb', line 110

def abs_media_directory(entity)
  @abs_klass_dir + entity.media_path_id
end

#abs_path(entity) ⇒ Object

/@root/Photo/1



93
94
95
# File 'lib/odata/collection_media.rb', line 93

def abs_path(entity)
  @root + media_path(entity)
end

#atomic_write(file_name) ⇒ Object

see also … File activesupport/lib/active_support/core_ext/file/atomic.rb, line 21



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/odata/collection_media.rb', line 53

def atomic_write(file_name)
  Tempfile.open('', @abs_temp_dir) do |temp_file|
    temp_file.binmode
    return_val = yield temp_file
    temp_file.close

    # Overwrite original file with temp file
    File.rename(temp_file.path, file_name)
    return_val
  end
end

#create_abs_class_dirObject



38
39
40
# File 'lib/odata/collection_media.rb', line 38

def create_abs_class_dir
  FileUtils.makedirs @abs_klass_dir
end

#create_abs_temp_dirObject



42
43
44
# File 'lib/odata/collection_media.rb', line 42

def create_abs_temp_dir
  FileUtils.makedirs @abs_temp_dir
end

#filename(entity) ⇒ Object

relative to @root eg Photo/1/1



88
89
90
# File 'lib/odata/collection_media.rb', line 88

def filename(entity)
  media_path(entity) + ressource_version(entity)
end

#finalizeObject



46
47
48
49
# File 'lib/odata/collection_media.rb', line 46

def finalize
  create_abs_class_dir
  create_abs_temp_dir
end

#media_directory(entity) ⇒ Object

this is relative to abs_klass_dir(entity) eg to /@root/Photo simplest implementation is media_directory = entity.media_path_id –> we get a 1 level depth flat directory structure



105
106
107
# File 'lib/odata/collection_media.rb', line 105

def media_directory(entity)
  entity.media_path_id
end

#media_path(entity) ⇒ Object

this is relative to @root eg. Photo/1



82
83
84
# File 'lib/odata/collection_media.rb', line 82

def media_path(entity)
  @media_dir_name + media_directory(entity)
end

#odata_delete(entity:) ⇒ Object



122
123
124
125
126
127
# File 'lib/odata/collection_media.rb', line 122

def odata_delete(entity:)
  mpi = abs_media_directory(entity)
  return unless Dir.exist?(mpi)

  mpi.children.each { |oldp| File.delete(oldp) }
end

#odata_get(request:, entity:) ⇒ Object

minimal working implementation…

Note: files_app works relative to @root directory


67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/odata/collection_media.rb', line 67

def odata_get(request:, entity:)
  media_env = request.env.dup
  media_env['PATH_INFO'] = filename(entity)
  # new app instance for each call for thread safety
  files_app = @files_class.new(@root)
  fsret = files_app.call(media_env)
  if fsret.first == 200
    # provide own content type as we keep it in the media entity
    fsret[1]['Content-Type'] = entity.content_type
  end
  fsret
end

#registerObject



33
34
35
36
# File 'lib/odata/collection_media.rb', line 33

def register
  @abs_klass_dir = @root + @media_dir_name
  @abs_temp_dir = @abs_klass_dir.join('tmp')
end

#replace_file(data:, entity:) ⇒ Object

Note: add a new Version and remove the previous one



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/odata/collection_media.rb', line 145

def replace_file(data:, entity:)
  with_media_directory(entity) do |d|
    tp = Tempfile.open('', @abs_temp_dir) do |temp_file|
      temp_file.binmode
      IO.copy_stream(data, temp_file)
      temp_file.path
    end

    # picking new filename and the "move" operation must
    # be protected
    @semaphore.synchronize do
      # new filename =  "version" + 1
      v = ressource_version(entity)
      filename = d + (v.to_i + 1).to_s

      # Move temp file to original target file
      File.rename(tp, filename)

      # remove the previous version
      filename = d + v
      File.delete(filename)
    end
  end
end

#ressource_version(entity) ⇒ Object

needed for having a changing media ressource “source” metadata after each upload, so that clients get informed about new versions of the same media ressource



140
141
142
# File 'lib/odata/collection_media.rb', line 140

def ressource_version(entity)
  abs_media_directory(entity).children(false).max.to_s
end

#save_file(data:, filename:, entity:) ⇒ Object

Here as well, MVP implementation



130
131
132
133
134
135
# File 'lib/odata/collection_media.rb', line 130

def save_file(data:, filename:, entity:)
  with_media_directory(entity) do |d|
    filename = d.join('1')
    atomic_write(filename) { |f| IO.copy_stream(data, f) }
  end
end

#with_media_directory(entity) {|Pathname(mpi)| ... } ⇒ Object

yields the absolute path of media directory and ensure the directory exists

Yields:

  • (Pathname(mpi))


116
117
118
119
120
# File 'lib/odata/collection_media.rb', line 116

def with_media_directory(entity)
  mpi = abs_media_directory(entity)
  FileUtils.mkdir_p mpi
  yield Pathname(mpi)
end