uuid 1.0.4 → 2.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.
- data/CHANGELOG +32 -19
- data/MIT-LICENSE +1 -1
- data/README.rdoc +103 -0
- data/Rakefile +65 -185
- data/lib/uuid.rb +212 -326
- data/test/test-uuid.rb +82 -52
- metadata +53 -36
- data/README +0 -81
- data/bin/uuid-setup +0 -7
data/CHANGELOG
CHANGED
@@ -1,19 +1,32 @@
|
|
1
|
-
|
2
|
-
* Changed:
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
* Changed:
|
11
|
-
|
12
|
-
|
13
|
-
1.0.
|
14
|
-
*
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
1.0.
|
19
|
-
*
|
1
|
+
2.0.0 (2008-08-28)
|
2
|
+
* Changed: API. UUID.generate still works as it always did, but the rest of
|
3
|
+
the API is brand spanking new, so if you rely on anything besides
|
4
|
+
UUID.generate, or just curious, check out the rdocs.
|
5
|
+
* Changed: uuid.state replaced by ruby-uuid file. By default stored in
|
6
|
+
/var/tmp, or if that path is not accessible, as .ruby-uuid in the
|
7
|
+
home directory.
|
8
|
+
* Changed: ruby-uuid is now stored as binary file (faster read/write), if you
|
9
|
+
need to have a peek, open irb and type UUID.new.inspect.
|
10
|
+
* Changed: Source code and documentation for this release hosted on the
|
11
|
+
wonderful Github: http://github.com/assaf/uuid
|
12
|
+
|
13
|
+
1.0.4 (2007-08-28)
|
14
|
+
* Changed: By default creates the uuid.state file in the working directory,
|
15
|
+
not in the installation directory, which requires sudo privileges
|
16
|
+
(e.g. gem).
|
17
|
+
|
18
|
+
1.0.3 (2006-11-08)
|
19
|
+
* Fixed: Work around YAML bug in serializing that occurs when MAC address
|
20
|
+
consists only of decimal digits. Credit: ebuprofen"
|
21
|
+
|
22
|
+
1.0.2 (2006-08-19)
|
23
|
+
* Changed: Constants are not conditionally defined (removes warnings when
|
24
|
+
using in Rails.
|
25
|
+
|
26
|
+
1.0.1 (2006-07-27)
|
27
|
+
* Added: Regular expressions to test if a string is a UUID.
|
28
|
+
* Changed: When used in ActiveRecord, adds as callback instead of overriding
|
29
|
+
save.
|
30
|
+
|
31
|
+
1.0.0 (2005-11-20)
|
32
|
+
* Changed: Separated form reliable-msg into its own package.
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
= UUID Generator
|
2
|
+
|
3
|
+
Generates universally unique identifiers (UUIDs) for use in distributed
|
4
|
+
applications. Based on {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
|
5
|
+
|
6
|
+
|
7
|
+
== Generating UUIDs
|
8
|
+
|
9
|
+
Call #generate to generate a new UUID. The method returns a string in one of
|
10
|
+
three formats. The default format is 36 characters long, and contains the 32
|
11
|
+
hexadecimal octets and hyphens separating the various value parts. The
|
12
|
+
<tt>:compact</tt> format omits the hyphens, while the <tt>:urn</tt> format
|
13
|
+
adds the <tt>:urn:uuid</tt> prefix.
|
14
|
+
|
15
|
+
For example:
|
16
|
+
|
17
|
+
uuid = UUID.new
|
18
|
+
10.times do
|
19
|
+
p uuid.generate
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
== UUIDs in Brief
|
24
|
+
|
25
|
+
UUID (universally unique identifier) are guaranteed to be unique across time
|
26
|
+
and space.
|
27
|
+
|
28
|
+
A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
|
29
|
+
sequence number and a 48-bit node identifier.
|
30
|
+
|
31
|
+
The time value is taken from the system clock, and is monotonically
|
32
|
+
incrementing. However, since it is possible to set the system clock
|
33
|
+
backward, a sequence number is added. The sequence number is incremented
|
34
|
+
each time the UUID generator is started. The combination guarantees that
|
35
|
+
identifiers created on the same machine are unique with a high degree of
|
36
|
+
probability.
|
37
|
+
|
38
|
+
Note that due to the structure of the UUID and the use of sequence number,
|
39
|
+
there is no guarantee that UUID values themselves are monotonically
|
40
|
+
incrementing. The UUID value cannot itself be used to sort based on order
|
41
|
+
of creation.
|
42
|
+
|
43
|
+
To guarantee that UUIDs are unique across all machines in the network,
|
44
|
+
the IEEE 802 MAC address of the machine's network interface card is used as
|
45
|
+
the node identifier.
|
46
|
+
|
47
|
+
For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
|
48
|
+
|
49
|
+
|
50
|
+
== UUID State File
|
51
|
+
|
52
|
+
The UUID generator uses a state file to hold the MAC address and sequence
|
53
|
+
number.
|
54
|
+
|
55
|
+
The MAC address is used to generate identifiers that are unique to your
|
56
|
+
machine, preventing conflicts in distributed applications. The MAC
|
57
|
+
address is six bytes (48 bit) long. It is automatically extracted from
|
58
|
+
one of the network cards on your machine.
|
59
|
+
|
60
|
+
The sequence number is incremented each time the UUID generator is
|
61
|
+
first used by the application, to prevent multiple processes from
|
62
|
+
generating the same set of identifiers, and deal with changes to the
|
63
|
+
system clock.
|
64
|
+
|
65
|
+
The UUID state file is created in <tt>/var/tmp/ruby-uuid</tt> or the Windows
|
66
|
+
common application data directory using mode 0644. If that directory is not
|
67
|
+
writable, the file is created as <tt>.ruby-uuid</tt> in the home directory.
|
68
|
+
If you need to create the file with a different mode, use UUID#state_file
|
69
|
+
before running the UUID generator.
|
70
|
+
|
71
|
+
State files are not portable across machines.
|
72
|
+
|
73
|
+
|
74
|
+
== Latest and Greatest
|
75
|
+
|
76
|
+
Stable release are made through RubyForge, to upgrade simply:
|
77
|
+
|
78
|
+
gem upgrade uuid
|
79
|
+
|
80
|
+
You can subscribe for notifications of new releases through the reliable-msg
|
81
|
+
project page at http://rubyforge.org/projects/reliable-msg
|
82
|
+
|
83
|
+
Source code and documentation hosted on Github:
|
84
|
+
|
85
|
+
http://github.com/assaf/uuid
|
86
|
+
|
87
|
+
To get UUID from source:
|
88
|
+
|
89
|
+
git clone git://github.com/assaf/uuid.git
|
90
|
+
|
91
|
+
|
92
|
+
== Change Log
|
93
|
+
|
94
|
+
:include: CHANGELOG
|
95
|
+
|
96
|
+
|
97
|
+
== License
|
98
|
+
|
99
|
+
This package is licensed under the MIT license and/or the {Creative
|
100
|
+
Commons Attribution-ShareAlike}[http://creativecommons.org/licenses/by-sa/2.5/legalcode].
|
101
|
+
|
102
|
+
:include: MIT-LICENSE
|
103
|
+
|
data/Rakefile
CHANGED
@@ -1,220 +1,100 @@
|
|
1
1
|
# Adapted from the rake Rakefile.
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
|
8
|
-
|
9
|
-
# Create the GEM package.
|
10
|
-
spec = Gem::Specification.new do |spec|
|
11
|
-
spec.name = "uuid"
|
12
|
-
spec.version = File.read(__FILE__.pathmap("%d/#{VERSION_FILE}")).scan(/VERSION\s*=\s*(['"])(.*)\1/)[0][1]
|
13
|
-
spec.summary = "UUID generator"
|
14
|
-
spec.description = <<-EOF
|
15
|
-
UUID generator for producing universally unique identifiers based
|
16
|
-
on RFC 4122 (http://www.ietf.org/rfc/rfc4122.txt).
|
17
|
-
EOF
|
18
|
-
spec.author = "Assaf Arkin"
|
19
|
-
spec.email = "assaf@labnotes.org"
|
20
|
-
spec.homepage = "http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/UuidGenerator"
|
21
|
-
spec.files = FileList["{bin,test,lib,docs}/**/*", "README", "MIT-LICENSE", "Rakefile", "CHANGELOG"].to_a
|
22
|
-
spec.require_path = "lib"
|
23
|
-
spec.autorequire = "uuid.rb"
|
24
|
-
spec.bindir = "bin"
|
25
|
-
spec.executables = ["uuid-setup"]
|
26
|
-
spec.default_executable = "uuid-setup"
|
27
|
-
spec.has_rdoc = true
|
28
|
-
spec.rdoc_options << "--main" << "README" << "--title" << "UUID generator" << "--line-numbers"
|
29
|
-
spec.extra_rdoc_files = ["README"]
|
30
|
-
spec.rubyforge_project = "reliable-msg"
|
31
|
-
end
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rubygems/source_info_cache'
|
8
|
+
|
32
9
|
|
10
|
+
spec = Gem::Specification.load(File.join(File.dirname(__FILE__), 'uuid.gemspec'))
|
33
11
|
|
34
12
|
desc "Default Task"
|
35
|
-
task
|
13
|
+
task 'default' => ['test', 'rdoc']
|
14
|
+
|
15
|
+
|
16
|
+
desc "If you're building from sources, run this task first to setup the necessary dependencies"
|
17
|
+
task 'setup' do
|
18
|
+
windows = Config::CONFIG['host_os'] =~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i
|
19
|
+
rb_bin = File.expand_path(Config::CONFIG['ruby_install_name'], Config::CONFIG['bindir'])
|
20
|
+
spec.dependencies.select { |dep| Gem::SourceIndex.from_installed_gems.search(dep).empty? }.each do |missing|
|
21
|
+
dep = Gem::Dependency.new(missing.name, missing.version_requirements)
|
22
|
+
spec = Gem::SourceInfoCache.search(dep, true, true).last
|
23
|
+
fail "#{dep} not found in local or remote repository!" unless spec
|
24
|
+
puts "Installing #{spec.full_name} ..."
|
25
|
+
args = [rb_bin, '-S', 'gem', 'install', spec.name, '-v', spec.version.to_s]
|
26
|
+
args.unshift('sudo') unless windows || ENV['GEM_HOME']
|
27
|
+
sh args.map{ |a| a.inspect }.join(' ')
|
28
|
+
end
|
29
|
+
end
|
36
30
|
|
37
31
|
|
38
32
|
desc "Run all test cases"
|
39
33
|
Rake::TestTask.new do |test|
|
40
34
|
test.verbose = true
|
41
|
-
test.test_files = [
|
42
|
-
|
35
|
+
test.test_files = ['test/*.rb']
|
36
|
+
test.warning = true
|
43
37
|
end
|
44
38
|
|
45
39
|
# Create the documentation.
|
46
40
|
Rake::RDocTask.new do |rdoc|
|
47
|
-
rdoc.main =
|
48
|
-
rdoc.rdoc_files.include(
|
41
|
+
rdoc.main = 'README.rdoc'
|
42
|
+
rdoc.rdoc_files.include('README.rdoc', 'lib/**/*.rb')
|
49
43
|
rdoc.title = "UUID generator"
|
50
44
|
end
|
51
45
|
|
46
|
+
|
52
47
|
gem = Rake::GemPackageTask.new(spec) do |pkg|
|
53
48
|
pkg.need_tar = true
|
54
49
|
pkg.need_zip = true
|
55
50
|
end
|
56
51
|
|
57
52
|
desc "Install the package locally"
|
58
|
-
task
|
59
|
-
|
53
|
+
task 'install'=>['setup', 'package'] do |task|
|
54
|
+
rb_bin = File.expand_path(Config::CONFIG['ruby_install_name'], Config::CONFIG['bindir'])
|
55
|
+
args = [rb_bin, '-S', 'gem', 'install', "pkg/#{spec.name}-#{spec.version}.gem"]
|
56
|
+
windows = Config::CONFIG['host_os'] =~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i
|
57
|
+
args.unshift('sudo') unless windows || ENV['GEM_HOME']
|
58
|
+
sh args.map{ |a| a.inspect }.join(' ')
|
60
59
|
end
|
61
60
|
|
62
61
|
desc "Uninstall previously installed packaged"
|
63
|
-
task
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
task :todo do
|
70
|
-
FileList["**/*.rb"].egrep /#.*(FIXME|TODO|TBD)/
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
# --------------------------------------------------------------------
|
75
|
-
# Creating a release
|
76
|
-
|
77
|
-
|
78
|
-
namespace :upload do
|
79
|
-
task :packages=>["rake:rdoc", "rake:package"] do |task|
|
80
|
-
require 'rubyforge'
|
81
|
-
|
82
|
-
# Read the changes for this release.
|
83
|
-
pattern = /(^(\d+\.\d+(?:\.\d+)?)\s+\(\d+\/\d+\/\d+\)\s*((:?^[^\n]+\n)*))/
|
84
|
-
changelog = File.read(__FILE__.pathmap("%d/CHANGELOG"))
|
85
|
-
changes = changelog.scan(pattern).inject({}) { |hash, set| hash[set[1]] = set[2] ; hash }
|
86
|
-
current = changes[spec.version.to_s]
|
87
|
-
if !current && spec.version.to_s =~ /\.0$/
|
88
|
-
current = changes[spec.version.to_s.split(".")[0..-2].join(".")]
|
89
|
-
end
|
90
|
-
fail "No changeset found for version #{spec.version}" unless current
|
91
|
-
|
92
|
-
puts "Uploading #{spec.name} #{spec.version}"
|
93
|
-
files = %w( gem tgz zip ).map { |ext| "pkg/#{spec.name}-#{spec.version}.#{ext}" }
|
94
|
-
rubyforge = RubyForge.new
|
95
|
-
rubyforge.login
|
96
|
-
File.open(".changes", 'w'){|f| f.write(current)}
|
97
|
-
rubyforge.userconfig.merge!("release_changes" => ".changes", "preformatted" => true)
|
98
|
-
rubyforge.add_release spec.rubyforge_project.downcase, spec.name.downcase, spec.version, *files
|
99
|
-
rm ".changes"
|
100
|
-
puts "Release #{spec.version} uploaded"
|
101
|
-
end
|
62
|
+
task 'uninstall' do |task|
|
63
|
+
rb_bin = File.expand_path(Config::CONFIG['ruby_install_name'], Config::CONFIG['bindir'])
|
64
|
+
args = [rb_bin, '-S', 'gem', 'install', spec.name, '-v', spec.version.to_s]
|
65
|
+
windows = Config::CONFIG['host_os'] =~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i
|
66
|
+
args.unshift('sudo') unless windows || ENV['GEM_HOME']
|
67
|
+
sh args.map{ |a| a.inspect }.join(' ')
|
102
68
|
end
|
103
69
|
|
104
|
-
namespace :release do
|
105
|
-
task :ready? do
|
106
|
-
require 'highline'
|
107
|
-
require 'highline/import'
|
108
|
-
|
109
|
-
puts "This version: #{spec.version}"
|
110
|
-
puts
|
111
|
-
puts "Top 4 lines form CHANGELOG:"
|
112
|
-
puts File.readlines("CHANGELOG")[0..3].map { |l| " #{l}" }
|
113
|
-
puts
|
114
|
-
ask("Top-entry in CHANGELOG file includes today's date?") =~ /yes/i or
|
115
|
-
fail "Please update CHANGELOG to include the right date"
|
116
|
-
end
|
117
70
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
File.open(__FILE__.pathmap("%d/CHANGELOG"), "w") { |file| file.write "#{next_version} (Pending)\n\n#{changelog}" }
|
128
|
-
end
|
129
|
-
|
130
|
-
task :meat=>["clobber", "test", "upload:packages"]
|
131
|
-
end
|
132
|
-
|
133
|
-
desc "Upload release to RubyForge including docs, tag SVN"
|
134
|
-
task :release=>[ "release:ready?", "release:meat", "release:post" ]
|
135
|
-
|
136
|
-
=begin
|
137
|
-
|
138
|
-
# Handle version number.
|
139
|
-
class Version
|
140
|
-
|
141
|
-
PATTERN = /(\s*)VERSION.*(\d+\.\d+\.\d+)/
|
142
|
-
|
143
|
-
def initialize file, new_version
|
144
|
-
@file = file
|
145
|
-
@version = File.open @file, "r" do |file|
|
146
|
-
version = nil
|
147
|
-
file.each_line do |line|
|
148
|
-
match = line.match PATTERN
|
149
|
-
if match
|
150
|
-
version = match[2]
|
151
|
-
break
|
152
|
-
end
|
153
|
-
end
|
154
|
-
version
|
155
|
-
end
|
156
|
-
fail "Can't determine version number" unless @version
|
157
|
-
@new_version = new_version || @version
|
158
|
-
end
|
71
|
+
task 'release'=>['setup', 'test', 'package'] do
|
72
|
+
|
73
|
+
require 'rubyforge'
|
74
|
+
=begin
|
75
|
+
header = File.readlines('CHANGELOG').first
|
76
|
+
version, date = header.scan(/(\d+\.\d+\.\d+) \((\d{4}-\d{2}-\d{2})\)/).first
|
77
|
+
fail "CHANGELOG and spec version numbers do not match: #{version} != #{spec.version}" unless version == spec.version.to_s
|
78
|
+
today = Time.now.strftime('%Y-%m-%d')
|
79
|
+
fail "CHANGELOG entry not using today's date: #{date} != #{today}" unless date = today
|
159
80
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def number
|
165
|
-
@version
|
166
|
-
end
|
167
|
-
|
168
|
-
def next
|
169
|
-
@new_version
|
170
|
-
end
|
171
|
-
|
172
|
-
def update
|
173
|
-
puts "Updating to version #{@new_version}"
|
174
|
-
copy = "#{@file}.new"
|
175
|
-
open @file, "r" do |input|
|
176
|
-
open copy, "w" do |output|
|
177
|
-
input.each_line do |line|
|
178
|
-
match = line.match PATTERN
|
179
|
-
if match
|
180
|
-
output.puts "#{match[1]}VERSION = '#{@new_version}'"
|
181
|
-
else
|
182
|
-
output.puts line
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
mv copy, @file
|
188
|
-
@version = @new_version
|
81
|
+
=end
|
82
|
+
changes = File.read('CHANGELOG')[/\d+.\d+.\d+.*\n((:?^[^\n]+\n)*)/]
|
83
|
+
File.open '.changes', 'w' do |file|
|
84
|
+
file.write changes
|
189
85
|
end
|
190
86
|
|
87
|
+
puts "Uploading #{spec.name} #{spec.version}"
|
88
|
+
files = Dir['pkg/*.{gem,tgz,zip}']
|
89
|
+
rubyforge = RubyForge.new
|
90
|
+
rubyforge.configure
|
91
|
+
rubyforge.login
|
92
|
+
rubyforge.userconfig.merge! 'release_changes'=>'.changes', 'preformatted'=>true
|
93
|
+
rubyforge.add_release spec.rubyforge_project.downcase, spec.name.downcase, spec.version, *files
|
94
|
+
rm_f '.changes'
|
95
|
+
puts "Release #{spec.version} uploaded"
|
191
96
|
end
|
192
|
-
version = Version.new "lib/uuid.rb", ENV["version"]
|
193
|
-
|
194
|
-
|
195
97
|
|
196
|
-
|
197
|
-
|
198
|
-
task :release => [:test, :prerelease, :clobber, :update_version, :package] do
|
199
|
-
puts
|
200
|
-
puts "**************************************************************"
|
201
|
-
puts "* Release #{version.number} Complete."
|
202
|
-
puts "* Packages ready to upload."
|
203
|
-
puts "**************************************************************"
|
204
|
-
puts
|
205
|
-
end
|
206
|
-
|
207
|
-
task :prerelease do
|
208
|
-
if !version.changed? && ENV["reuse"] != version.number
|
209
|
-
fail "Current version is #{version.number}, must specify reuse=ver to reuse existing version"
|
210
|
-
end
|
98
|
+
task 'clobber' do
|
99
|
+
rm_f '.changes'
|
211
100
|
end
|
212
|
-
|
213
|
-
task :update_version => [:prerelease] do
|
214
|
-
if !version.changed?
|
215
|
-
puts "No version change ... skipping version update"
|
216
|
-
else
|
217
|
-
version.update
|
218
|
-
end
|
219
|
-
end
|
220
|
-
=end
|
data/lib/uuid.rb
CHANGED
@@ -2,394 +2,280 @@
|
|
2
2
|
# = uuid.rb - UUID generator
|
3
3
|
#
|
4
4
|
# Author:: Assaf Arkin assaf@labnotes.org
|
5
|
+
# Eric Hodel drbrain@segment7.net
|
5
6
|
# Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/UuidGenerator
|
6
|
-
# Copyright:: Copyright (c) 2005
|
7
|
+
# Copyright:: Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
|
7
8
|
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
8
9
|
|
9
|
-
|
10
|
+
require 'fileutils'
|
10
11
|
require 'thread'
|
11
|
-
require '
|
12
|
-
|
13
|
-
require '
|
12
|
+
require 'tmpdir'
|
13
|
+
|
14
|
+
require 'rubygems'
|
15
|
+
require 'macaddr'
|
14
16
|
|
15
17
|
|
16
|
-
|
18
|
+
##
|
19
|
+
# = Generating UUIDs
|
17
20
|
#
|
18
|
-
# Call
|
19
|
-
# formats. The default format is 36 characters long, and contains the 32
|
20
|
-
# hyphens separating the various value parts. The
|
21
|
-
#
|
21
|
+
# Call #generate to generate a new UUID. The method returns a string in one of
|
22
|
+
# three formats. The default format is 36 characters long, and contains the 32
|
23
|
+
# hexadecimal octets and hyphens separating the various value parts. The
|
24
|
+
# <tt>:compact</tt> format omits the hyphens, while the <tt>:urn</tt> format
|
25
|
+
# adds the <tt>:urn:uuid</tt> prefix.
|
22
26
|
#
|
23
27
|
# For example:
|
24
|
-
# 10.times do
|
25
|
-
# p UUID.new
|
26
|
-
# end
|
27
28
|
#
|
28
|
-
#
|
29
|
-
#
|
29
|
+
# uuid = UUID.new
|
30
|
+
#
|
31
|
+
# 10.times do
|
32
|
+
# p uuid.generate
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# = UUIDs in Brief
|
30
36
|
#
|
31
|
-
# UUID (universally unique identifier) are guaranteed to be unique across time
|
37
|
+
# UUID (universally unique identifier) are guaranteed to be unique across time
|
38
|
+
# and space.
|
32
39
|
#
|
33
|
-
# A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
|
34
|
-
# a 48-bit node identifier.
|
40
|
+
# A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
|
41
|
+
# sequence number and a 48-bit node identifier.
|
35
42
|
#
|
36
|
-
# The time value is taken from the system clock, and is monotonically
|
37
|
-
# since it is possible to set the system clock
|
38
|
-
# sequence number is
|
39
|
-
#
|
43
|
+
# The time value is taken from the system clock, and is monotonically
|
44
|
+
# incrementing. However, since it is possible to set the system clock
|
45
|
+
# backward, a sequence number is added. The sequence number is incremented
|
46
|
+
# each time the UUID generator is started. The combination guarantees that
|
47
|
+
# identifiers created on the same machine are unique with a high degree of
|
40
48
|
# probability.
|
41
49
|
#
|
42
|
-
# Note that due to the structure of the UUID and the use of sequence number,
|
43
|
-
# guarantee that UUID values themselves are monotonically
|
44
|
-
# cannot itself be used to sort based on order
|
50
|
+
# Note that due to the structure of the UUID and the use of sequence number,
|
51
|
+
# there is no guarantee that UUID values themselves are monotonically
|
52
|
+
# incrementing. The UUID value cannot itself be used to sort based on order
|
53
|
+
# of creation.
|
45
54
|
#
|
46
|
-
# To guarantee that UUIDs are unique across all machines in the network,
|
47
|
-
# MAC address of the machine's network interface card
|
48
|
-
#
|
49
|
-
# Use +ipconfig+ (Windows), or +ifconfig+ (Unix) to find the MAC address (aka physical address)
|
50
|
-
# of a network card. It takes the form of six pairs of hexadecimal digits, separated by hypen or
|
51
|
-
# colon, e.g. '<tt>08-0E-46-21-4B-35</tt>'
|
55
|
+
# To guarantee that UUIDs are unique across all machines in the network,
|
56
|
+
# the IEEE 802 MAC address of the machine's network interface card is used as
|
57
|
+
# the node identifier.
|
52
58
|
#
|
53
59
|
# For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
|
54
|
-
#
|
55
|
-
# == Configuring the UUID generator
|
56
|
-
#
|
57
|
-
# The UUID generator requires a state file which maintains the MAC address and next sequence
|
58
|
-
# number to use. By default, the UUID generator will use the file <tt>uuid.state</tt> contained
|
59
|
-
# in the current directory.
|
60
|
-
#
|
61
|
-
# Use UUID.config to specify a different location for the UUID state file. If the UUID state file
|
62
|
-
# does not exist, you can create one manually, or use UUID.config with the options <tt>:sequence</tt>
|
63
|
-
# and <tt>:mac_addr</tt>.
|
64
|
-
#
|
65
|
-
# A UUID state file looks like:
|
66
|
-
# ---
|
67
|
-
# last_clock: "0x28227f76122d80"
|
68
|
-
# mac_addr: 08-0E-46-21-4B-35
|
69
|
-
# sequence: "0x1639"
|
70
|
-
#
|
71
|
-
#--
|
72
|
-
# === Time-based UUID
|
73
|
-
#
|
74
|
-
# The UUID specification prescribes the following format for representing UUIDs. Four octets encode
|
75
|
-
# the low field of the time stamp, two octects encode the middle field of the timestamp, and two
|
76
|
-
# octets encode the high field of the timestamp with the version number. Two octets encode the
|
77
|
-
# clock sequence number and six octets encode the unique node identifier.
|
78
|
-
#
|
79
|
-
# The timestamp is a 60 bit value holding UTC time as a count of 100 nanosecond intervals since
|
80
|
-
# October 15, 1582. UUIDs generated in this manner are guaranteed not to roll over until 3400 AD.
|
81
|
-
#
|
82
|
-
# The clock sequence is used to help avoid duplicates that could arise when the clock is set backward
|
83
|
-
# in time or if the node ID changes. Although the system clock is guaranteed to be monotonic, the
|
84
|
-
# system clock is not guaranteed to be monotonic across system failures. The UUID cannot be sure
|
85
|
-
# that no UUIDs were generated with timestamps larger than the current timestamp.
|
86
|
-
#
|
87
|
-
# If the clock sequence can be determined at initialization, it is incremented by one. The clock sequence
|
88
|
-
# MUST be originally (i.e. once in the lifetime of a system) initialized to a random number to minimize the
|
89
|
-
# correlation across systems. The initial value must not be correlated to the node identifier.
|
90
|
-
#
|
91
|
-
# The node identifier must be unique for each UUID generator. This is accomplished using the IEEE 802
|
92
|
-
# network card address. For systems with multiple IEEE 802 addresses, any available address can be used.
|
93
|
-
# For systems with no IEEE address, a 47 bit random value is used and the multicast bit is set so it will
|
94
|
-
# never conflict with addresses obtained from network cards.
|
95
|
-
#
|
96
|
-
# === UUID state file
|
97
|
-
#
|
98
|
-
# The UUID state is contained in the UUID state file. The file name can be specified when configuring
|
99
|
-
# the UUID generator with UUID.config. The default is to use the file +uuid.state+ in the current directory.
|
100
|
-
#
|
101
|
-
# The UUID state file is read once when the UUID generator is first used (or configured). The sequence
|
102
|
-
# number contained in the UUID is read and used, and the state file is updated to the next sequence
|
103
|
-
# number. The MAC address is also read from the state file. The current clock time (in 100ns resolution)
|
104
|
-
# is stored in the state file whenever the sequence number is updated, but is never read.
|
105
|
-
#
|
106
|
-
# If the UUID generator detects that the system clock has been moved backwards, it will obtain a new
|
107
|
-
# sequence in the same manner. So it is possible that the UUID state file will be updated while the
|
108
|
-
# application is running.
|
109
|
-
#++
|
110
|
-
module UUID
|
111
|
-
|
112
|
-
VERSION = '1.0.4'
|
113
|
-
|
114
|
-
# Regular expression to identify a 36 character UUID. Can be used for a partial match.
|
115
|
-
REGEXP = /[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}/
|
116
60
|
|
117
|
-
|
118
|
-
REGEXP_FULL = /^[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}$/
|
61
|
+
class UUID
|
119
62
|
|
120
|
-
|
121
|
-
REGEXP_COMPACT = /^[[:xdigit:]]{32}$/
|
63
|
+
VERSION = '2.0.0'
|
122
64
|
|
123
|
-
|
124
|
-
|
65
|
+
##
|
66
|
+
# Clock multiplier. Converts Time (resolution: seconds) to UUID clock
|
67
|
+
# (resolution: 10ns)
|
68
|
+
CLOCK_MULTIPLIER = 10000000
|
125
69
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
CLOCK_GAPS = 100000 #:nodoc:
|
70
|
+
##
|
71
|
+
# Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time
|
72
|
+
# ticks.
|
73
|
+
CLOCK_GAPS = 100000
|
131
74
|
|
75
|
+
##
|
132
76
|
# Version number stamped into the UUID to identify it as time-based.
|
133
|
-
VERSION_CLOCK = 0x0100
|
77
|
+
VERSION_CLOCK = 0x0100
|
134
78
|
|
79
|
+
##
|
135
80
|
# Formats supported by the UUID generator.
|
81
|
+
#
|
82
|
+
# <tt>:default</tt>:: Produces 36 characters, including hyphens separating
|
83
|
+
# the UUID value parts
|
84
|
+
# <tt>:compact</tt>:: Produces a 32 digits (hexadecimal) value with no
|
85
|
+
# hyphens
|
86
|
+
# <tt>:urn</tt>:: Adds the prefix <tt>urn:uuid:</tt> to the default format
|
136
87
|
FORMATS = {
|
137
|
-
:compact=>'%08x%04x%04x%04x%012x',
|
138
|
-
:default=>'%08x-%04x-%04x-%04x-%012x',
|
88
|
+
:compact => '%08x%04x%04x%04x%012x',
|
89
|
+
:default => '%08x-%04x-%04x-%04x-%012x',
|
90
|
+
:urn => 'urn:uuid:%08x-%04x-%04x-%04x-%012x',
|
91
|
+
}
|
139
92
|
|
140
|
-
|
141
|
-
|
93
|
+
##
|
94
|
+
# MAC address (48 bits), sequence number and last clock
|
95
|
+
STATE_FILE_FORMAT = 'SLLQ'
|
142
96
|
|
143
|
-
|
97
|
+
@state_file = nil
|
98
|
+
@mode = nil
|
99
|
+
@uuid = nil
|
144
100
|
|
145
|
-
|
101
|
+
##
|
102
|
+
# The access mode of the state file. Set it with state_file.
|
146
103
|
|
147
|
-
|
104
|
+
def self.mode
|
105
|
+
@mode
|
106
|
+
end
|
148
107
|
|
149
|
-
|
108
|
+
##
|
109
|
+
# Generates a new UUID string using +format+. See FORMATS for a list of
|
110
|
+
# supported formats.
|
150
111
|
|
151
|
-
|
112
|
+
def self.generate(format = :default)
|
113
|
+
@uuid ||= new
|
114
|
+
@uuid.generate format
|
115
|
+
end
|
152
116
|
|
153
|
-
|
117
|
+
##
|
118
|
+
# Creates an empty state file in /var/tmp/ruby-uuid or the windows common
|
119
|
+
# application data directory using mode 0644. Call with a different mode
|
120
|
+
# before creating a UUID generator if you want to open access beyond your
|
121
|
+
# user by default.
|
122
|
+
#
|
123
|
+
# If the default state dir is not writable, UUID falls back to ~/.ruby-uuid.
|
124
|
+
#
|
125
|
+
# State files are not portable across machines.
|
126
|
+
def self.state_file(mode = 0644)
|
127
|
+
return @state_file if @state_file
|
154
128
|
|
155
|
-
|
129
|
+
@mode = mode
|
156
130
|
|
157
|
-
|
131
|
+
begin
|
132
|
+
require 'Win32API'
|
158
133
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
# is <tt>uuid.state</tt>
|
164
|
-
# * <tt>:logger<tt> -- The UUID generator will use this logger to report the state information (optional).
|
165
|
-
# * <tt>:sequence</tt> -- Specifies the sequence number (0 to 0xFFFF) to use. Required to
|
166
|
-
# create a new state file, ignored if the state file already exists.
|
167
|
-
# * <tt>:mac_addr</tt> -- Specifies the MAC address (xx-xx-xx-xx-xx) to use. Required to
|
168
|
-
# create a new state file, ignored if the state file already exists.
|
169
|
-
#
|
170
|
-
# For example, to create a new state file:
|
171
|
-
# UUID.config :state_file=>'my-uuid.state', :sequence=>rand(0x10000), :mac_addr=>'0C-0E-35-41-60-65'
|
172
|
-
# To use an existing state file and log to +STDOUT+:
|
173
|
-
# UUID.config :state_file=>'my-uuid.state', :logger=>Logger.new(STDOUT)
|
174
|
-
#
|
175
|
-
# :call-seq:
|
176
|
-
# UUID.config(config)
|
177
|
-
#
|
178
|
-
def config(options)
|
179
|
-
options ||= {}
|
180
|
-
@@mutex.synchronize do
|
181
|
-
@@logger = options[:logger]
|
182
|
-
next_sequence options
|
183
|
-
end
|
184
|
-
end
|
134
|
+
csidl_common_appdata = 0x0023
|
135
|
+
path = 0.chr * 260
|
136
|
+
get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L')
|
137
|
+
get_folder_path.call 0, csidl_common_appdata, 0, 1, path
|
185
138
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
def setup()
|
190
|
-
file = File.expand_path(File.join(Dir.pwd, STATE_FILE))
|
191
|
-
if File.exist? file
|
192
|
-
puts "UUID: Found an existing UUID state file: #{file}"
|
193
|
-
else
|
194
|
-
puts "UUID: No UUID state file found, attempting to create one for you:"
|
195
|
-
# Run ifconfig for UNIX, or ipconfig for Windows.
|
196
|
-
config = ""
|
197
|
-
Kernel.open("|ifconfig") { |input| input.each_line { |line| config << line } } rescue nil
|
198
|
-
Kernel.open("|ipconfig /all") { |input| input.each_line { |line| config << line } } rescue nil
|
199
|
-
|
200
|
-
addresses = config.scan(IFCONFIG_PATTERN).map { |addr| addr[1..-2] }
|
201
|
-
if addresses.empty?
|
202
|
-
puts "Could not find any IEEE 802 NIC MAC addresses for this machine."
|
203
|
-
puts "You need to create the uuid.state file manually."
|
204
|
-
else
|
205
|
-
puts "Found the following IEEE 802 NIC MAC addresses on your computer:"
|
206
|
-
addresses.each { |addr| puts " #{addr}" }
|
207
|
-
puts "Selecting the first address #{addresses[0]} for use in your UUID state file."
|
208
|
-
File.open file, "w" do |output|
|
209
|
-
output.puts "mac_addr: \"#{addresses[0]}\""
|
210
|
-
output.puts format("sequence: \"0x%04x\"", rand(0x10000))
|
211
|
-
end
|
212
|
-
puts "Created a new UUID state file: #{file}"
|
213
|
-
end
|
214
|
-
end
|
215
|
-
file
|
139
|
+
state_dir = File.join(path.strip)
|
140
|
+
rescue LoadError
|
141
|
+
state_dir = File.join('', 'var', 'tmp')
|
216
142
|
end
|
217
143
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
# If called from configuration, use the specified or default state file.
|
223
|
-
state_file = (config && config[:state_file]) || @@state_file
|
224
|
-
|
225
|
-
unless state_file
|
226
|
-
if File.exist?(STATE_FILE)
|
227
|
-
state_file = STATE_FILE
|
228
|
-
else
|
229
|
-
file = File.expand_path(File.join(Dir.pwd, STATE_FILE))
|
230
|
-
state_file = File.exist?(file) ? file : setup
|
231
|
-
end
|
232
|
-
end
|
233
|
-
begin
|
234
|
-
File.open state_file, "r+" do |file|
|
235
|
-
# Lock the file for exclusive access, just to make sure it's not being read while we're
|
236
|
-
# updating its contents.
|
237
|
-
file.flock(File::LOCK_EX)
|
238
|
-
state = YAML::load file
|
239
|
-
# Get the sequence number. Must be a valid 16-bit hexadecimal value.
|
240
|
-
sequence = state['sequence']
|
241
|
-
if sequence
|
242
|
-
raise RuntimeError, format(ERROR_INVALID_SEQUENCE, sequence) unless sequence.is_a?(String) && sequence =~ /[0-9a-fA-F]{4}/
|
243
|
-
sequence = sequence.hex & 0xFFFF
|
244
|
-
else
|
245
|
-
sequence = rand(0x10000)
|
246
|
-
end
|
247
|
-
# Get the MAC address. Must be 6 pairs of hexadecimal octets. Convert MAC address into
|
248
|
-
# a 48-bit value with the higher bit being zero.
|
249
|
-
mac_addr = state['mac_addr']
|
250
|
-
raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless
|
251
|
-
mac_addr.is_a?(String) && mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
|
252
|
-
mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
|
253
|
-
|
254
|
-
# If everything is OK, proceed to the next step. Grab the sequence number and store
|
255
|
-
# the new state. Start at beginning of file, and truncate file when done.
|
256
|
-
@@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
|
257
|
-
file.pos = 0
|
258
|
-
dump file, true
|
259
|
-
file.truncate file.pos
|
260
|
-
end
|
261
|
-
# Initialized.
|
262
|
-
if @@logger
|
263
|
-
@@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
|
264
|
-
else
|
265
|
-
warn "UUID: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
|
266
|
-
end
|
267
|
-
@@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
|
268
|
-
rescue Errno::ENOENT=>error
|
269
|
-
if !config
|
270
|
-
# Generate random values.
|
271
|
-
@@mac_hex, @@sequence, @@state_file = rand(0x800000000000) | 0xF00000000000, rand(0x10000), nil
|
272
|
-
# Initialized.
|
273
|
-
if @@logger
|
274
|
-
@@logger.error ERROR_INITIALIZED_RANDOM_1
|
275
|
-
@@logger.error ERROR_INITIALIZED_RANDOM_2
|
276
|
-
else
|
277
|
-
warn "UUID: " + ERROR_INITIALIZED_RANDOM_1
|
278
|
-
warn "UUID: " + ERROR_INITIALIZED_RANDOM_2
|
279
|
-
end
|
280
|
-
@@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
|
281
|
-
else
|
282
|
-
# No state file. If we were called for configuration with valid sequence number and MAC address,
|
283
|
-
# attempt to create state file. See code above for how we interpret these values.
|
284
|
-
sequence = config[:sequence]
|
285
|
-
raise RuntimeError, format(ERROR_NOT_A_SEQUENCE, sequence) unless sequence.is_a?(Integer)
|
286
|
-
sequence &= 0xFFFF
|
287
|
-
mac_addr = config[:mac_addr]
|
288
|
-
raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless
|
289
|
-
mac_addr.is_a?(String) && mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
|
290
|
-
mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
|
291
|
-
File.open state_file, "w" do |file|
|
292
|
-
file.flock(File::LOCK_EX)
|
293
|
-
@@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
|
294
|
-
file.pos = 0
|
295
|
-
dump file, true
|
296
|
-
file.truncate file.pos
|
297
|
-
end
|
298
|
-
# Initialized.
|
299
|
-
if @@logger
|
300
|
-
@@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
|
301
|
-
else
|
302
|
-
warn "UUID: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
|
303
|
-
end
|
304
|
-
@@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
|
305
|
-
end
|
306
|
-
rescue Exception=>error
|
307
|
-
@@last_clock = nil
|
308
|
-
raise error
|
309
|
-
end
|
144
|
+
if File.writable?(state_dir) then
|
145
|
+
@state_file = File.join(state_dir, 'ruby-uuid')
|
146
|
+
else
|
147
|
+
@state_file = File.expand_path(File.join('~', '.ruby-uuid'))
|
310
148
|
end
|
311
149
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
150
|
+
@state_file
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Create a new UUID generator. You really only need to do this once.
|
155
|
+
def initialize
|
156
|
+
@drift = 0
|
157
|
+
@last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
|
158
|
+
@mutex = Mutex.new
|
159
|
+
|
160
|
+
if File.exist?(self.class.state_file) then
|
161
|
+
next_sequence
|
162
|
+
else
|
163
|
+
@mac = Mac.addr.gsub(':', '').hex & 0x7FFFFFFFFFFF
|
164
|
+
@sequence = rand 0x10000
|
165
|
+
|
166
|
+
open_lock 'w' do |io|
|
167
|
+
write_state io
|
318
168
|
end
|
319
169
|
end
|
320
170
|
end
|
321
171
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
172
|
+
##
|
173
|
+
# Generates a new UUID string using +format+. See FORMATS for a list of
|
174
|
+
# supported formats.
|
175
|
+
def generate(format = :default)
|
176
|
+
template = FORMATS[format]
|
326
177
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
# UUID.new([format]) -> string
|
341
|
-
#
|
342
|
-
def new(format = nil)
|
343
|
-
# Determine which format we're using for the UUID string.
|
344
|
-
template = FORMATS[format || :default] or raise RuntimeError, "I don't know the format '#{format}'"
|
345
|
-
|
346
|
-
# The clock must be monotonically increasing. The clock resolution is at best 100 ns
|
347
|
-
# (UUID spec), but practically may be lower (on my setup, around 1ms). If this method
|
348
|
-
# is called too fast, we don't have a monotonically increasing clock, so the solution is
|
349
|
-
# to just wait.
|
350
|
-
# It is possible for the clock to be adjusted backwards, in which case we would end up
|
351
|
-
# blocking for a long time. When backward clock is detected, we prevent duplicates by
|
352
|
-
# asking for a new sequence number and continue with the new clock.
|
353
|
-
clock = @@mutex.synchronize do
|
354
|
-
# Initialize UUID generator if not already initialized. Uninitizlied UUID generator has no
|
355
|
-
# last known clock.
|
356
|
-
next_sequence unless @@last_clock
|
178
|
+
raise ArgumentError, "invalid UUID format #{format.inspect}" unless template
|
179
|
+
|
180
|
+
# The clock must be monotonically increasing. The clock resolution is at
|
181
|
+
# best 100 ns (UUID spec), but practically may be lower (on my setup,
|
182
|
+
# around 1ms). If this method is called too fast, we don't have a
|
183
|
+
# monotonically increasing clock, so the solution is to just wait.
|
184
|
+
#
|
185
|
+
# It is possible for the clock to be adjusted backwards, in which case we
|
186
|
+
# would end up blocking for a long time. When backward clock is detected,
|
187
|
+
# we prevent duplicates by asking for a new sequence number and continue
|
188
|
+
# with the new clock.
|
189
|
+
|
190
|
+
clock = @mutex.synchronize do
|
357
191
|
clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
192
|
+
|
193
|
+
if clock > @last_clock then
|
194
|
+
@drift = 0
|
195
|
+
@last_clock = clock
|
196
|
+
elsif clock == @last_clock then
|
197
|
+
drift = @drift += 1
|
198
|
+
|
199
|
+
if drift < 10000 then
|
200
|
+
@last_clock += 1
|
365
201
|
else
|
366
202
|
Thread.pass
|
367
203
|
nil
|
368
204
|
end
|
369
205
|
else
|
370
206
|
next_sequence
|
371
|
-
|
207
|
+
@last_clock = clock
|
372
208
|
end
|
373
|
-
end
|
374
|
-
|
375
|
-
|
209
|
+
end until clock
|
210
|
+
|
211
|
+
template % [
|
212
|
+
clock & 0xFFFFFFFF,
|
213
|
+
(clock >> 32) & 0xFFFF,
|
214
|
+
((clock >> 48) & 0xFFFF | VERSION_CLOCK),
|
215
|
+
@sequence & 0xFFFF,
|
216
|
+
@mac & 0xFFFFFFFFFFFF
|
217
|
+
]
|
376
218
|
end
|
377
219
|
|
378
|
-
|
379
|
-
|
220
|
+
##
|
221
|
+
# Updates the state file with a new sequence number.
|
222
|
+
def next_sequence
|
223
|
+
open_lock 'r+' do |io|
|
224
|
+
@mac, @sequence, @last_clock = read_state(io)
|
380
225
|
|
381
|
-
|
226
|
+
io.rewind
|
227
|
+
io.truncate 0
|
228
|
+
|
229
|
+
@sequence += 1
|
230
|
+
|
231
|
+
write_state io
|
232
|
+
end
|
233
|
+
rescue Errno::ENOENT
|
234
|
+
open_lock 'w' do |io|
|
235
|
+
write_state io
|
236
|
+
end
|
237
|
+
ensure
|
238
|
+
@last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
|
239
|
+
@drift = 0
|
240
|
+
end
|
382
241
|
|
242
|
+
def inspect
|
243
|
+
mac = ("%08x" % @mac).scan(/[0-9a-f]{2}/).join(':')
|
244
|
+
"MAC: #{mac} Sequence: #{@sequence}"
|
245
|
+
end
|
246
|
+
|
247
|
+
protected
|
383
248
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
249
|
+
##
|
250
|
+
# Open the state file with an exclusive lock and access mode +mode+.
|
251
|
+
def open_lock(mode)
|
252
|
+
File.open self.class.state_file, mode, self.class.mode do |io|
|
253
|
+
begin
|
254
|
+
io.flock File::LOCK_EX
|
255
|
+
yield io
|
256
|
+
ensure
|
257
|
+
io.flock File::LOCK_UN
|
258
|
+
end
|
388
259
|
end
|
389
260
|
end
|
390
|
-
end if defined?(ActiveRecord)
|
391
261
|
|
262
|
+
##
|
263
|
+
# Read the state from +io+
|
264
|
+
def read_state(io)
|
265
|
+
mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT)
|
266
|
+
mac = (mac1 << 32) + mac2
|
392
267
|
|
393
|
-
|
394
|
-
|
268
|
+
return mac, seq, last_clock
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
##
|
273
|
+
# Write that state to +io+
|
274
|
+
def write_state(io)
|
275
|
+
mac2 = @mac & 0xffffffff
|
276
|
+
mac1 = (@mac >> 32) & 0xffff
|
277
|
+
|
278
|
+
io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT)
|
279
|
+
end
|
280
|
+
|
395
281
|
end
|