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
# 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
  
  # 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.
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.



114
115
116
# File 'lib/scorm/package.rb', line 114

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

#closeObject

Closes the package.



105
106
107
108
109
110
111
# File 'lib/scorm/package.rb', line 105

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.

Returns:

  • (Boolean)


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

def exists?(filename)
  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

#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.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/scorm/package.rb', line 123

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.



154
155
156
157
158
159
160
161
162
# File 'lib/scorm/package.rb', line 154

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.



195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/scorm/package.rb', line 195

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.

Returns:

  • (Boolean)


144
145
146
147
# File 'lib/scorm/package.rb', line 144

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>


186
187
188
189
190
191
192
# File 'lib/scorm/package.rb', line 186

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