odeon_uk 2.0.4 → 3.0.0
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 +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +6 -0
- data/Rakefile +29 -0
- data/lib/odeon_uk/api/cinema.rb +62 -0
- data/lib/odeon_uk/api/response.rb +55 -0
- data/lib/odeon_uk/api/screenings.rb +89 -0
- data/lib/odeon_uk/cinema.rb +53 -114
- data/lib/odeon_uk/configuration.rb +21 -0
- data/lib/odeon_uk/html/cinema.rb +101 -0
- data/lib/odeon_uk/html/parser/film_with_screenings.rb +160 -0
- data/lib/odeon_uk/html/screenings.rb +38 -0
- data/lib/odeon_uk/{internal → html}/website.rb +1 -1
- data/lib/odeon_uk/internal/title_sanitizer.rb +1 -0
- data/lib/odeon_uk/screening.rb +7 -15
- data/lib/odeon_uk/version.rb +2 -2
- data/lib/odeon_uk.rb +11 -4
- data/odeon_uk.gemspec +2 -0
- data/rake/fixture_creator.rb +98 -0
- data/test/fixtures/api/all_cinemas.plist +0 -0
- data/test/fixtures/api/app_init.plist +0 -0
- data/test/fixtures/api/film_times/71-100747.plist +0 -0
- data/test/fixtures/api/film_times/71-100750.plist +0 -0
- data/test/fixtures/api/film_times/71-100790.plist +0 -0
- data/test/fixtures/api/film_times/71-14646.plist +0 -0
- data/test/fixtures/api/film_times/71-14725.plist +0 -0
- data/test/fixtures/api/film_times/71-15086.plist +0 -0
- data/test/fixtures/api/film_times/71-15096.plist +0 -0
- data/test/fixtures/api/film_times/71-15122.plist +0 -0
- data/test/fixtures/api/film_times/71-15130.plist +0 -0
- data/test/fixtures/api/film_times/71-15141.plist +0 -0
- data/test/fixtures/api/film_times/71-15142.plist +0 -0
- data/test/fixtures/api/film_times/71-15143.plist +0 -0
- data/test/fixtures/api/film_times/71-15144.plist +0 -0
- data/test/fixtures/api/film_times/71-15145.plist +0 -0
- data/test/fixtures/api/film_times/71-15170.plist +0 -0
- data/test/fixtures/api/film_times/71-15172.plist +0 -0
- data/test/fixtures/api/film_times/71-15177.plist +0 -0
- data/test/fixtures/api/film_times/71-15179.plist +0 -0
- data/test/fixtures/api/film_times/71-15182.plist +0 -0
- data/test/fixtures/api/film_times/71-15286.plist +0 -0
- data/test/fixtures/api/film_times/71-15360.plist +0 -0
- data/test/fixtures/api/film_times/71-15586.plist +0 -0
- data/test/fixtures/api/film_times/71-15700.plist +0 -0
- data/test/fixtures/api/film_times/71-15718.plist +0 -0
- data/test/fixtures/api/film_times/71-15768.plist +0 -0
- data/test/fixtures/api/film_times/71-15788.plist +0 -0
- data/test/fixtures/api/film_times/71-15793.plist +0 -0
- data/test/fixtures/api/film_times/71-15794.plist +0 -0
- data/test/fixtures/api/film_times/71-15795.plist +0 -0
- data/test/fixtures/api/film_times/71-15796.plist +0 -0
- data/test/fixtures/api/film_times/71-15799.plist +0 -0
- data/test/fixtures/api/film_times/71-15802.plist +0 -0
- data/test/fixtures/api/film_times/71-15814.plist +0 -0
- data/test/fixtures/api/film_times/71-15817.plist +0 -0
- data/test/fixtures/api/film_times/71-15840.plist +0 -0
- data/test/fixtures/{cinema/leicester_square.html → html/cinema/105.html} +384 -313
- data/test/fixtures/{cinema/bfi_imax.html → html/cinema/211.html} +677 -355
- data/test/fixtures/{cinema/brighton.html → html/cinema/71.html} +899 -431
- data/test/fixtures/html/showtimes/11-imax.html +170 -0
- data/test/fixtures/html/showtimes/171-d-box.html +203 -0
- data/test/fixtures/html/showtimes/71-0.html +59 -0
- data/test/fixtures/html/showtimes/71.html +2395 -0
- data/test/fixtures/{sitemap.html → html/sitemap.html} +264 -352
- data/test/lib/odeon_uk/api/cinema_test.rb +149 -0
- data/test/lib/odeon_uk/api/response_test.rb +44 -0
- data/test/lib/odeon_uk/api/screenings_test.rb +43 -0
- data/test/lib/odeon_uk/cinema_test.rb +373 -148
- data/test/lib/odeon_uk/configuration_test.rb +29 -0
- data/test/lib/odeon_uk/html/cinema_test.rb +149 -0
- data/test/lib/odeon_uk/{internal/film_with_screenings_parser_test.rb → html/parser/film_with_screenings_test.rb} +12 -42
- data/test/lib/odeon_uk/html/screenings_test.rb +43 -0
- data/test/lib/odeon_uk/html/website_test.rb +37 -0
- data/test/lib/odeon_uk/screening_test.rb +17 -83
- data/test/support/api_fixtures_helper.rb +34 -0
- data/test/support/website_fixtures_helper.rb +25 -0
- data/test/test_helper.rb +7 -2
- metadata +149 -34
- data/lib/odeon_uk/film.rb +0 -65
- data/lib/odeon_uk/internal/film_with_screenings_parser.rb +0 -159
- data/lib/odeon_uk/internal/showtimes_page.rb +0 -36
- data/test/fixture_updater.rb +0 -72
- data/test/fixtures/showtimes/brighton/film_first.html +0 -92
- data/test/fixtures/showtimes/brighton/film_last.html +0 -100
- data/test/fixtures/showtimes/brighton.html +0 -2071
- data/test/fixtures/showtimes/liverpool_one/film_first_dbox.html +0 -188
- data/test/fixtures/showtimes/manchester/film_first_imax.html +0 -182
- data/test/lib/odeon_uk/film_test.rb +0 -142
- data/test/lib/odeon_uk/internal/showtimes_page_test.rb +0 -51
- data/test/lib/odeon_uk/internal/website_test.rb +0 -59
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
module OdeonUk
|
|
2
|
+
# Internal utility classes: Do not use
|
|
3
|
+
# @api private
|
|
4
|
+
module Html
|
|
5
|
+
# Parses HTML with Nokogiri
|
|
6
|
+
module Parser
|
|
7
|
+
# Parses a chunk of HTML to derive movie showing data
|
|
8
|
+
class FilmWithScreenings
|
|
9
|
+
# CSS selector for a film title inside an individual film HTML
|
|
10
|
+
NAME_CSS = '.presentation-info h4 a'
|
|
11
|
+
# CSS selector for a group of showtimes inside an individual film HTML
|
|
12
|
+
SHOWTIMES_CSS = '.times-all.accordion-group'
|
|
13
|
+
|
|
14
|
+
# @param [String] html a chunk of html
|
|
15
|
+
def initialize(html)
|
|
16
|
+
@html = html
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The film name
|
|
20
|
+
# @return [String]
|
|
21
|
+
def film_name
|
|
22
|
+
@film_name ||= Internal::TitleSanitizer.new(raw_film_name).sanitized
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# array containing hashes of screening attributes
|
|
26
|
+
# @return [Array<Hash>]
|
|
27
|
+
def to_a
|
|
28
|
+
doc.css(SHOWTIMES_CSS).map do |node|
|
|
29
|
+
dimension_parser(node).to_a.map do |hash|
|
|
30
|
+
hash.merge(film_name: film_name)
|
|
31
|
+
end
|
|
32
|
+
end.flatten
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def doc
|
|
38
|
+
@doc ||= Nokogiri::HTML(@html)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def raw_film_name
|
|
42
|
+
@raw_film_name ||= doc.css(NAME_CSS).children.first.to_s
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def dimension_parser(node)
|
|
46
|
+
DimensionNodeParser.new(node)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# parses chunk of screenings for a particular screening dimension
|
|
51
|
+
class DimensionNodeParser
|
|
52
|
+
# CSS selector for a single screening inside a 'group of showtimes' HTML
|
|
53
|
+
SCREENING_CSS = '.show'
|
|
54
|
+
# CSS selector for showing technology for a 'group of showtimes' HTML
|
|
55
|
+
TECH_CSS = '.tech a'
|
|
56
|
+
|
|
57
|
+
# @param [Nokigiri::Node] node a Nokogiri node object
|
|
58
|
+
def initialize(node)
|
|
59
|
+
@node = node
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# array containing hashes of screening attributes for dimension
|
|
63
|
+
# @return [Array<Hash>]
|
|
64
|
+
def to_a
|
|
65
|
+
screening_hashes.map do |hash|
|
|
66
|
+
hash.merge(dimension: dimension,
|
|
67
|
+
variant: add_imax(hash[:variant]))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def add_imax(original)
|
|
74
|
+
imax? ? "#{original} imax".strip : original
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def dimension
|
|
78
|
+
dimension_from_tech || '2d'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def dimension_from_tech
|
|
82
|
+
tech.match(/[23]d/i) { |data| data[0] }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def imax?
|
|
86
|
+
!!tech.match(/imax/)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def screening_hashes
|
|
90
|
+
screening_nodes.map { |node| ScreeningNodeParser.new(node).to_hash }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def screening_nodes
|
|
94
|
+
@node.css(SCREENING_CSS)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def tech
|
|
98
|
+
@tech ||= @node.css(TECH_CSS).text.gsub('in ', '').downcase
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# parses a single screening
|
|
103
|
+
class ScreeningNodeParser
|
|
104
|
+
# regex for time format
|
|
105
|
+
TIME_REGEX = %r(\d+/\d+/\d+ \d{2}\:\d{2})
|
|
106
|
+
# regex for D-Box screenings
|
|
107
|
+
DBOX_REGEX = /D-Box/
|
|
108
|
+
|
|
109
|
+
# @param [Nokigiri::Node] node a Nokogiri node object
|
|
110
|
+
def initialize(node)
|
|
111
|
+
@node = node
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# hashes of screening attributes
|
|
115
|
+
# @return [Hash]
|
|
116
|
+
def to_hash
|
|
117
|
+
{
|
|
118
|
+
booking_url: booking_url,
|
|
119
|
+
time: utc_time,
|
|
120
|
+
variant: variant
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def booking_url
|
|
127
|
+
link_attr('href').to_s
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def link_attr(attribute)
|
|
131
|
+
@node.css('a').attribute(attribute)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def info
|
|
135
|
+
@node.css('i').to_s
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def time
|
|
139
|
+
Time.parse(time_string)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def time_string
|
|
143
|
+
link_attr('title').to_s.match(TIME_REGEX).to_s + ' UTC'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def tz
|
|
147
|
+
@tz ||= TZInfo::Timezone.get('Europe/London')
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def utc_time
|
|
151
|
+
tz.local_to_utc(time)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def variant
|
|
155
|
+
info.match(DBOX_REGEX) ? 'd-box' : ''
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module OdeonUk
|
|
2
|
+
# Internal utility classes: Do not use
|
|
3
|
+
# @api private
|
|
4
|
+
module Html
|
|
5
|
+
# The object representing a single screening of a film on the Odeon UK website
|
|
6
|
+
class Screenings
|
|
7
|
+
# css selector for film html chunks
|
|
8
|
+
FILM_CSS = '.film-detail'
|
|
9
|
+
|
|
10
|
+
# All currently listed films showing at a cinema
|
|
11
|
+
# @param [Integer] cinema_id id of the cinema on the website
|
|
12
|
+
# @return [Array<Hash>]
|
|
13
|
+
def self.at(cinema_id)
|
|
14
|
+
film_nodes(cinema_id).flat_map do |node|
|
|
15
|
+
screenings_parser(node).to_a
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def self.screenings_parser(html)
|
|
22
|
+
Parser::FilmWithScreenings.new(html)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.film_nodes(id)
|
|
26
|
+
showtimes_doc(id).css(FILM_CSS).map { |node| node.to_s.gsub(/^\s+/, '') }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.showtimes(id)
|
|
30
|
+
OdeonUk::Html::Website.new.showtimes(id)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.showtimes_doc(id)
|
|
34
|
+
Nokogiri::HTML(showtimes(id))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/odeon_uk/screening.rb
CHANGED
|
@@ -5,6 +5,8 @@ module OdeonUk
|
|
|
5
5
|
attr_reader :booking_url
|
|
6
6
|
# @return [String] 2d or 3d
|
|
7
7
|
attr_reader :dimension
|
|
8
|
+
# @return [String] the cinema id
|
|
9
|
+
attr_reader :cinema_id
|
|
8
10
|
# @return [String] the cinema name
|
|
9
11
|
attr_reader :cinema_name
|
|
10
12
|
# @return [String] the film name
|
|
@@ -32,11 +34,9 @@ module OdeonUk
|
|
|
32
34
|
# @param [Integer] cinema_id id of the cinema on the website
|
|
33
35
|
# @return [Array<OdeonUk::Screening>]
|
|
34
36
|
def self.at(cinema_id)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
39
|
-
end.flatten
|
|
37
|
+
cinema = { cinema_id: cinema_id, cinema_name: Cinema.new(cinema_id).name }
|
|
38
|
+
|
|
39
|
+
parser.at(cinema_id).map { |hash| new(hash.merge(cinema)) }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# The date of the screening
|
|
@@ -60,16 +60,8 @@ module OdeonUk
|
|
|
60
60
|
|
|
61
61
|
private
|
|
62
62
|
|
|
63
|
-
def self.
|
|
64
|
-
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def self.showtimes_page(cinema_id)
|
|
68
|
-
OdeonUk::Internal::ShowtimesPage.new(cinema_id)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def self.cinema_hash(id)
|
|
72
|
-
{ cinema_id: id, cinema_name: Cinema.find(id).name }
|
|
63
|
+
def self.parser
|
|
64
|
+
Html::Screenings
|
|
73
65
|
end
|
|
74
66
|
end
|
|
75
67
|
end
|
data/lib/odeon_uk/version.rb
CHANGED
data/lib/odeon_uk.rb
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
require 'forwardable'
|
|
1
2
|
require 'nokogiri'
|
|
2
3
|
require 'tzinfo'
|
|
3
4
|
require 'tzinfo/data'
|
|
4
5
|
|
|
5
6
|
require_relative './odeon_uk/version'
|
|
7
|
+
require_relative './odeon_uk/configuration'
|
|
6
8
|
|
|
7
|
-
require_relative './odeon_uk/internal/film_with_screenings_parser'
|
|
8
|
-
require_relative './odeon_uk/internal/showtimes_page'
|
|
9
9
|
require_relative './odeon_uk/internal/title_sanitizer'
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
require_relative './odeon_uk/api/response'
|
|
12
|
+
require_relative './odeon_uk/api/cinema'
|
|
13
|
+
require_relative './odeon_uk/api/screenings'
|
|
14
|
+
|
|
15
|
+
require_relative './odeon_uk/html/parser/film_with_screenings'
|
|
16
|
+
require_relative './odeon_uk/html/website'
|
|
17
|
+
require_relative './odeon_uk/html/cinema'
|
|
18
|
+
require_relative './odeon_uk/html/screenings'
|
|
11
19
|
|
|
12
20
|
require_relative './odeon_uk/cinema'
|
|
13
|
-
require_relative './odeon_uk/film'
|
|
14
21
|
require_relative './odeon_uk/screening'
|
|
15
22
|
|
|
16
23
|
# odeon_uk gem
|
data/odeon_uk.gemspec
CHANGED
|
@@ -21,8 +21,10 @@ Gem::Specification.new do |gem|
|
|
|
21
21
|
gem.version = OdeonUk::VERSION
|
|
22
22
|
|
|
23
23
|
gem.add_development_dependency 'rake'
|
|
24
|
+
gem.add_development_dependency 'minitest-reporters'
|
|
24
25
|
gem.add_development_dependency 'webmock'
|
|
25
26
|
|
|
27
|
+
gem.add_runtime_dependency 'CFPropertyList'
|
|
26
28
|
gem.add_runtime_dependency 'nokogiri'
|
|
27
29
|
gem.add_runtime_dependency 'tzinfo'
|
|
28
30
|
gem.add_runtime_dependency 'tzinfo-data'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module FixtureCreator
|
|
2
|
+
class Api
|
|
3
|
+
def all_cinemas!
|
|
4
|
+
write_fixture(:all_cinemas)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def app_init!
|
|
8
|
+
write_fixture(:app_init)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def film_times!(cinema_id)
|
|
12
|
+
OdeonUk::Api::Screenings.send(:film_ids, cinema_id).each do |i|
|
|
13
|
+
write_film_times_fixture(cinema_id, i)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def fixture(name)
|
|
20
|
+
File.expand_path("../../test/fixtures/api/#{name}.plist", __FILE__)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def log(message)
|
|
24
|
+
puts "Create API fixture: #{message}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def write_film_times_fixture(cinema_id, film_id)
|
|
28
|
+
FileUtils.mkdir_p 'film_times'
|
|
29
|
+
text = "film_times/#{cinema_id}-#{film_id}"
|
|
30
|
+
File.open(fixture(text), 'w+') do |file|
|
|
31
|
+
log(text)
|
|
32
|
+
file.write OdeonUk::Api::Response.new.send(:film_times_raw,
|
|
33
|
+
cinema_id,
|
|
34
|
+
film_id)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def write_fixture(kind)
|
|
39
|
+
FileUtils.mkdir_p kind.to_s
|
|
40
|
+
File.open(fixture(kind), 'w+') do |file|
|
|
41
|
+
log(kind)
|
|
42
|
+
file.write OdeonUk::Api::Response.new.send("#{kind}_raw".to_sym)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Html < Struct.new(:cinema_id)
|
|
48
|
+
def cinema!
|
|
49
|
+
write_page_fixture(:cinema)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def film_node!(index)
|
|
53
|
+
write_node_fixture(index)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def showtimes!
|
|
57
|
+
write_page_fixture(:showtimes)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def sitemap!
|
|
61
|
+
write_page_fixture(:sitemap)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def fixture(name)
|
|
67
|
+
File.expand_path("../../test/fixtures/html/#{name}.html", __FILE__)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def log(message)
|
|
71
|
+
puts "Create HTML fixture: #{message}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def write_node_fixture(index)
|
|
75
|
+
FileUtils.mkdir_p 'showtimes'
|
|
76
|
+
text = "showtimes/#{cinema_id}-#{index}"
|
|
77
|
+
File.open(fixture(text), 'w+') do |file|
|
|
78
|
+
log(text)
|
|
79
|
+
nodes = OdeonUk::Html::Screenings.send(:film_nodes, cinema_id)
|
|
80
|
+
node = if index.is_a?(Fixnum)
|
|
81
|
+
nodes[index]
|
|
82
|
+
else
|
|
83
|
+
nodes.select { |n| !!n.to_s.match(/#{index}/i) }[0]
|
|
84
|
+
end
|
|
85
|
+
file.write(node.to_s)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def write_page_fixture(kind)
|
|
90
|
+
FileUtils.mkdir_p kind.to_s
|
|
91
|
+
text = kind.to_s + (cinema_id ? "/#{cinema_id}" : '')
|
|
92
|
+
File.open(fixture(text), 'w+') do |file|
|
|
93
|
+
log(text)
|
|
94
|
+
file.write OdeonUk::Html::Website.new.send(*[kind, cinema_id].compact)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|