Back to index

obnam  1.1
clientlist.py
Go to the documentation of this file.
00001 # Copyright 2010  Lars Wirzenius
00002 # 
00003 # This program is free software: you can redistribute it and/or modify
00004 # it under the terms of the GNU General Public License as published by
00005 # the Free Software Foundation, either version 3 of the License, or
00006 # (at your option) any later version.
00007 # 
00008 # This program is distributed in the hope that it will be useful,
00009 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 # GNU General Public License for more details.
00012 # 
00013 # You should have received a copy of the GNU General Public License
00014 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
00015 
00016 
00017 import hashlib
00018 import logging
00019 import struct
00020 import random
00021 import tracing
00022 
00023 import obnamlib
00024 
00025 
00026 class ClientList(obnamlib.RepositoryTree):
00027 
00028     '''Repository's list of clients.
00029     
00030     The list maps a client name to an arbitrary (string) identifier,
00031     which is unique within the repository.
00032     
00033     The list is implemented as a B-tree, with a three-part key:
00034     128-bit MD5 of client name, 64-bit unique identifier, and subkey
00035     identifier. The value depends on the subkey: it's either the
00036     client's full name, or the public key identifier the client
00037     uses to encrypt their backups.
00038     
00039     The client's identifier is a random, unique 64-bit integer.
00040     
00041     '''
00042     
00043     # subkey values
00044     CLIENT_NAME = 0
00045     KEYID = 1
00046     SUBKEY_MAX = 255
00047 
00048     def __init__(self, fs, node_size, upload_queue_size, lru_size, hooks):
00049         tracing.trace('new ClientList')
00050         self.hash_len = len(self.hashfunc(''))
00051         self.fmt = '!%dsQB' % self.hash_len
00052         self.key_bytes = len(self.key('', 0, 0))
00053         self.minkey = self.hashkey('\x00' * self.hash_len, 0, 0)
00054         self.maxkey = self.hashkey('\xff' * self.hash_len, obnamlib.MAX_ID, 
00055                                    self.SUBKEY_MAX)
00056         obnamlib.RepositoryTree.__init__(self, fs, 'clientlist', 
00057                                          self.key_bytes, node_size, 
00058                                          upload_queue_size, lru_size, hooks)
00059         self.keep_just_one_tree = True
00060 
00061     def hashfunc(self, string):
00062         return hashlib.new('md5', string).digest()
00063 
00064     def hashkey(self, namehash, client_id, subkey):
00065         return struct.pack(self.fmt, namehash, client_id, subkey)
00066 
00067     def key(self, client_name, client_id, subkey):
00068         h = self.hashfunc(client_name)
00069         return self.hashkey(h, client_id, subkey)
00070 
00071     def unkey(self, key):
00072         return struct.unpack(self.fmt, key)
00073 
00074     def random_id(self):
00075         return random.randint(0, obnamlib.MAX_ID)
00076 
00077     def list_clients(self):
00078         if self.init_forest() and self.forest.trees:
00079             t = self.forest.trees[-1]
00080             return [v 
00081                      for k, v in t.lookup_range(self.minkey, self.maxkey)
00082                      if self.unkey(k)[2] == self.CLIENT_NAME]
00083         else:
00084             return []
00085 
00086     def find_client_id(self, t, client_name):
00087         minkey = self.key(client_name, 0, 0)
00088         maxkey = self.key(client_name, obnamlib.MAX_ID, self.SUBKEY_MAX)
00089         for k, v in t.lookup_range(minkey, maxkey):
00090             checksum, client_id, subkey = self.unkey(k)
00091             if subkey == self.CLIENT_NAME and v == client_name:
00092                 return client_id
00093         return None
00094 
00095     def get_client_id(self, client_name):
00096         if not self.init_forest() or not self.forest.trees:
00097             return None
00098         t = self.forest.trees[-1]
00099         return self.find_client_id(t, client_name)
00100 
00101     def add_client(self, client_name):
00102         logging.info('Adding client %s' % client_name)
00103         self.start_changes()
00104         if self.find_client_id(self.tree, client_name) is None:
00105             while True:
00106                 candidate_id = self.random_id()
00107                 key = self.key(client_name, candidate_id, self.CLIENT_NAME)
00108                 try:
00109                     self.tree.lookup(key)
00110                 except KeyError:
00111                     break
00112             key = self.key(client_name, candidate_id, self.CLIENT_NAME)
00113             self.tree.insert(key, client_name)
00114             logging.debug('Client %s has id %s' % (client_name, candidate_id))
00115         
00116     def remove_client(self, client_name):
00117         logging.info('Removing client %s' % client_name)
00118         self.start_changes()
00119         client_id = self.find_client_id(self.tree, client_name)
00120         if client_id is not None:
00121             key = self.key(client_name, client_id, self.CLIENT_NAME)
00122             self.tree.remove(key)
00123 
00124     def get_client_keyid(self, client_name):
00125         if self.init_forest() and self.forest.trees:
00126             t = self.forest.trees[-1]
00127             client_id = self.find_client_id(t, client_name)
00128             if client_id is not None:
00129                 key = self.key(client_name, client_id, self.KEYID)
00130                 for k, v in t.lookup_range(key, key):
00131                     return v
00132         return None
00133         
00134     def set_client_keyid(self, client_name, keyid):
00135         logging.info('Setting client %s to use key %s' % (client_name, keyid))
00136         self.start_changes()
00137         client_id = self.find_client_id(self.tree, client_name)
00138         key = self.key(client_name, client_id, self.KEYID)
00139         if keyid is None:
00140             self.tree.remove_range(key, key)
00141         else:
00142             self.tree.insert(key, keyid)
00143