rubymtp 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|