cassandra-driver 3.0.0.rc.2-java → 3.0.2-java
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/README.md +51 -39
- data/lib/cassandra.rb +74 -32
- data/lib/cassandra/auth.rb +3 -1
- data/lib/cassandra/cluster.rb +14 -3
- data/lib/cassandra/cluster/client.rb +98 -62
- data/lib/cassandra/cluster/connector.rb +7 -9
- data/lib/cassandra/cluster/metadata.rb +1 -1
- data/lib/cassandra/cluster/options.rb +19 -7
- data/lib/cassandra/cluster/schema/cql_type_parser.rb +3 -0
- data/lib/cassandra/cluster/schema/fetchers.rb +1 -1
- data/lib/cassandra/custom_data.rb +51 -0
- data/lib/cassandra/driver.rb +23 -20
- data/lib/cassandra/execution/options.rb +1 -1
- data/lib/cassandra/index.rb +22 -8
- data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +19 -1
- data/lib/cassandra/load_balancing/policies/round_robin.rb +7 -0
- data/lib/cassandra/load_balancing/policies/token_aware.rb +7 -0
- data/lib/cassandra/load_balancing/policies/white_list.rb +7 -0
- data/lib/cassandra/protocol.rb +7 -0
- data/lib/cassandra/protocol/coder.rb +28 -8
- data/lib/cassandra/protocol/cql_byte_buffer.rb +21 -4
- data/lib/cassandra/protocol/cql_protocol_handler.rb +3 -2
- data/lib/cassandra/protocol/requests/batch_request.rb +1 -1
- data/lib/cassandra/protocol/requests/execute_request.rb +1 -1
- data/lib/cassandra/protocol/requests/query_request.rb +1 -1
- data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +4 -2
- data/lib/cassandra/protocol/v1.rb +2 -1
- data/lib/cassandra/protocol/v3.rb +2 -1
- data/lib/cassandra/protocol/v4.rb +5 -3
- data/lib/cassandra/result.rb +2 -2
- data/lib/cassandra/session.rb +10 -15
- data/lib/cassandra/statement.rb +5 -0
- data/lib/cassandra/statements/batch.rb +6 -0
- data/lib/cassandra/statements/bound.rb +5 -0
- data/lib/cassandra/statements/prepared.rb +11 -2
- data/lib/cassandra/statements/simple.rb +5 -0
- data/lib/cassandra/statements/void.rb +5 -0
- data/lib/cassandra/table.rb +5 -5
- data/lib/cassandra/timestamp_generator.rb +37 -0
- data/lib/cassandra/timestamp_generator/simple.rb +38 -0
- data/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +58 -0
- data/lib/cassandra/tuple.rb +2 -2
- data/lib/cassandra/types.rb +2 -2
- data/lib/cassandra/util.rb +136 -2
- data/lib/cassandra/version.rb +1 -1
- data/lib/cassandra_murmur3.jar +0 -0
- metadata +8 -4
@@ -92,6 +92,13 @@ module Cassandra
|
|
92
92
|
"Not enough bytes available to decode a double: #{e.message}", e.backtrace
|
93
93
|
end
|
94
94
|
|
95
|
+
def read_double_le
|
96
|
+
read(8).unpack(Formats::DOUBLE_FORMAT_LE).first
|
97
|
+
rescue RangeError => e
|
98
|
+
raise Errors::DecodingError,
|
99
|
+
"Not enough bytes available to decode a double: #{e.message}", e.backtrace
|
100
|
+
end
|
101
|
+
|
95
102
|
def read_float
|
96
103
|
read(4).unpack(Formats::FLOAT_FORMAT).first
|
97
104
|
rescue RangeError => e
|
@@ -108,6 +115,13 @@ module Cassandra
|
|
108
115
|
"Not enough bytes available to decode an int: #{e.message}", e.backtrace
|
109
116
|
end
|
110
117
|
|
118
|
+
def read_unsigned_int_le
|
119
|
+
read(4).unpack(Formats::INT_FORMAT_LE).first
|
120
|
+
rescue RangeError => e
|
121
|
+
raise Errors::DecodingError,
|
122
|
+
"Not enough bytes available to decode an int: #{e.message}", e.backtrace
|
123
|
+
end
|
124
|
+
|
111
125
|
def read_unsigned_short
|
112
126
|
read_short
|
113
127
|
rescue RangeError => e
|
@@ -115,6 +129,13 @@ module Cassandra
|
|
115
129
|
"Not enough bytes available to decode a short: #{e.message}", e.backtrace
|
116
130
|
end
|
117
131
|
|
132
|
+
def read_unsigned_short_le
|
133
|
+
read(2).unpack(Formats::SHORT_FORMAT_LE).first
|
134
|
+
rescue RangeError => e
|
135
|
+
raise Errors::DecodingError,
|
136
|
+
"Not enough bytes available to decode a short: #{e.message}", e.backtrace
|
137
|
+
end
|
138
|
+
|
118
139
|
def read_string
|
119
140
|
length = read_unsigned_short
|
120
141
|
string = read(length)
|
@@ -324,10 +345,6 @@ module Cassandra
|
|
324
345
|
self
|
325
346
|
end
|
326
347
|
|
327
|
-
def append_timestamp(timestamp)
|
328
|
-
append_long(timestamp.tv_sec * 1000000 + timestamp.tv_usec)
|
329
|
-
end
|
330
|
-
|
331
348
|
def append_long(n)
|
332
349
|
top = n >> 32
|
333
350
|
bottom = n & 0xffffffff
|
@@ -46,7 +46,8 @@ module Cassandra
|
|
46
46
|
compressor = nil,
|
47
47
|
heartbeat_interval = 30,
|
48
48
|
idle_timeout = 60,
|
49
|
-
requests_per_connection = 128
|
49
|
+
requests_per_connection = 128,
|
50
|
+
custom_type_handlers = {})
|
50
51
|
@protocol_version = protocol_version
|
51
52
|
@connection = connection
|
52
53
|
@scheduler = scheduler
|
@@ -60,7 +61,7 @@ module Cassandra
|
|
60
61
|
|
61
62
|
if protocol_version > 3
|
62
63
|
@frame_encoder = V4::Encoder.new(@compressor, protocol_version)
|
63
|
-
@frame_decoder = V4::Decoder.new(self, @compressor)
|
64
|
+
@frame_decoder = V4::Decoder.new(self, @compressor, custom_type_handlers)
|
64
65
|
elsif protocol_version > 2
|
65
66
|
@frame_encoder = V3::Encoder.new(@compressor, protocol_version)
|
66
67
|
@frame_decoder = V3::Decoder.new(self, @compressor)
|
@@ -78,7 +78,7 @@ module Cassandra
|
|
78
78
|
buffer.append_int(@page_size) if @page_size
|
79
79
|
buffer.append_bytes(@paging_state) if @paging_state
|
80
80
|
buffer.append_consistency(@serial_consistency) if @serial_consistency
|
81
|
-
buffer.
|
81
|
+
buffer.append_long(@timestamp) if protocol_version > 2 && @timestamp
|
82
82
|
else
|
83
83
|
encoder.write_parameters(buffer, @values, @metadata)
|
84
84
|
buffer.append_consistency(@consistency)
|
@@ -71,7 +71,7 @@ module Cassandra
|
|
71
71
|
buffer.append_int(@page_size) if @page_size
|
72
72
|
buffer.append_bytes(@paging_state) if @paging_state
|
73
73
|
buffer.append_consistency(@serial_consistency) if @serial_consistency
|
74
|
-
buffer.
|
74
|
+
buffer.append_long(@timestamp) if protocol_version > 2 && @timestamp
|
75
75
|
end
|
76
76
|
buffer
|
77
77
|
end
|
@@ -24,17 +24,19 @@ module Cassandra
|
|
24
24
|
protocol_version,
|
25
25
|
raw_rows,
|
26
26
|
paging_state,
|
27
|
-
trace_id
|
27
|
+
trace_id,
|
28
|
+
custom_type_handlers = nil)
|
28
29
|
super(custom_payload, warnings, nil, nil, paging_state, trace_id)
|
29
30
|
@protocol_version = protocol_version
|
30
31
|
@raw_rows = raw_rows
|
32
|
+
@custom_type_handlers = custom_type_handlers
|
31
33
|
end
|
32
34
|
|
33
35
|
def materialize(metadata)
|
34
36
|
@metadata = metadata
|
35
37
|
|
36
38
|
@rows = if @protocol_version == 4
|
37
|
-
Coder.read_values_v4(@raw_rows, @metadata)
|
39
|
+
Coder.read_values_v4(@raw_rows, @metadata, @custom_type_handlers)
|
38
40
|
elsif @protocol_version == 3
|
39
41
|
Coder.read_values_v3(@raw_rows, @metadata)
|
40
42
|
else
|
@@ -59,7 +59,7 @@ module Cassandra
|
|
59
59
|
end
|
60
60
|
|
61
61
|
class Decoder
|
62
|
-
def initialize(handler, compressor = nil)
|
62
|
+
def initialize(handler, compressor = nil, custom_type_handlers = {})
|
63
63
|
@handler = handler
|
64
64
|
@compressor = compressor
|
65
65
|
@state = :initial
|
@@ -68,6 +68,7 @@ module Cassandra
|
|
68
68
|
@code = nil
|
69
69
|
@length = nil
|
70
70
|
@buffer = CqlByteBuffer.new
|
71
|
+
@custom_type_handlers = custom_type_handlers
|
71
72
|
end
|
72
73
|
|
73
74
|
def <<(data)
|
@@ -325,11 +326,12 @@ module Cassandra
|
|
325
326
|
protocol_version,
|
326
327
|
remaining_bytes,
|
327
328
|
paging_state,
|
328
|
-
trace_id
|
329
|
+
trace_id,
|
330
|
+
@custom_type_handlers)
|
329
331
|
else
|
330
332
|
RowsResultResponse.new(custom_payload,
|
331
333
|
warnings,
|
332
|
-
Coder.read_values_v4(buffer, column_specs),
|
334
|
+
Coder.read_values_v4(buffer, column_specs, @custom_type_handlers),
|
333
335
|
column_specs,
|
334
336
|
paging_state,
|
335
337
|
trace_id)
|
data/lib/cassandra/result.rb
CHANGED
@@ -76,8 +76,8 @@ module Cassandra
|
|
76
76
|
#
|
77
77
|
# @note `:paging_state` option will be ignored.
|
78
78
|
#
|
79
|
-
# @return [Cassandra::Future<Cassandra::Result
|
80
|
-
#
|
79
|
+
# @return [Cassandra::Future<Cassandra::Result>] a future that resolves to a new Result if there is a new page,
|
80
|
+
# `nil` otherwise.
|
81
81
|
#
|
82
82
|
# @see Cassandra::Session#execute
|
83
83
|
def next_page_async(options = nil)
|
data/lib/cassandra/session.rb
CHANGED
@@ -89,20 +89,13 @@ module Cassandra
|
|
89
89
|
|
90
90
|
case statement
|
91
91
|
when ::String
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
when
|
98
|
-
@client
|
99
|
-
when Statements::Prepared
|
100
|
-
@client.execute(statement.bind(options.arguments), options)
|
101
|
-
when Statements::Bound
|
102
|
-
@client.execute(statement, options)
|
103
|
-
when Statements::Batch
|
104
|
-
Util.assert_not_empty(statement.statements) { 'batch cannot be empty' }
|
105
|
-
@client.batch(statement, options)
|
92
|
+
Statements::Simple.new(statement,
|
93
|
+
options.arguments,
|
94
|
+
options.type_hints,
|
95
|
+
options.idempotent?).accept(@client,
|
96
|
+
options)
|
97
|
+
when Statement
|
98
|
+
statement.accept(@client, options)
|
106
99
|
else
|
107
100
|
@futures.error(::ArgumentError.new("unsupported statement #{statement.inspect}"))
|
108
101
|
end
|
@@ -238,7 +231,9 @@ module Cassandra
|
|
238
231
|
|
239
232
|
# @private
|
240
233
|
def inspect
|
241
|
-
"#<#{self.class.name}:0x#{object_id.to_s(16)}
|
234
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} " \
|
235
|
+
"@keyspace=#{keyspace.inspect}, " \
|
236
|
+
"@options=#{@options.inspect}>"
|
242
237
|
end
|
243
238
|
end
|
244
239
|
end
|
data/lib/cassandra/statement.rb
CHANGED
@@ -113,6 +113,12 @@ module Cassandra
|
|
113
113
|
self
|
114
114
|
end
|
115
115
|
|
116
|
+
# @private
|
117
|
+
def accept(client, options)
|
118
|
+
Util.assert_not_empty(statements) { 'batch cannot be empty' }
|
119
|
+
client.batch(self, options)
|
120
|
+
end
|
121
|
+
|
116
122
|
# Determines whether or not the statement is safe to retry on timeout
|
117
123
|
# Batches are idempotent only when all statements in a batch are.
|
118
124
|
# @return [Boolean] whether the statement is safe to retry on timeout
|
@@ -46,6 +46,11 @@ module Cassandra
|
|
46
46
|
@idempotent = idempotent
|
47
47
|
end
|
48
48
|
|
49
|
+
# @private
|
50
|
+
def accept(client, options)
|
51
|
+
client.execute(self, options)
|
52
|
+
end
|
53
|
+
|
49
54
|
# @return [String] a CLI-friendly bound statement representation
|
50
55
|
def inspect
|
51
56
|
"#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect} " \
|
@@ -155,6 +155,11 @@ module Cassandra
|
|
155
155
|
nil)
|
156
156
|
end
|
157
157
|
|
158
|
+
# @private
|
159
|
+
def accept(client, options)
|
160
|
+
client.execute(bind(options.arguments), options)
|
161
|
+
end
|
162
|
+
|
158
163
|
# @return [String] a CLI-friendly prepared statement representation
|
159
164
|
def inspect
|
160
165
|
"#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect}>"
|
@@ -180,7 +185,9 @@ module Cassandra
|
|
180
185
|
'the partition key and must be present.'
|
181
186
|
end
|
182
187
|
|
183
|
-
if @connection_options.protocol_version >=
|
188
|
+
if @connection_options.protocol_version >= 4
|
189
|
+
Protocol::Coder.write_value_v4(buffer, value, type)
|
190
|
+
elsif @connection_options.protocol_version >= 3
|
184
191
|
Protocol::Coder.write_value_v3(buffer, value, type)
|
185
192
|
else
|
186
193
|
Protocol::Coder.write_value_v1(buffer, value, type)
|
@@ -200,7 +207,9 @@ module Cassandra
|
|
200
207
|
'the partition key and must be present.'
|
201
208
|
end
|
202
209
|
|
203
|
-
if @connection_options.protocol_version >=
|
210
|
+
if @connection_options.protocol_version >= 4
|
211
|
+
Protocol::Coder.write_value_v4(buf, value, type)
|
212
|
+
elsif @connection_options.protocol_version >= 3
|
204
213
|
Protocol::Coder.write_value_v3(buf, value, type)
|
205
214
|
else
|
206
215
|
Protocol::Coder.write_value_v1(buf, value, type)
|
@@ -93,6 +93,11 @@ module Cassandra
|
|
93
93
|
@idempotent = idempotent
|
94
94
|
end
|
95
95
|
|
96
|
+
# @private
|
97
|
+
def accept(client, options)
|
98
|
+
client.query(self, options)
|
99
|
+
end
|
100
|
+
|
96
101
|
# @return [String] a CLI-friendly simple statement representation
|
97
102
|
def inspect
|
98
103
|
"#<#{self.class.name}:0x#{object_id.to_s(16)} @cql=#{@cql.inspect} " \
|
data/lib/cassandra/table.rb
CHANGED
@@ -80,14 +80,14 @@ module Cassandra
|
|
80
80
|
else
|
81
81
|
cql << ",\n"
|
82
82
|
end
|
83
|
-
cql << " #{column.name} #{type_to_cql(column.type, column.frozen?)}"
|
83
|
+
cql << " #{Util.escape_name(column.name)} #{type_to_cql(column.type, column.frozen?)}"
|
84
84
|
cql << ' PRIMARY KEY' if primary_key && column.name == primary_key
|
85
85
|
end
|
86
86
|
|
87
87
|
unless primary_key
|
88
88
|
cql << ",\n PRIMARY KEY ("
|
89
89
|
if @partition_key.one?
|
90
|
-
cql << @partition_key.first.name
|
90
|
+
cql << Util.escape_name(@partition_key.first.name)
|
91
91
|
else
|
92
92
|
cql << '('
|
93
93
|
first = true
|
@@ -97,12 +97,12 @@ module Cassandra
|
|
97
97
|
else
|
98
98
|
cql << ', '
|
99
99
|
end
|
100
|
-
cql << column.name
|
100
|
+
cql << Util.escape_name(column.name)
|
101
101
|
end
|
102
102
|
cql << ')'
|
103
103
|
end
|
104
104
|
@clustering_columns.each do |column|
|
105
|
-
cql << ", #{column.name}"
|
105
|
+
cql << ", #{Util.escape_name(column.name)}"
|
106
106
|
end
|
107
107
|
cql << ')'
|
108
108
|
end
|
@@ -118,7 +118,7 @@ module Cassandra
|
|
118
118
|
else
|
119
119
|
cql << ', '
|
120
120
|
end
|
121
|
-
cql << "#{column.name} #{order.to_s.upcase}"
|
121
|
+
cql << "#{Util.escape_name(column.name)} #{order.to_s.upcase}"
|
122
122
|
end
|
123
123
|
cql << ")\n AND "
|
124
124
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright 2013-2016 DataStax, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#++
|
18
|
+
|
19
|
+
module Cassandra
|
20
|
+
# A generator is used to create client-timestamps (in the form of long integers) to send with C* requests when
|
21
|
+
# the `:client_timestamps` cluster option is set to true.
|
22
|
+
#
|
23
|
+
# @abstract A timestamp generator given to {Cassandra.cluster} doesn't need to include this module, but needs to
|
24
|
+
# implement the same methods. This module exists only for documentation purposes.
|
25
|
+
module TimestampGenerator
|
26
|
+
# Create a new timestamp, as a 64-bit integer. Calls must return monotonically increasing values.
|
27
|
+
#
|
28
|
+
# @return [Integer] an integer representing a timestamp in microseconds.
|
29
|
+
# @raise [NotImplementedError] if a class including this module does not define this method.
|
30
|
+
def next
|
31
|
+
raise NotImplementedError, "#{self.class} class must implement the 'next' method"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'cassandra/timestamp_generator/ticking_on_duplicate'
|
37
|
+
require 'cassandra/timestamp_generator/simple'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright 2013-2016 DataStax, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#++
|
18
|
+
|
19
|
+
module Cassandra
|
20
|
+
module TimestampGenerator
|
21
|
+
# Generate long integer timestamps from current time. This implementation relies on the {::Time} class to return
|
22
|
+
# microsecond precision time.
|
23
|
+
# @note It is not appropriate for use with JRuby because its {::Time#now} returns millisecond precision time.
|
24
|
+
class Simple
|
25
|
+
include TimestampGenerator
|
26
|
+
|
27
|
+
# Create a new timestamp, as a 64-bit integer. This is just a wrapper around Time::now.
|
28
|
+
#
|
29
|
+
# @return [Integer] an integer representing a timestamp in microseconds.
|
30
|
+
def next
|
31
|
+
# Use Time.now, which has microsecond precision on MRI (and probably Rubinius) to make an int representing
|
32
|
+
# client timestamp in protocol requests.
|
33
|
+
timestamp = ::Time.now
|
34
|
+
timestamp.tv_sec * 1000000 + timestamp.tv_usec
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright 2013-2016 DataStax, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#++
|
18
|
+
|
19
|
+
module Cassandra
|
20
|
+
module TimestampGenerator
|
21
|
+
# In JRuby, {::Time} has millisecond precision. We require client timestamps to have microsecond precision to
|
22
|
+
# minimize clashes in C*. This generator keeps track of the last generated timestamp, and if the current-time
|
23
|
+
# is within the same millisecond as the last, it fills the microsecond portion of the new timestamp with the
|
24
|
+
# value of an incrementing counter.
|
25
|
+
#
|
26
|
+
# For example, if the generator triggers twice at time 12345678000 (microsecond granularity, but ms precisions
|
27
|
+
# as shown by 0's for the three least-significant digits), it'll return 12345678000 and 12345678001.
|
28
|
+
class TickingOnDuplicate
|
29
|
+
include MonitorMixin
|
30
|
+
include TimestampGenerator
|
31
|
+
|
32
|
+
# @private
|
33
|
+
def initialize
|
34
|
+
mon_initialize
|
35
|
+
@last = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create a new timestamp, as a 64-bit integer.
|
39
|
+
#
|
40
|
+
# @return [Integer] an integer representing a timestamp in microseconds.
|
41
|
+
def next
|
42
|
+
now = ::Time.now
|
43
|
+
now_millis = now.tv_sec * 1000 + now.tv_usec / 1000
|
44
|
+
synchronize do
|
45
|
+
millis = @last / 1000
|
46
|
+
counter = @last % 1000
|
47
|
+
if millis >= now_millis
|
48
|
+
counter += 1
|
49
|
+
else
|
50
|
+
millis = now_millis
|
51
|
+
counter = 0
|
52
|
+
end
|
53
|
+
@last = millis * 1000 + counter
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|