zip-container 2.2.0 → 4.0.2
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.
- checksums.yaml +5 -13
- data/Changes.rdoc +22 -0
- data/Gemfile +1 -1
- data/Licence.rdoc +1 -1
- data/Rakefile +15 -12
- data/ReadMe.rdoc +20 -8
- data/examples/create-zip-container +7 -8
- data/examples/zip-container-info +4 -4
- data/lib/zip-container/container.rb +49 -23
- data/lib/zip-container/dir.rb +8 -11
- data/lib/zip-container/entries/directory.rb +15 -11
- data/lib/zip-container/entries/entry.rb +38 -29
- data/lib/zip-container/entries/file.rb +19 -15
- data/lib/zip-container/entries/managed.rb +28 -16
- data/lib/zip-container/entries/reserved.rb +2 -2
- data/lib/zip-container/exceptions.rb +20 -13
- data/lib/zip-container/file.rb +16 -16
- data/lib/zip-container/util.rb +3 -3
- data/lib/zip-container/version.rb +4 -3
- data/version.yml +3 -3
- data/zip-container.gemspec +31 -28
- metadata +54 -95
- data/.gitignore +0 -9
- data/.ruby-env +0 -1
- data/.ruby-gemset +0 -2
- data/.ruby-version +0 -2
- data/.travis.yml +0 -17
- data/test/data/compressed_mimetype.container +0 -0
- data/test/data/dirs/dir-mimetype/mimetype/.gitkeep +0 -1
- data/test/data/dirs/empty/mimetype +0 -1
- data/test/data/dirs/managed/dir/.gitkeep +0 -0
- data/test/data/dirs/managed/greeting.txt +0 -1
- data/test/data/dirs/managed/mimetype +0 -1
- data/test/data/dirs/null/.gitkeep +0 -1
- data/test/data/empty.container +0 -0
- data/test/data/empty.zip +0 -0
- data/test/data/example.container +0 -0
- data/test/data/null.file +0 -0
- data/test/data/subclassed.container +0 -0
- data/test/helpers/entry_lists.rb +0 -35
- data/test/tc_create_dir.rb +0 -56
- data/test/tc_create_file.rb +0 -140
- data/test/tc_exceptions.rb +0 -56
- data/test/tc_managed_entries.rb +0 -399
- data/test/tc_read_dir.rb +0 -86
- data/test/tc_read_file.rb +0 -109
- data/test/tc_reserved_names.rb +0 -334
- data/test/tc_util.rb +0 -67
- data/test/ts_container.rb +0 -59
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
Mzg5YjU5ZjYyYjMzOGUyZWU1ZTk5NjI4YWRmNTQwNTk1NTU1OGJmZQ==
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 840b4e7e6338a773a393a94e42f87c7bff876ab520e0361e3378ac11fd8e4741
|
4
|
+
data.tar.gz: c441f69cb0988145551c09f837dc0d9a287c55452b1737ec4a43bf3740939ce8
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ODg2M2ZmZTllNjU2YjNmMWU2ZGQzOTlhYTE3ODAzNzI3NjY2YmRiY2JiYjg2
|
11
|
-
MjZkN2E5NTljMmIzYjIyNTcyZTE4ZDg3OGY4M2Q1NzkxOGJhZjc=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NzNhYjc1YmQ5MTg5YWU1ZDNlN2U0MDlkMDYxNDgxNDYxOTA2YWM5MzI2Njgz
|
14
|
-
MmRjOTkyNWM1NWMzZmE5ZDA1ZTBiNGJhMzRhNWZkMGU5MWEwMDBjNjcxNzUx
|
15
|
-
NDU2ZGMwNGEyNjBkYjFkM2Q3MzMwMzAzMzcyYjMyZTEwZjcyNjA=
|
6
|
+
metadata.gz: ebe9848aaa420c00e28ed4223d43ca1e186b1905ac6e3102b1ee7661f8edee5e07a68960577651d5206aa98530ea253590e9b25618b2f0aca2655bac98954b23
|
7
|
+
data.tar.gz: fec65830abe5391d805cfa2c3a417cfcc52cb47f1c99e5affebfb91b6262d3cd3292a39f4af5eea689cee4f4ff91568db0a797ee7dd706874e011b1e7798b297
|
data/Changes.rdoc
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
= Changes log for the ZIP Container Ruby Gem
|
2
2
|
|
3
|
+
== Version 3.0.2
|
4
|
+
|
5
|
+
* Update rubyzip dependency to fix security vulnerability.
|
6
|
+
|
7
|
+
== Version 3.0.1
|
8
|
+
|
9
|
+
* Fix deep content verification bug.
|
10
|
+
|
11
|
+
== Version 3.0.0
|
12
|
+
|
13
|
+
* Refactor the malformed container error constructor.
|
14
|
+
* Verifying entries only raises errors at the last moment.
|
15
|
+
* Redesign the verification system.
|
16
|
+
* Update badges to use SVG versions.
|
17
|
+
* Fix the documentation for updated verify methods.
|
18
|
+
* Fix Container#verify so that it returns consistent types.
|
19
|
+
* Rename ContainerError -> Error.
|
20
|
+
* Reword the exceptions so they don't specify "file".
|
21
|
+
* Add ruby 2.2.0 to the Travis test matrix.
|
22
|
+
* Add a compatibility note about ContainerError.
|
23
|
+
* Improve the Usage information in the ReadMe.
|
24
|
+
|
3
25
|
== Version 2.2.0
|
4
26
|
|
5
27
|
* Fix pathname bug in ZipContainer::Dir.
|
data/Gemfile
CHANGED
data/Licence.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2013
|
1
|
+
# Copyright (c) 2013-2018 The University of Manchester, UK.
|
2
2
|
#
|
3
3
|
# All rights reserved.
|
4
4
|
#
|
@@ -30,24 +30,27 @@
|
|
30
30
|
#
|
31
31
|
# Author: Robert Haines
|
32
32
|
|
33
|
-
require
|
34
|
-
require
|
35
|
-
require
|
33
|
+
require 'bundler/gem_tasks'
|
34
|
+
require 'rake/testtask'
|
35
|
+
require 'rdoc/task'
|
36
|
+
require 'rubocop/rake_task'
|
36
37
|
|
37
|
-
task :
|
38
|
+
task default: :test
|
38
39
|
|
39
40
|
Rake::TestTask.new do |t|
|
40
|
-
t.libs <<
|
41
|
+
t.libs << 'test'
|
41
42
|
t.test_files = FileList['test/ts_container.rb']
|
42
43
|
t.verbose = true
|
43
44
|
end
|
44
45
|
|
45
46
|
RDoc::Task.new do |r|
|
46
|
-
r.main =
|
47
|
-
lib = Dir.glob(
|
48
|
-
r.rdoc_files.include(
|
49
|
-
r.options <<
|
47
|
+
r.main = 'ReadMe.rdoc'
|
48
|
+
lib = Dir.glob('lib/**/*.rb')
|
49
|
+
r.rdoc_files.include('ReadMe.rdoc', 'Licence.rdoc', 'Changes.rdoc', lib)
|
50
|
+
r.options << '-t ZIP Container Format Ruby Library version ' \
|
50
51
|
"#{ZipContainer::Version::STRING}"
|
51
|
-
r.options <<
|
52
|
-
r.options <<
|
52
|
+
r.options << '-N'
|
53
|
+
r.options << '--tab-width=2'
|
53
54
|
end
|
55
|
+
|
56
|
+
RuboCop::RakeTask.new
|
data/ReadMe.rdoc
CHANGED
@@ -5,12 +5,12 @@ Contact:: mailto:support@mygrid.org.uk
|
|
5
5
|
Homepage:: http://mygrid.github.io/ruby-zip-container
|
6
6
|
Source code:: https://github.com/myGrid/ruby-zip-container
|
7
7
|
Licence:: BSD (See Licence file or http://www.opensource.org/licenses/bsd-license.php)
|
8
|
-
Copyright:: (c) 2013
|
8
|
+
Copyright:: (c) 2013-2015 The University of Manchester, UK
|
9
9
|
|
10
|
-
{<img src="https://badge.fury.io/rb/zip-container.
|
11
|
-
{<img src="https://codeclimate.com/github/myGrid/ruby-zip-container.
|
10
|
+
{<img src="https://badge.fury.io/rb/zip-container.svg" alt="Gem Version" />}[http://badge.fury.io/rb/zip-container]
|
11
|
+
{<img src="https://codeclimate.com/github/myGrid/ruby-zip-container.svg" />}[https://codeclimate.com/github/myGrid/ruby-zip-container]
|
12
12
|
{<img src="https://travis-ci.org/myGrid/ruby-zip-container.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/myGrid/ruby-zip-container]
|
13
|
-
{<img src="https://coveralls.io/repos/myGrid/ruby-zip-container/badge.
|
13
|
+
{<img src="https://coveralls.io/repos/myGrid/ruby-zip-container/badge.svg?branch=master" alt="Coverage Status" />}[https://coveralls.io/r/myGrid/ruby-zip-container?branch=master]
|
14
14
|
|
15
15
|
== Synopsis
|
16
16
|
|
@@ -36,6 +36,11 @@ underlying rubyzip API (Zip::File) and allows this library to work with
|
|
36
36
|
ZipContainer::Container should not be used directly from version 2.0.0
|
37
37
|
onwards.
|
38
38
|
|
39
|
+
=== ContainerError
|
40
|
+
|
41
|
+
This class has been renamed in version 3.0.0. It is now simply called Error so
|
42
|
+
that its fully qualified name is the more sensible ZipContainer::Error.
|
43
|
+
|
39
44
|
=== Rubyzip
|
40
45
|
|
41
46
|
Version 1.0.0 and up of this gem uses version 1.0.0 and up of the
|
@@ -46,10 +51,17 @@ in the rubyzip readme for a workaround.
|
|
46
51
|
|
47
52
|
== Usage
|
48
53
|
|
49
|
-
This library
|
50
|
-
|
51
|
-
|
52
|
-
|
54
|
+
This library has two entry points.
|
55
|
+
|
56
|
+
The main ZipContainer::File class largely mimics the rubyzip
|
57
|
+
{Zip::File}[http://www.rubydoc.info/gems/rubyzip/1.1.6/Zip/File] and
|
58
|
+
{Zip::FileSystem}[http://www.rubydoc.info/gems/rubyzip/1.1.6/Zip/FileSystem]
|
59
|
+
APIs so much of what you can do with them are supported for ZIP Containers.
|
60
|
+
There is also {API documentation}[http://mygrid.github.io/ruby-zip-container/]
|
61
|
+
with much more detail and any differences explained.
|
62
|
+
|
63
|
+
The ZipContainer::Dir class mimics, where possible, the core ruby
|
64
|
+
{Dir API}[http://ruby-doc.org/core-1.9.3/Dir.html].
|
53
65
|
|
54
66
|
There are some examples of how to use the library provided in the examples
|
55
67
|
directory. See the contents of the tests directory for even more.
|
@@ -43,27 +43,26 @@ usage unless ARGV.length == 1
|
|
43
43
|
|
44
44
|
container_file = ARGV[0]
|
45
45
|
|
46
|
-
if File.
|
46
|
+
if File.exist?(container_file)
|
47
47
|
puts "File '#{container_file}' already exists. Exiting."
|
48
48
|
exit 1
|
49
49
|
end
|
50
50
|
|
51
51
|
begin
|
52
|
-
ZipContainer::File.create(container_file,
|
53
|
-
|
52
|
+
ZipContainer::File.create(container_file, 'application/epub+zip') do |c|
|
54
53
|
# Add a cheery greeting file from a string.
|
55
|
-
c.file.open(
|
56
|
-
f.puts
|
54
|
+
c.file.open('greeting.txt', 'w') do |f|
|
55
|
+
f.puts 'Hello, World!'
|
57
56
|
end
|
58
57
|
|
59
58
|
# Create a subdirectory.
|
60
|
-
c.dir.mkdir(
|
59
|
+
c.dir.mkdir('dir')
|
61
60
|
|
62
61
|
# Copy this example code in straight from a file.
|
63
|
-
c.add(
|
62
|
+
c.add('dir/code.rb', __FILE__)
|
64
63
|
|
65
64
|
# Add a explanation of this file.
|
66
|
-
c.comment =
|
65
|
+
c.comment = 'This is an example Container file!'
|
67
66
|
end
|
68
67
|
rescue ZipContainer::MalformedContainerError, ZipContainer::ZipError => err
|
69
68
|
puts err.to_s
|
data/examples/zip-container-info
CHANGED
@@ -50,7 +50,7 @@ rescue ZipContainer::MalformedContainerError, ZipContainer::ZipError => err
|
|
50
50
|
exit 1
|
51
51
|
end
|
52
52
|
|
53
|
-
puts "Archive: #{container
|
53
|
+
puts "Archive: #{container}"
|
54
54
|
puts "Container file size: #{File.size(container_file)} bytes, "\
|
55
55
|
"number of entries: #{container.size}"
|
56
56
|
|
@@ -60,11 +60,11 @@ total_comp = 0
|
|
60
60
|
container.each do |entry|
|
61
61
|
total_size += entry.size
|
62
62
|
total_comp += entry.compressed_size
|
63
|
-
comp = entry.compression_method
|
63
|
+
comp = entry.compression_method.zero? ? 'stor' : 'defN'
|
64
64
|
size = entry.size.to_s.rjust(8)
|
65
65
|
puts "#{size} #{comp} #{entry.time} #{entry.name}"
|
66
66
|
end
|
67
67
|
|
68
68
|
ratio = ((total_size - total_comp) / total_size.to_f) * 100
|
69
|
-
puts "
|
70
|
-
|
69
|
+
puts "#{container.size} files, #{total_size} bytes uncompressed, "\
|
70
|
+
"#{total_comp} bytes compressed: #{ratio.round(1)}%"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2014 The University of Manchester, UK.
|
1
|
+
# Copyright (c) 2014, 2015 The University of Manchester, UK.
|
2
2
|
#
|
3
3
|
# All rights reserved.
|
4
4
|
#
|
@@ -30,13 +30,14 @@
|
|
30
30
|
#
|
31
31
|
# Author: Robert Haines
|
32
32
|
|
33
|
-
|
33
|
+
##
|
34
34
|
module ZipContainer
|
35
35
|
|
36
36
|
# The superclass of anything that represents a Zip Container. That
|
37
37
|
# representation could be as a Zip file (most commonly), as a directory or
|
38
38
|
# something else.
|
39
39
|
class Container
|
40
|
+
|
40
41
|
include ReservedNames
|
41
42
|
include ManagedEntries
|
42
43
|
|
@@ -47,13 +48,13 @@ module ZipContainer
|
|
47
48
|
|
48
49
|
# :stopdoc:
|
49
50
|
# The reserved mimetype file name for standard ZipContainers.
|
50
|
-
MIMETYPE_FILE =
|
51
|
+
MIMETYPE_FILE = 'mimetype'.freeze
|
51
52
|
|
52
53
|
def initialize(location)
|
53
54
|
@container = open_container(location)
|
54
55
|
|
55
|
-
|
56
|
-
@mimetype = read_mimetype
|
56
|
+
@mimetype_error = verify_mimetype
|
57
|
+
@mimetype = read_mimetype if @mimetype_error.nil?
|
57
58
|
|
58
59
|
# Reserved entry names. Just the mimetype file by default.
|
59
60
|
register_reserved_name(MIMETYPE_FILE)
|
@@ -69,7 +70,7 @@ module ZipContainer
|
|
69
70
|
#
|
70
71
|
# Open an existing ZipContainer. It will be checked for conformance upon
|
71
72
|
# first access.
|
72
|
-
def self.open(filename
|
73
|
+
def self.open(filename)
|
73
74
|
c = new(filename)
|
74
75
|
|
75
76
|
if block_given?
|
@@ -84,19 +85,26 @@ module ZipContainer
|
|
84
85
|
end
|
85
86
|
|
86
87
|
# :call-seq:
|
87
|
-
# verify(filename) ->
|
88
|
+
# verify(filename) -> Array
|
88
89
|
#
|
89
90
|
# Verify that the specified ZipContainer conforms to the specification.
|
90
|
-
# This method returns
|
91
|
-
#
|
91
|
+
# This method returns a list of problems with the container.
|
92
|
+
#
|
93
|
+
# Exceptions are still raised for fundamental file system errors.
|
92
94
|
def self.verify(filename)
|
93
|
-
|
94
|
-
|
95
|
-
rescue
|
96
|
-
return false
|
97
|
-
end
|
95
|
+
new(filename).verify
|
96
|
+
end
|
98
97
|
|
99
|
-
|
98
|
+
# :call-seq:
|
99
|
+
# verify?(filename) -> boolean
|
100
|
+
#
|
101
|
+
# Verify that the specified ZipContainer conforms to the specification.
|
102
|
+
# This method returns +false+ if there are any problems at all with the
|
103
|
+
# container.
|
104
|
+
#
|
105
|
+
# Exceptions are still raised for fundamental file system errors.
|
106
|
+
def self.verify?(filename)
|
107
|
+
new(filename).verify?
|
100
108
|
end
|
101
109
|
|
102
110
|
# :call-seq:
|
@@ -111,21 +119,39 @@ module ZipContainer
|
|
111
119
|
end
|
112
120
|
|
113
121
|
# :call-seq:
|
114
|
-
# verify
|
122
|
+
# verify -> Array
|
115
123
|
#
|
116
124
|
# Verify the contents of this ZipContainer file. All managed files and
|
117
125
|
# directories are checked to make sure that they exist, if required.
|
118
|
-
def verify
|
119
|
-
verify_managed_entries
|
126
|
+
def verify
|
127
|
+
@mimetype_error.nil? ? verify_managed_entries : [@mimetype_error]
|
120
128
|
end
|
121
129
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
130
|
+
# :call-seq:
|
131
|
+
# verify? -> true or false
|
132
|
+
#
|
133
|
+
# Verify the contents of this ZipContainer file. All managed files and
|
134
|
+
# directories are checked to make sure that they exist, if required.
|
135
|
+
#
|
136
|
+
# This method returns +false+ if there are any problems at all with the
|
137
|
+
# container.
|
138
|
+
def verify?
|
139
|
+
verify.empty? ? true : false
|
127
140
|
end
|
128
141
|
|
142
|
+
# :call-seq:
|
143
|
+
# verify!
|
144
|
+
#
|
145
|
+
# Verify the contents of this ZipContainer file. All managed files and
|
146
|
+
# directories are checked to make sure that they exist, if required.
|
147
|
+
#
|
148
|
+
# This method raises a MalformedContainerError if there are any problems
|
149
|
+
# with the container.
|
150
|
+
def verify!
|
151
|
+
raise MalformedContainerError, @mimetype_error unless @mimetype_error.nil?
|
152
|
+
|
153
|
+
verify_managed_entries!
|
154
|
+
end
|
129
155
|
end
|
130
156
|
|
131
157
|
end
|
data/lib/zip-container/dir.rb
CHANGED
@@ -49,7 +49,7 @@ module ZipContainer
|
|
49
49
|
|
50
50
|
extend Forwardable
|
51
51
|
def_delegators :@container, :close, :each, :path, :pos, :pos=, :rewind,
|
52
|
-
|
52
|
+
:seek, :tell
|
53
53
|
|
54
54
|
private_class_method :new
|
55
55
|
|
@@ -65,7 +65,7 @@ module ZipContainer
|
|
65
65
|
#
|
66
66
|
# Create a new (or convert an existing) directory as a ZipContainer with
|
67
67
|
# the specified mimetype.
|
68
|
-
def self.create(pathname, mimetype
|
68
|
+
def self.create(pathname, mimetype)
|
69
69
|
::Dir.mkdir(pathname) unless ::File.directory?(pathname)
|
70
70
|
::File.write(::File.join(pathname, MIMETYPE_FILE), mimetype)
|
71
71
|
|
@@ -108,7 +108,8 @@ module ZipContainer
|
|
108
108
|
Entries.new(@container)
|
109
109
|
end
|
110
110
|
|
111
|
-
class Entries
|
111
|
+
class Entries # :nodoc:
|
112
|
+
|
112
113
|
include Enumerable
|
113
114
|
|
114
115
|
Entry = Struct.new(:name, :ftype)
|
@@ -122,7 +123,7 @@ module ZipContainer
|
|
122
123
|
end
|
123
124
|
end
|
124
125
|
|
125
|
-
def each
|
126
|
+
def each
|
126
127
|
@entries.each do |entry|
|
127
128
|
yield entry
|
128
129
|
end
|
@@ -145,12 +146,9 @@ module ZipContainer
|
|
145
146
|
|
146
147
|
def verify_mimetype
|
147
148
|
mime_path = full_path(MIMETYPE_FILE)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
else
|
152
|
-
return "'mimetype' file is missing."
|
153
|
-
end
|
149
|
+
return "'mimetype' file is missing." unless ::File.exist?(mime_path)
|
150
|
+
return "'mimetype' is not a regular file" unless ::File.file?(mime_path)
|
151
|
+
return "'mimetype' is not readable." unless ::File.readable?(mime_path)
|
154
152
|
end
|
155
153
|
|
156
154
|
def read_mimetype
|
@@ -225,7 +223,6 @@ module ZipContainer
|
|
225
223
|
#
|
226
224
|
# Equal to
|
227
225
|
# {::Dir.tell}[http://ruby-doc.org/core-1.9.3/Dir.html#method-i-tell]
|
228
|
-
|
229
226
|
end
|
230
227
|
|
231
228
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2013 The University of Manchester, UK.
|
1
|
+
# Copyright (c) 2013-2015 The University of Manchester, UK.
|
2
2
|
#
|
3
3
|
# All rights reserved.
|
4
4
|
#
|
@@ -30,7 +30,7 @@
|
|
30
30
|
#
|
31
31
|
# Author: Robert Haines
|
32
32
|
|
33
|
-
|
33
|
+
##
|
34
34
|
module ZipContainer
|
35
35
|
|
36
36
|
# A ManagedDirectory acts as the interface to a set of (possibly) managed
|
@@ -40,6 +40,7 @@ module ZipContainer
|
|
40
40
|
# Once a ManagedDirectory is registered in a Container then only it can be
|
41
41
|
# used to write to its contents.
|
42
42
|
class ManagedDirectory < ManagedEntry
|
43
|
+
|
43
44
|
include ReservedNames
|
44
45
|
include ManagedEntries
|
45
46
|
|
@@ -55,9 +56,9 @@ module ZipContainer
|
|
55
56
|
# that are within this directory (default []).
|
56
57
|
def initialize(name, options = {})
|
57
58
|
options = {
|
58
|
-
:
|
59
|
-
:
|
60
|
-
:
|
59
|
+
required: false,
|
60
|
+
hidden: false,
|
61
|
+
entries: []
|
61
62
|
}.merge(options)
|
62
63
|
|
63
64
|
super(name, options[:required], options[:hidden])
|
@@ -66,16 +67,19 @@ module ZipContainer
|
|
66
67
|
end
|
67
68
|
|
68
69
|
# :call-seq:
|
69
|
-
# verify
|
70
|
+
# verify -> Array
|
70
71
|
#
|
71
72
|
# Verify this ManagedDirectory for correctness. ManagedFiles registered
|
72
73
|
# within it are verified recursively.
|
73
74
|
#
|
74
|
-
#
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
75
|
+
# If it does not pass verification a list of reasons why it fails is
|
76
|
+
# returned. The empty list is returned if verification passes.
|
77
|
+
def verify
|
78
|
+
messages = super
|
79
79
|
|
80
|
+
@files.values.each { |f| messages += f.verify }
|
81
|
+
|
82
|
+
messages
|
83
|
+
end
|
80
84
|
end
|
81
85
|
end
|