Class: Scorm::Package

Inherits:
Object
  • Object
show all
Defined in:
lib/scorm/package.rb

Constant Summary collapse

DEFAULT_LOAD_OPTIONS =
{
  :strict => false,
  :dry_run => false,
  :cleanup => true,
  :force_cleanup => false,
  :name => nil,
  :repository => nil
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename, options = {}, &block) ⇒ Package

This method will load a SCORM package and extract its content to the directory specified by the :repository option. The manifest file will be parsed and made available through the manifest instance variable. This method should be called with an associated block as it yields the opened package and then auto-magically closes it when the block has finished. It will also do any necessary cleanup if an exception occur anywhere in the block. The available options are:

:+strict+:     If +false+ the manifest will be parsed in a nicer way. Default: +true+.
:+dry_run+:    If +true+ nothing will be written to the file system. Default: +false+.
:+cleanup+:    If +false+ no cleanup will take place if an error occur. Default: +true+.
:+name+:       The name to use when extracting the package to the
               repository. Default: will use the filename of the package
               (minus the .zip extension).
:+repository+: Path to the course repository. Default: the same directory as the package.

53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/scorm/package.rb', line 53

def initialize(filename, options = {}, &block)
  @options = DEFAULT_LOAD_OPTIONS.merge(options)
  @package = filename.respond_to?(:path) ? filename.path : filename

  if @package.kind_of?(ActiveRecord::Relation)
    manifest = @package.detect{|file| file.file.filename.match(/imsmanifest.xml/)}.file.read
    @manifest = Manifest.new(@package, manifest)

    yield(self)

    self.close
  else
    # Check if package is a directory or a file.
    if File.directory?(@package)
      @name = File.basename(@package)
      @repository = File.dirname(@package)
      @path = File.expand_path(@package)
    else
      i = nil
      begin
        # Decide on a name for the package.
        @name = [(@options[:name] || File.basename(@package, File.extname(@package))), i].flatten.join

        # Set the path for the extracted package.
        @repository = @options[:repository] || File.dirname(@package)
        @path = File.expand_path(File.join(@repository, @name))

        # First try is nil, subsequent tries sets and increments the value with
        # one starting at zero.
        i = (i || 0) + 1

        # Make sure the generated path is unique.
      end while File.exists?(@path)
    end

    # Extract the package
    extract!

    # Detect and read imsmanifest.xml
    if exists?('imsmanifest.xml')
      @manifest = Manifest.new(self, file('imsmanifest.xml'))
    else
      raise InvalidPackage, "#{File.basename(@package)}: no imsmanifest.xml, maybe not SCORM compatible?"
    end

    # Yield to the caller.
    yield(self)

    # Make sure the package is closed when the caller has finished reading it.
    self.close

    # If an exception occur the package is auto-magically closed and any
    # residual data deleted in a clean way.
  end


rescue Exception => e
  self.close
  self.cleanup
  raise e
end

Instance Attribute Details

#manifestObject

An instance of Scorm::Manifest.


14
15
16
# File 'lib/scorm/package.rb', line 14

def manifest
  @manifest
end

#nameObject

Name of the package.


13
14
15
# File 'lib/scorm/package.rb', line 13

def name
  @name
end

#optionsObject

The options hash supplied when opening the package.


17
18
19
# File 'lib/scorm/package.rb', line 17

def options
  @options
end

#packageObject

The file name of the package file.


18
19
20
# File 'lib/scorm/package.rb', line 18

def package
  @package
end

#pathObject

Path to the extracted course.


15
16
17
# File 'lib/scorm/package.rb', line 15

def path
  @path
end

#repositoryObject

The directory to which the packages is extracted.


16
17
18
# File 'lib/scorm/package.rb', line 16

def repository
  @repository
end

Class Method Details

.open(filename, options = {}, &block) ⇒ Object


33
34
35
# File 'lib/scorm/package.rb', line 33

def self.open(filename, options = {}, &block)
  Package.new(filename, options, &block)
end

.set_default_load_options(options = {}) ⇒ Object


29
30
31
# File 'lib/scorm/package.rb', line 29

