rubymtp 0.1
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.
- data/COPYING +340 -0
- data/ChangeLog +2 -0
- data/README +57 -0
- data/bin/mtpfs +10 -0
- data/bin/podcast +10 -0
- data/lib/mtp.rb +285 -0
- data/lib/mtp/association.rb +49 -0
- data/lib/mtp/container.rb +121 -0
- data/lib/mtp/datacode.rb +485 -0
- data/lib/mtp/datatypes.rb +51 -0
- data/lib/mtp/device.rb +270 -0
- data/lib/mtp/fuse.rb +368 -0
- data/lib/mtp/object.rb +122 -0
- data/lib/mtp/playlist.rb +60 -0
- data/lib/mtp/podcast.rb +162 -0
- data/lib/mtp/properties.rb +226 -0
- data/lib/mtp/protocol.rb +298 -0
- data/lib/mtp/storage.rb +39 -0
- data/lib/mtp/track.rb +74 -0
- data/patches/ruby-mp3info-0.5.diff +80 -0
- data/patches/syndication-0.6.1.diff +41 -0
- data/test/container_test.rb +13 -0
- metadata +87 -0
data/lib/mtp/object.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
module MTP
|
2
|
+
class Object
|
3
|
+
|
4
|
+
attr_reader(:ph, :properties, :id, :format, :protection_status, :compressed_size, :thumb_format, :thumb_compressed_size,
|
5
|
+
:thumb_pix_width, :thumb_pix_height, :image_pix_with, :image_pix_height, :image_bit_depth,
|
6
|
+
:sequence_number)
|
7
|
+
attr_accessor :compressed_size, :filename, :date_created, :date_modified, :keywords, :association, :data
|
8
|
+
|
9
|
+
PROTECTION_STATUS = { 0x0000 => "No protection", 0x0001 => "Read-only", 0x8002 => "Read-only Data", 0x8003 => "Non-transferable data"}
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@properties = Properties.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.property_writer(*properties)
|
16
|
+
properties.each do |property|
|
17
|
+
Object.class_eval %{
|
18
|
+
def #{property}=(prop)
|
19
|
+
properties.#{property} = prop
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.property_reader(*properties)
|
26
|
+
properties.each do |property|
|
27
|
+
Object.class_eval %{
|
28
|
+
def #{property}
|
29
|
+
properties.#{property}
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.property_accessor(*properties)
|
36
|
+
Object.property_reader(*properties)
|
37
|
+
Object.property_writer(*properties)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.register_format(klass, code)
|
41
|
+
@@format_classes ||= Hash.new { |h,k| h[k] = Object }
|
42
|
+
@@format_classes[code.to_i] = klass
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def self.load(ph, id, storage = nil)
|
47
|
+
object = nil
|
48
|
+
t = ph.transaction.get_object_info(id)
|
49
|
+
t.expect("OK")
|
50
|
+
storage_id, format = t.data.payload.unpack("VK")
|
51
|
+
object = @@format_classes[format.to_i].new
|
52
|
+
object.load(ph, id, t.data.payload)
|
53
|
+
end
|
54
|
+
|
55
|
+
def load(ph, id, payload, storage = nil)
|
56
|
+
@id = id
|
57
|
+
@ph = ph
|
58
|
+
@storage = storage
|
59
|
+
@sent = true
|
60
|
+
storage_id, @format, @protection_status, @compressed_size, @thumb_format,
|
61
|
+
@thumb_compressed_size, @thumb_pix_width, @thumb_pix_height, @image_pix_width, @image_pix_height, @image_bit_depth,
|
62
|
+
@parent_id, association_code, association_desc, @sequence_number, @filename, @date_created, @date_modified, @keywords =
|
63
|
+
payload.unpack("VKSIKIIIIIIISIIJDDJ")
|
64
|
+
@protection_status = Datacode.new(@protection_status, PROTECTION_STATUS)
|
65
|
+
@association = Association.load(association_code, association_desc)
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect
|
70
|
+
"#<MTP::Object:0x%08x @format=#{@format.name.inspect} @filename=#{@filename.inspect} @parent_id=0x%08x>" % [ @id, @parent_id ]
|
71
|
+
end
|
72
|
+
|
73
|
+
def dump_properties
|
74
|
+
puts "0x%04x:%16s: %64s %s" % [ 0, "id", @id, "ro" ]
|
75
|
+
properties.supported.each do |property|
|
76
|
+
p = properties.get_value(property)
|
77
|
+
puts "0x%04x:%16s: %64s %s" % [ p.code.to_i, property, p.value, (p.writable? ? 'rw' : 'ro') ]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def format=(code)
|
82
|
+
@format = (code.is_a?(Datacode) ? code : Datacode.new(code, nil, MTP::Datacode::Masks::OBJECT_FORMAT))
|
83
|
+
end
|
84
|
+
|
85
|
+
def association
|
86
|
+
@association ||= Association.load(0, 0)
|
87
|
+
end
|
88
|
+
|
89
|
+
def valid?
|
90
|
+
!@format.nil? and !@compressed_size.nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def pack
|
95
|
+
[ 0, @format, @protection_status || 0, @compressed_size || 0, @thumb_format || 0, # VKSIK
|
96
|
+
@thumb_compressed_size || 0, @thumb_pix_width || 0, @thumb_pix_height || 0, #III
|
97
|
+
@image_pix_width || 0, @image_pix_height || 0, @image_bit_depth || 0, #III
|
98
|
+
@parent_id || 0, association.code, association.desc, @sequence_number || 0, #IKII
|
99
|
+
@filename, @date_created, @date_modified, @keywords || "" ].pack("VKSIKIIIIIIIKIIJDDJ")
|
100
|
+
end
|
101
|
+
|
102
|
+
def new_object?
|
103
|
+
!@sent
|
104
|
+
end
|
105
|
+
|
106
|
+
def before_sending(ph)
|
107
|
+
@ph = ph
|
108
|
+
end
|
109
|
+
|
110
|
+
def after_sending
|
111
|
+
@sent = true
|
112
|
+
end
|
113
|
+
|
114
|
+
def <=>(object)
|
115
|
+
@id <=> object.id
|
116
|
+
end
|
117
|
+
|
118
|
+
def data
|
119
|
+
@data || Data.new("\0\0", @compressed_size)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/mtp/playlist.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module MTP
|
2
|
+
class Playlist < Object
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
register_format self, Datacode.new("AbstractAudioVideoPlaylist")
|
6
|
+
property_accessor :name
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
self.format = "AbstractAudioVideoPlaylist"
|
11
|
+
self.compressed_size = 0
|
12
|
+
@data = Data.new("\0\0")
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(ph, id, payload)
|
16
|
+
super(ph, id, payload)
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
MTP.logger.info("retrieving playlist reference")
|
21
|
+
t = @ph.transaction.get_object_references(id)
|
22
|
+
t.expect("OK")
|
23
|
+
t.data.payload.unpack("V+").first.each do |object_id|
|
24
|
+
yield @ph.device[object_id] unless @ph.device[object_id].nil?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def <<(object)
|
29
|
+
current_ids = map { |o| o.id }
|
30
|
+
current_ids << object.id
|
31
|
+
store_references(current_ids)
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(object)
|
35
|
+
current_ids = select { |o| o.id != object.id }
|
36
|
+
current_ids.map! { |o| o.id }
|
37
|
+
store_references(current_ids)
|
38
|
+
end
|
39
|
+
|
40
|
+
def sort!(&block)
|
41
|
+
current_ids = sort(&block).map { |o| o.id }
|
42
|
+
store_references(current_ids)
|
43
|
+
end
|
44
|
+
|
45
|
+
def sort_by!(&block)
|
46
|
+
current_ids = sort_by(&block).map { |o| o.id }
|
47
|
+
store_references(current_ids)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def store_references(ids)
|
52
|
+
t = @ph.transaction
|
53
|
+
t.data = Data.new([ ids ].pack("V+"))
|
54
|
+
t.set_object_references(id)
|
55
|
+
t.expect("OK")
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/lib/mtp/podcast.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'mtp'
|
2
|
+
require 'yaml'
|
3
|
+
require 'syndication/rss'
|
4
|
+
require 'syndication/podcast'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'facets/more/times'
|
8
|
+
require 'facet/date/to_time'
|
9
|
+
|
10
|
+
class Syndication::RSS::Item
|
11
|
+
attr_accessor :keep
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
module MTP
|
16
|
+
class Podcast
|
17
|
+
@@logger = Logger.new(STDERR)
|
18
|
+
def self.logger
|
19
|
+
@@logger
|
20
|
+
end
|
21
|
+
|
22
|
+
def logger
|
23
|
+
@@logger
|
24
|
+
end
|
25
|
+
|
26
|
+
class Feed
|
27
|
+
attr_accessor :title, :url, :keep
|
28
|
+
def initialize(title, options = {})
|
29
|
+
@title = title
|
30
|
+
@url = options["url"] || options[:url]
|
31
|
+
@keep = options["keep"] || options[:keep]
|
32
|
+
end
|
33
|
+
|
34
|
+
def items
|
35
|
+
content = nil
|
36
|
+
open(@url) { |s| content = s.read }
|
37
|
+
parser = Syndication::RSS::Parser.new
|
38
|
+
rss = parser.parse(content)
|
39
|
+
rss.items.select { |i| i.keep = @keep; i.pubdate.to_time > @keep.days.ago }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(device, options = {})
|
44
|
+
@device = device
|
45
|
+
@playlist_name = options[:playlist] || "podcasts"
|
46
|
+
@configuration_dir = File.join(ENV['HOME'], '.mtp-podcast')
|
47
|
+
@configuration_file = options[:configuration] || File.join(@configuration_dir, 'config.yml')
|
48
|
+
end
|
49
|
+
|
50
|
+
def playlist
|
51
|
+
return @playlist unless @playlist.nil?
|
52
|
+
|
53
|
+
@playlist = @device.playlists.detect { |p| p.name == @playlist_name }
|
54
|
+
|
55
|
+
if @playlist.nil?
|
56
|
+
@playlist = Playlist.new
|
57
|
+
@device.send(@playlist)
|
58
|
+
@playlist.name = @playlist_name
|
59
|
+
end
|
60
|
+
|
61
|
+
@playlist
|
62
|
+
end
|
63
|
+
|
64
|
+
def default_configuration
|
65
|
+
[ "feeds" => [] ]
|
66
|
+
end
|
67
|
+
|
68
|
+
def configuration
|
69
|
+
return @configuration unless @configuration.nil?
|
70
|
+
|
71
|
+
Dir.mkdir(@configuration_dir) unless File.exists?(@configuration_dir)
|
72
|
+
if File.exists?(@configuration_file)
|
73
|
+
File.open(@configuration_file, 'r') do |f|
|
74
|
+
@configuration = YAML.load(f)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
@configuration = default_configuration
|
78
|
+
end
|
79
|
+
@configuration
|
80
|
+
end
|
81
|
+
|
82
|
+
def feeds
|
83
|
+
@feeds ||= configuration["feeds"].keys.map do |k|
|
84
|
+
options = configuration["feeds"][k]
|
85
|
+
options["directory"] = File.join(@configuration_dir, 'feeds', k) if options["directory"].nil?
|
86
|
+
Feed.new(k, options)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def update
|
91
|
+
items = feeds.inject([]) { |ary, feed| ary + feed.items }
|
92
|
+
items.sort! { |a, b| a.pubdate <=> b.pubdate }
|
93
|
+
|
94
|
+
|
95
|
+
playlist.each do |track|
|
96
|
+
in_rss_list = nil
|
97
|
+
items.each do |item|
|
98
|
+
if File.basename(item.enclosure.url) == track.filename
|
99
|
+
in_rss_list = item
|
100
|
+
break
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# remove from device the tracks which aren't in rss or which are too old
|
105
|
+
if in_rss_list.nil? or in_rss_list.pubdate.to_time < in_rss_list.keep.days.ago
|
106
|
+
puts "removing #{track.filename}"
|
107
|
+
playlist.delete(track)
|
108
|
+
@device.delete(track)
|
109
|
+
else
|
110
|
+
puts "skipping #{track.filename}"
|
111
|
+
items.delete(in_rss_list)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# download missing items
|
116
|
+
items.each do |item|
|
117
|
+
puts "downloading #{File.basename(item.enclosure.url)}"
|
118
|
+
uri = URI.parse(item.enclosure.url)
|
119
|
+
|
120
|
+
# connect to server
|
121
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
122
|
+
|
123
|
+
# big hack to access socket io
|
124
|
+
netio = http.instance_variable_get(:@socket)
|
125
|
+
if netio.respond_to?(:io)
|
126
|
+
sock = netio.io # 1.9
|
127
|
+
else
|
128
|
+
sock = netio.instance_variable_get(:@socket) # 1.8
|
129
|
+
end
|
130
|
+
|
131
|
+
# execute request
|
132
|
+
request = Net::HTTP::Get.new(uri.path)
|
133
|
+
request["host"] = (uri.port ? "#{uri.host}:#{uri.port}" : uri.host)
|
134
|
+
request.exec(sock, Net::HTTP::HTTPVersion, request.path)
|
135
|
+
|
136
|
+
# fetch headers
|
137
|
+
while (str = sock.readline) != "\r\n"
|
138
|
+
if str.match(/^Content-Length:\s*(\d+)\s*$/)
|
139
|
+
length = $1.to_i
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# store object
|
144
|
+
object = MP3Track.new
|
145
|
+
object.filename = File.basename(item.enclosure.url)
|
146
|
+
object.compressed_size = length
|
147
|
+
@device.send(object, sock) do |written, total|
|
148
|
+
printf("%02u%% %u/%u\r", written * 100/total, written, total)
|
149
|
+
end
|
150
|
+
printf("\n")
|
151
|
+
object.name = item.title
|
152
|
+
object.artist = item.itunes_author
|
153
|
+
object.duration = item.itunes_duration * 1000
|
154
|
+
playlist << object
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module MTP
|
2
|
+
class Properties
|
3
|
+
def initialize(object)
|
4
|
+
@object = object
|
5
|
+
end
|
6
|
+
|
7
|
+
def clear
|
8
|
+
@supported = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def supported
|
12
|
+
return @supported unless @supported.nil?
|
13
|
+
|
14
|
+
t = @object.ph.transaction.get_object_props_supported(@object.format)
|
15
|
+
t.expect("OK")
|
16
|
+
@supported = t.data.payload.unpack("K+").first
|
17
|
+
end
|
18
|
+
|
19
|
+
def writable
|
20
|
+
supported.select { |p| Property.description(@object, p).writable? }
|
21
|
+
end
|
22
|
+
|
23
|
+
def supports?(code)
|
24
|
+
supported.include?(code)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_value(code)
|
28
|
+
code = Property.code(code)
|
29
|
+
raise UnsupportedProperty.new(@object, code) unless supports?(code)
|
30
|
+
Property.get(@object, code)
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_value(code, value)
|
34
|
+
code = Property.code(code)
|
35
|
+
raise UnsupportedProperty.new(self, code) unless supports?(code)
|
36
|
+
Property.set(@object, code, value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_missing(method, *args)
|
40
|
+
method = method.to_s
|
41
|
+
if method[-1,1] == '='
|
42
|
+
method.slice!(-1,1)
|
43
|
+
set_value(method.camelize, *args).value
|
44
|
+
else
|
45
|
+
get_value(method.camelize, *args).value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class Property
|
50
|
+
@@descriptions = {}
|
51
|
+
|
52
|
+
attr_reader :description, :code, :value
|
53
|
+
|
54
|
+
def self.description(object, code)
|
55
|
+
raise UnsupportedProperty.new(object, code) unless object.properties.supports?(code)
|
56
|
+
@@descriptions[code.to_i] ||= PropertyDescription.load(object.ph, code, object.format)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.code(code)
|
60
|
+
(code.is_a?(Datacode) ? code : Datacode.new(code, nil, MTP::Datacode::Masks::OBJECT_PROPERTY))
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.get(object, c)
|
64
|
+
raise MTPError.new("object is not stored on the device") if object.new_object?
|
65
|
+
prop = Property.new
|
66
|
+
prop.instance_eval do
|
67
|
+
@object = object
|
68
|
+
@code = Property.code(c)
|
69
|
+
@description = Property.description(object, @code)
|
70
|
+
t = object.ph.transaction.get_object_prop_value(object.id, @code)
|
71
|
+
t.expect("OK")
|
72
|
+
@value = @description.unpack(t.data.payload)
|
73
|
+
end
|
74
|
+
prop
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.set(object, c, value)
|
78
|
+
raise MTPError.new("object is not stored on the device") if object.new_object?
|
79
|
+
prop = Property.new
|
80
|
+
prop.instance_eval do
|
81
|
+
@object = object
|
82
|
+
@code = Property.code(c)
|
83
|
+
@description = Property.description(object, @code)
|
84
|
+
t = object.ph.transaction
|
85
|
+
t.data = Data.new(@description.pack(value))
|
86
|
+
t.set_object_prop_value(object.id, @code)
|
87
|
+
t.expect("OK")
|
88
|
+
@value = value
|
89
|
+
end
|
90
|
+
prop
|
91
|
+
end
|
92
|
+
|
93
|
+
def writable?
|
94
|
+
Property.description(@object, @code).writable?
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class PropertyDescription
|
99
|
+
attr_reader :datatype, :form
|
100
|
+
def self.load(ph, code, format)
|
101
|
+
prop_desc = PropertyDescription.new
|
102
|
+
t = ph.transaction.get_object_prop_desc(code, format)
|
103
|
+
t.expect("OK")
|
104
|
+
prop_desc.instance_eval do
|
105
|
+
@format = format
|
106
|
+
@code, @datatype, @access, str = t.data.payload.unpack("KSCa*")
|
107
|
+
@dts = Datacode.new(@datatype, MTP::DATA_TYPE_PACK)
|
108
|
+
@datatype = Datacode.new(@datatype, MTP::DATA_TYPES)
|
109
|
+
@default_value, group_code, form_flag, form =
|
110
|
+
str.unpack("#{@dts.name}VCa*")
|
111
|
+
@form = Forms.load(form_flag, form, @dts)
|
112
|
+
end
|
113
|
+
prop_desc
|
114
|
+
end
|
115
|
+
|
116
|
+
def unpack(raw_value)
|
117
|
+
if raw_value.empty?
|
118
|
+
nil
|
119
|
+
else
|
120
|
+
@form.unpack(@datatype, @dts, raw_value)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def pack(value)
|
125
|
+
@form.pack(@datatype, @dts, value)
|
126
|
+
end
|
127
|
+
|
128
|
+
def writable?
|
129
|
+
@access == 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
module Forms
|
134
|
+
def self.load(form_flag, form, dts)
|
135
|
+
FORM_FLAGS[form_flag].new(form, dts)
|
136
|
+
end
|
137
|
+
|
138
|
+
class None
|
139
|
+
def initialize(form, dts)
|
140
|
+
end
|
141
|
+
|
142
|
+
def unpack(datatype, dts, raw_value)
|
143
|
+
if datatype.name == "String"
|
144
|
+
raw_value.unpack("J").first
|
145
|
+
elsif (datatype & 0x4000) == 0x4000
|
146
|
+
raw_value.unpack("#{dts}+").first
|
147
|
+
else
|
148
|
+
raw_value.unpack("#{dts}").first
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def pack(datatype, dts, value)
|
153
|
+
if datatype.name == "String"
|
154
|
+
[ value ].pack("J")
|
155
|
+
elsif (datatype & 0x4000) == 0x4000
|
156
|
+
[ value ].pack("#{dts}+")
|
157
|
+
else
|
158
|
+
[ value ].pack("#{dts}")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Range < None
|
164
|
+
attr_reader :minimum_value, :maximum_value, :step_size
|
165
|
+
def initialize(form, dts)
|
166
|
+
@minimum_value, @maximum_value, @step_size = form.unpack(dts.name * 3)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class Enumeration < None
|
171
|
+
attr_reader :values
|
172
|
+
def initialize(form, dts)
|
173
|
+
num, *@values = form.unpack("S#{dts.name}*")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class DateTime < None
|
178
|
+
def initialize(form, dts)
|
179
|
+
end
|
180
|
+
|
181
|
+
def unpack(datatype, dts, raw_value)
|
182
|
+
raw_value.unpack("D").first
|
183
|
+
end
|
184
|
+
|
185
|
+
def pack(datatype, dts, value)
|
186
|
+
[ value ].pack("D")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class FixedLengthArray < None
|
191
|
+
attr_reader :size
|
192
|
+
def initialize(form, dts)
|
193
|
+
@size = form.unpack("S")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class RegularExpression < None
|
198
|
+
attr_reader :regexp
|
199
|
+
def initialize(form, dts)
|
200
|
+
@regexp = Regexp.new(form.unpack("J")) unless form.empty?
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class ByteArray < None
|
205
|
+
attr_reader :max_length
|
206
|
+
def initialize(form, dts)
|
207
|
+
@max_length = form.unpack("S")
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class LongString < None
|
212
|
+
attr_reader :max_length
|
213
|
+
def initialize(form, dts)
|
214
|
+
@max_length = form.unpack("S")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
FORM_FLAGS = {
|
219
|
+
0x00 => None, 0x01 => Range, 0x02 => Enumeration, 0x03 => DateTime,
|
220
|
+
0x04 => FixedLengthArray, 0x05 => RegularExpression, 0x06 => ByteArray, 0xFF => String
|
221
|
+
}
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
end
|