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.
Files changed (9) hide show
  1. data/CHANGELOG +32 -19
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +103 -0
  4. data/Rakefile +65 -185
  5. data/lib/uuid.rb +212 -326
  6. data/test/test-uuid.rb +82 -52
  7. metadata +53 -36
  8. data/README +0 -81
  9. data/bin/uuid-setup +0 -7
data/CHANGELOG CHANGED
@@ -1,19 +1,32 @@
1
- 1.0.4 (1/2/2006)
2
- * Changed: By default creates the uuid.state file in the working directory,
3
- not in the installation directory, which requires sudo privileges (e.g. gem).
4
-
5
- 1.0.3 (1/8/2006)
6
- * Fixed: Work around YAML bug in serializing that occurs when MAC address
7
- consists only of decimal digits. Credit: ebuprofen"
8
-
9
- 1.0.2 (1/28/2006)
10
- * Changed: Constants are not conditionally defined (removes warnings when
11
- using in Rails.
12
-
13
- 1.0.1 (1/26/2006)
14
- * Added: Regular expressions to test if a string is a UUID.
15
- * Changed: When used in ActiveRecord, adds as callback instead of overriding
16
- save.
17
-
18
- 1.0.0 (1/20/2005)
19
- * Changed: Separated form reliable-msg into its own package.
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.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2005,2007 Assaf Arkin
1
+ Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -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 "rubygems"
4
- require "rake/testtask"
5
- require "rake/rdoctask"
6
- require "rake/gempackagetask"
7
-
8
- VERSION_FILE = "lib/uuid.rb"
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 :default => [:test, :rdoc]
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 = ["test/*.rb"]
42
- #test.warning = true
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 = "README"
48
- rdoc.rdoc_files.include("README", "lib/**/*.rb")
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 :install=>:package do |task|
59
- system "gem", "install", "pkg/#{spec.name}-#{spec.version}.gem"
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 :uninstall do |task|
64
- system "gem", "uninstall", spec.name, "-v", spec.version.to_s
65
- end
66
-
67
-
68
- desc "Look for TODO and FIXME tags in the code"
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
- task :post do
119
- # Practical example of functional read but not comprehend code:
120
- next_version = spec.version.to_ints.zip([0, 0, 1]).map { |a| a.inject(0) { |t,i| t + i } }.join(".")
121
- puts "Updating #{VERSION_FILE} to next version number: #{next_version}"
122
- ver_file = File.read(__FILE__.pathmap("%d/#{VERSION_FILE}")).
123
- sub(/(VERSION\s*=\s*)(['"])(.*)\2/) { |line| "#{$1}#{$2}#{next_version}#{$2}" }
124
- File.open(__FILE__.pathmap("%d/#{VERSION_FILE}"), "w") { |file| file.write ver_file }
125
- puts "Adding entry to CHANGELOG"
126
- changelog = File.read(__FILE__.pathmap("%d/CHANGELOG"))
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
- def changed?
161
- @version != @new_version
162
- end
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
- desc "Make a new release"
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
@@ -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,2007 Assaf Arkin
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 'yaml'
12
- require 'singleton'
13
- require 'logger'
12
+ require 'tmpdir'
13
+
14
+ require 'rubygems'
15
+ require 'macaddr'
14
16
 
15
17
 
16
- # == Generating UUIDs
18
+ ##
19
+ # = Generating UUIDs
17
20
  #
18
- # Call UUID.new to generate and return a new UUID. The method returns a string in one of three
19
- # formats. The default format is 36 characters long, and contains the 32 hexadecimal octets and
20
- # hyphens separating the various value parts. The <tt>:compact</tt> format omits the hyphens,
21
- # while the <tt>:urn</tt> format adds the <tt>:urn:uuid</tt> prefix.
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
- # == UUIDs in Brief
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 and space.
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 sequence number and
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 incrementing. However,
37
- # since it is possible to set the system clock backward, a sequence number is added. The
38
- # sequence number is incremented each time the UUID generator is started. The combination
39
- # guarantees that identifiers created on the same machine are unique with a high degree of
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, there is no
43
- # guarantee that UUID values themselves are monotonically incrementing. The UUID value
44
- # cannot itself be used to sort based on order of creation.
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, use the IEEE 802
47
- # MAC address of the machine's network interface card as the node identifier. Network interface
48
- # cards have unique MAC address that are 47-bit long (the last bit acts as a broadcast flag).
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
- # Regular expression to identify a 36 character UUID. Can only be used for a full match.
118
- REGEXP_FULL = /^[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}$/
61
+ class UUID
119
62
 
120
- # Regular expression to identify a 32 character UUID, no hyphens (compact) full match.
121
- REGEXP_COMPACT = /^[[:xdigit:]]{32}$/
63
+ VERSION = '2.0.0'
122
64
 
123
- # Default state file.
124
- STATE_FILE = 'uuid.state'
65
+ ##
66
+ # Clock multiplier. Converts Time (resolution: seconds) to UUID clock
67
+ # (resolution: 10ns)
68
+ CLOCK_MULTIPLIER = 10000000
125
69
 
126
- # Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)
127
- CLOCK_MULTIPLIER = 10000000 #:nodoc:
128
-
129
- # Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
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 #:nodoc:
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', :urn=>'urn:uuid:%08x-%04x-%04x-%04x-%012x' } #:nodoc:
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
- # Length (in characters) of UUIDs generated for each of the formats.
141
- FORMATS_LENGTHS = { :compact=>32, :default=>36, :urn=>45 } #:nodoc:
93
+ ##
94
+ # MAC address (48 bits), sequence number and last clock
95
+ STATE_FILE_FORMAT = 'SLLQ'
142
96
 
143
- ERROR_INVALID_SEQUENCE = "Invalid sequence number: found '%s', expected 4 hexdecimal digits" #:nodoc:
97
+ @state_file = nil
98
+ @mode = nil
99
+ @uuid = nil
144
100
 
145
- ERROR_NOT_A_SEQUENCE = "Not a sequence number: expected integer between 0 and 0xFFFF" #:nodoc:
101
+ ##
102
+ # The access mode of the state file. Set it with state_file.
146
103
 
147
- ERROR_INVALID_MAC_ADDR = "Invalid MAC address: found '%s', expected a number in the format XX-XX-XX-XX-XX-XX" #:nodoc:
104
+ def self.mode
105
+ @mode
106
+ end
148
107
 
149
- INFO_INITIALIZED = "Initialized UUID generator with sequence number 0x%04x and MAC address %s" #:nodoc:
108
+ ##
109
+ # Generates a new UUID string using +format+. See FORMATS for a list of
110
+ # supported formats.
150
111
 
151
- ERROR_INITIALIZED_RANDOM_1 = "Initialized UUID generator with random sequence number/MAC address." #:nodoc:
112
+ def self.generate(format = :default)
113
+ @uuid ||= new
114
+ @uuid.generate format
115
+ end
152
116
 
153
- ERROR_INITIALIZED_RANDOM_2 = "UUIDs are not guaranteed to be unique. Please create a uuid.state file as soon as possible." #:nodoc:
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
- IFCONFIG_PATTERN = /[^:\-](?:[0-9A-Za-z][0-9A-Za-z][:\-]){5}[0-9A-Za-z][0-9A-Za-z][^:\-]/ #:nodoc:
129
+ @mode = mode
156
130
 
157
- class << self
131
+ begin
132
+ require 'Win32API'
158
133
 
159
- # Configures the UUID generator. Use this method to specify the UUID state file, logger, etc.
160
- #
161
- # The method accepts the following options:
162
- # * <tt>:state_file</tt> -- Specifies the location of the state file. If missing, the default
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
- # Create a uuid.state file by finding the IEEE 802 NIC MAC address for this machine.
187
- # Works for UNIX (ifconfig) and Windows (ipconfig). Creates the uuid.state file in the
188
- # current directory.
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
- private
219
-
220
- def next_sequence(config = nil)
221
- # If called to advance the sequence number (config is nil), we have a state file that we're able to use.
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
- def dump(file, plus_one)
313
- # Gets around YAML weirdness, like ont storing the MAC address as a string.
314
- if @@sequence && @@mac_addr
315
- file.puts "mac_addr: \"#{@@mac_addr}\""
316
- file.puts "sequence: \"0x%04x\"" % ((plus_one ? @@sequence + 1 : @@sequence) & 0xFFFF)
317
- file.puts "last_clock: \"0x%x\"" % (@@last_clock || (Time.new.to_f * CLOCK_MULTIPLIER).to_i)
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
- @@mutex = Mutex.new
323
- @@last_clock = nil
324
- @@logger = nil
325
- @@state_file = nil
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
- # Generates and returns a new UUID string.
328
- #
329
- # The argument +format+ specifies which formatting template to use:
330
- # * <tt>:default</tt> -- Produces 36 characters, including hyphens separating the UUID value parts
331
- # * <tt>:compact</tt> -- Produces a 32 digits (hexadecimal) value with no hyphens
332
- # * <tt>:urn</tt> -- Aadds the prefix <tt>urn:uuid:</tt> to the <tt>:default</tt> format
333
- #
334
- # For example:
335
- # print UUID.new :default
336
- # or just
337
- # print UUID.new
338
- #
339
- # :call-seq:
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
- if clock > @@last_clock
359
- @@drift = 0
360
- @@last_clock = clock
361
- elsif clock = @@last_clock
362
- drift = @@drift += 1
363
- if drift < 10000
364
- @@last_clock += 1
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
- @@last_clock = clock
207
+ @last_clock = clock
372
208
  end
373
- end while not clock
374
- sprintf template, clock & 0xFFFFFFFF, (clock >> 32)& 0xFFFF, ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
375
- @@sequence & 0xFFFF, @@mac_hex & 0xFFFFFFFFFFFF
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
- alias uuid new
379
- module_function :uuid, :new
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
- end
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
- class ActiveRecord::Base
385
- class << self
386
- def uuid_primary_key()
387
- before_create { |record| record.id = UUID.new unless record.id }
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
- if __FILE__ == $0
394
- UUID.setup
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