$search
00001 #!/usr/bin/env python 00002 00003 """ 00004 usage: %(progname)s [args] 00005 """ 00006 00007 # GUID.py 00008 # Version 2.1. 00009 # 00010 # Copyright (C) 2003 Dr. Conan C. Albrecht <conan_albrechtATbyu.edu> 00011 # 00012 # This library is free software; you can redistribute it and/or 00013 # modify it under the terms of the GNU Lesser General Public 00014 # License as published by the Free Software Foundation; either 00015 # version 2.1 of the License, or (at your option) any later version. 00016 # 00017 # This library is distributed in the hope that it will be useful, 00018 # but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00020 # Lesser General Public License for more details. 00021 # 00022 # You should have received a copy of the GNU Lesser General Public 00023 # License along with this library; if not, write to the Free Software 00024 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00025 00026 00027 00028 ################################################################################################## 00029 ### A globally-unique identifier made up of time and ip and 8 random digits: 16 characters wide 00030 ### 00031 ### A globally unique identifier that combines ip, time, and random bits. Since the 00032 ### time is listed first, you can sort records by guid. You can also extract the time 00033 ### and ip if needed. 00034 ### 00035 ### GUIDs make wonderful database keys. They require no access to the 00036 ### database (to get the max index number), they are extremely unique, and they sort 00037 ### automatically by time. GUIDs prevent key clashes when merging 00038 ### two databases together, combining data, or generating keys in distributed 00039 ### systems. 00040 ### 00041 ### There is an Internet Draft for UUIDs, but this module does not implement it. 00042 ### If the draft catches on, perhaps I'll conform the module to it. 00043 ### 00044 00045 00046 # Changelog 00047 # Sometime, 1997 Created the Java version of GUID 00048 # Went through many versions in Java 00049 # Sometime, 2002 Created the Python version of GUID, mirroring the Java version 00050 # November 24, 2003 Changed Python version to be more pythonic, took out object and made just a module 00051 # December 2, 2003 Fixed duplicating GUIDs. Sometimes they duplicate if multiples are created 00052 # in the same millisecond (it checks the last 100 GUIDs now and has a larger random part) 00053 # December 9, 2003 Fixed MAX_RANDOM, which was going over sys.maxint 00054 # November 21, 2005 Changed to pack the guid using 64 bit encoding 0-9, A-Z, a-z, @, [ 00055 # Now, the guid is only 16 bytes long. 00056 # 00057 00058 import math 00059 import random 00060 import socket 00061 import os, sys 00062 import time 00063 import threading 00064 import string, struct 00065 00066 import getopt 00067 00068 # The size of the circular queue. Larger sizes give more assurance for uniqueness. 00069 # Smaller sizes take less memory and are a tiny bit faster 00070 QUEUE_SIZE = 100 00071 00072 00073 ############################# 00074 ### global module variables 00075 00076 MAX_RANDOM = sys.maxint # converted to hex goes to 8 chars (at least, in Python 2.3) 00077 rand = random.Random() 00078 ip = '' 00079 lock = threading.RLock() 00080 lastguid = '' 00081 try: 00082 ip = socket.gethostbyname(socket.gethostname()) 00083 except (socket.gaierror): # if we don't have an ip, default to someting in the 10.x.x.x private range 00084 ip = '10' 00085 for i in range(3): 00086 ip += '.' + str(rand.randrange(1, 254)) 00087 hexip = ''.join(["%04x" % long(i) for i in ip.split('.')]) # leave space for ip v6 (65K in each sub) 00088 ipaddrStr = socket.inet_aton(ip) 00089 (ipaddr, ) = struct.unpack(">I", ipaddrStr) 00090 00091 def encode64Char(i): 00092 if i<10: return chr(i+48) 00093 elif i<38: return chr(i-10+64) 00094 elif i<64: return chr(i-38+97) 00095 raise Error 00096 00097 def pack64(i, bytes=6): 00098 parts = [] 00099 for j in range(bytes): 00100 a = i & 0x3f 00101 i = (i >> 6) 00102 parts.append(encode64Char(a)) 00103 parts.reverse() 00104 p = string.join(parts, "") 00105 return p 00106 00107 def decode64Char(c): 00108 n = ord(c) 00109 if n>=48 and n<=58: 00110 return n-48 00111 elif n >= 64 and n <= 91: 00112 return n - 54 00113 elif n >= 97 and n <= 122: 00114 return n - 59 00115 raise Error 00116 00117 def unpack64(s, bytes=6): 00118 i = 0L 00119 00120 for j in range(bytes): 00121 c = s[j] 00122 a = decode64Char(c) 00123 i = i | a 00124 if j < bytes-1: 00125 i = i << 6 00126 return i 00127 00128 if 0: 00129 for i in range(64): 00130 c = encode64Char(i) 00131 j = decode64Char(c) 00132 if i != j: 00133 print i, c, j 00134 00135 ipaddr64 = pack64(ipaddr) 00136 00137 ####################################### 00138 ### A simple circular set 00139 ### to ensure we don't duplicate 00140 ### GUIDs in the same millisecond 00141 00142 class CircularSet: 00143 '''A circular set. A set that maxes at a given size, replacing the oldest element after maximum size. 00144 This implementation is NOT thread safe. (generate() below is thread safe, though) 00145 ''' 00146 def __init__(self): 00147 self.queue = [] 00148 self.queue_map = {} # for efficiency, we keep a map of everything 00149 self.queueindex = 0 00150 00151 def add(self, val): 00152 '''Adds a value to the queue''' 00153 # check to see if we have this value. If so, throw an exception 00154 assert not self.queue_map.has_key(val), 'This value is already in the set!' 00155 00156 # add the new one to the list 00157 if len(self.queue) > self.queueindex: 00158 # first remove the previous key at this location 00159 del self.queue_map[self.queue[self.queueindex]] 00160 self.queue[self.queueindex] = val 00161 else: 00162 self.queue.append(val) 00163 00164 # now add to the map for efficiency 00165 self.queue_map[val] = val 00166 00167 # increment the queue index 00168 self.queueindex += 1 00169 if self.queueindex >= QUEUE_SIZE: 00170 self.queueindex = 0 00171 00172 queue = CircularSet() 00173 00174 ################################# 00175 ### Public module functions 00176 00177 00178 def generate(time_t = None): 00179 '''Generates a new guid''' 00180 global lock, queue # since we modify the module variable 00181 if time_t > 1136102400: 00182 raise ValueError, "time_t is too large %s" % time_t 00183 00184 try: 00185 lock.acquire() # can't generate two guids at the same time 00186 while 1: 00187 # time part 00188 if time_t == None: 00189 t = long(time.time() * 100) 00190 else: 00191 t = time_t * 100 00192 00193 # random part 00194 r = int(rand.random() * MAX_RANDOM) & 0xffff 00195 n = 0L | (long(t)<<48) | (ipaddr<<16) | r 00196 guid = pack64(n, bytes=16) 00197 00198 try: 00199 queue.add(guid) # throws the AssertionError if this GUID is a duplicate of the queue 00200 return guid 00201 except AssertionError: # signals we already have this GUID in the queue 00202 pass 00203 finally: 00204 lock.release() 00205 00206 InvalidGUID = "Invalid GUID" 00207 00208 def extract_time(guid): 00209 '''Extracts the time portion out of the guid and returns the 00210 number of seconds since the epoch as a float''' 00211 if len(guid) != 16: raise InvalidGUID 00212 n = unpack64(guid, bytes=16) 00213 t = long(n >> 48) / 100 00214 return t 00215 00216 00217 def extract_ip(guid): 00218 '''Extracts the ip portion out of the guid and returns it 00219 as a string like 10.10.10.10''' 00220 00221 if len(guid) != 16: raise InvalidGUID 00222 00223 n = unpack64(guid, bytes=16) 00224 n = n >> 16 00225 n = n & 0xffffffffL 00226 00227 ip = struct.pack(">L", n) 00228 ipaddrStr = socket.inet_ntoa(ip) 00229 return ipaddrStr 00230 00231 00232 def extract_random(guid): 00233 '''Extracts the random bits from the guid (returns the bits in decimal)''' 00234 if len(guid) != 16: raise InvalidGUID 00235 00236 n = unpack64(guid, bytes=16) 00237 r = int(n & 0xffff) 00238 return r 00239 00240 00241 ### TESTING OF GUID CLASS ### 00242 def test(): 00243 pass 00244 00245 def usage(progname): 00246 print __doc__ % vars() 00247 00248 def main(argv, stdout, environ): 00249 progname = argv[0] 00250 optlist, args = getopt.getopt(argv[1:], "", ["help", "test", "debug"]) 00251 00252 testflag = 0 00253 00254 for (field, val) in optlist: 00255 if field == "--help": 00256 usage(progname) 00257 return 00258 elif field == "--debug": 00259 debugfull() 00260 elif field == "--test": 00261 testflag = 1 00262 00263 if testflag: 00264 test() 00265 return 00266 00267 guids = [] 00268 if len(args) == 0: 00269 guids.append(generate()) 00270 else: 00271 for arg in args: 00272 guids.append(arg) 00273 00274 for guid in guids: 00275 print "GUID:", guid 00276 print "Time:", time.strftime('%a, %d %b %Y %H:%M:%S', time.localtime(extract_time(guid))) 00277 print "IP: ", extract_ip(guid) 00278 print "Rand:", extract_random(guid) 00279 print 00280 00281 00282 if __name__ == "__main__": 00283 main(sys.argv, sys.stdout, os.environ) 00284 00285