def self.set_default_load_options(options = {})
  DEFAULT_LOAD_OPTIONS.merge!(options)
end

Instance Method Details

#cleanupObject

Cleans up by deleting all extracted files. Called when an error occurs.


125
126
127
# File 'lib/scorm/package.rb', line 125

def cleanup
  FileUtils.rmtree(@path) if @options[:cleanup] && !@options[:dry_run] && @path && File.exists?(@path) && package?
end

#closeObject

Closes the package.


116
117
118
119
120
121
122
# File 'lib/scorm/package.rb', line 116

def close
  @zipfile.close if @zipfile

  # Make sure the extracted package is deleted if force_cleanup_on_close
  # is enabled.
  self.cleanup if @options[:force_cleanup_on_close]
end

#exists?(filename) ⇒ Boolean

Returns true if the specified file (or directory) exists in the package.


176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/scorm/package.rb', line 176

def exists?(filename)
  if @package.kind_of?(ActiveRecord::Relation)
    return true if @package.detect{|file| file.path == filename}
  else
    if File.exists?(@path)
      File.exists?(path_to(filename))
    else
      Zip::ZipFile::foreach(@package) do |entry|
        return true if entry.name == filename
      end
      false
    end
  end
end

#extract!(force = false) ⇒ Object

Extracts the content of the package to the course repository. This will be done automatically when opening a package so this method will rarely be used. If the dry_run option was set to true when the package was opened nothing will happen. This behavior can be overridden with the force parameter.


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/scorm/package.rb', line 134

def extract!(force = false)
  return if @options[:dry_run] && !force

  # If opening an already extracted package; do nothing.
  if not package?
    return
  end

  # Create the path to the course
  FileUtils.mkdir_p(@path)

  Zip::ZipFile::foreach(@package) do |entry|
    entry_path = File.join(@path, entry.name)
    entry_dir = File.dirname(entry_path)
    FileUtils.mkdir_p(entry_dir) unless File.exists?(entry_dir)
    entry.extract(entry_path)
  end
end

#file(filename) ⇒ Object

Reads a file from the package. If the file is not extracted yet (all files are extracted by default when opening the package) it will be extracted to the file system and its content returned. If the dry_run option was set to true when opening the package the file will not be extracted to the file system, but read directly into memory.


165
166
167
168
169
170
171
172
173
# File 'lib/scorm/package.rb', line 165

def file(filename)
  if File.exists?(@path)
    File.read(path_to(filename))
  else
    Zip::ZipFile.foreach(@package) do |entry|
      return entry.get_input_stream {|io| io.read } if entry.name == filename
    end
  end
end

#filesObject

Returns an array with the paths to all the files in the package.


210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/scorm/package.rb', line 210

def files
  if File.directory?(@package)
    Dir.glob(File.join(File.join(File.expand_path(@package), '**'), '*')).reject {|f|
      File.directory?(f) }.map {|f| f.sub(/^#{File.expand_path(@package)}\/?/, '') }
  else
    entries = []
    Zip::ZipFile::foreach(@package) do |entry|
      entries << entry.name unless entry.name[-1..-1] == '/'
    end
    entries
  end
end

#package?Boolean

This will only return true if what was opened was an actual zip file. It returns false if what was opened was a filesystem directory.


155
156
157
158
# File 'lib/scorm/package.rb', line 155

def package?
  return false if File.directory?(@package)
  return true
end

#path_to(relative_filename, relative = false) ⇒ Object

Computes the absolute path to a file in an extracted package given its relative path. The argument relative can be used to get the path relative to the course repository.

Ex.

<tt>pkg.path => '/var/lms/courses/MyCourse/'</tt>
<tt>pkg.course_repository => '/var/lms/courses/'</tt>
<tt>path_to('images/myimg.jpg') => '/var/lms/courses/MyCourse/images/myimg.jpg'</tt>
<tt>path_to('images/myimg.jpg', true) => 'MyCourse/images/myimg.jpg'</tt>

201
202
203
204
205
206
207
# File 'lib/scorm/package.rb', line 201

def path_to(relative_filename, relative = false)
  if relative
    File.join(@name, relative_filename)
  else
    File.join(@path, relative_filename)
  end
end