shep

Multi-state key stores using bit masks for python3
git clone git://git.defalsify.org/shep.git
Log | Files | Refs | LICENSE

file.py (5998B)


      1 # standard imports
      2 import os
      3 import re
      4 import stat
      5 
      6 # local imports
      7 from .base import (
      8         re_processedname,
      9         StoreFactory,
     10         )
     11 from shep.error import StateLockedKey
     12 
     13 
     14 class SimpleFileStore:
     15     """Filesystem store of contents for state, with one directory per state.
     16 
     17     :param path: Filesystem base path for all state directory
     18     :type path: str
     19     """
     20     def __init__(self, path, binary=False, lock_path=None):
     21         self.__path = path
     22         os.makedirs(self.__path, exist_ok=True)
     23         if binary:
     24             self.__m = ['rb', 'wb']
     25         else:
     26             self.__m = ['r', 'w']
     27         self.__lock_path = lock_path
     28         if self.__lock_path != None:
     29             os.makedirs(lock_path, exist_ok=True)
     30 
     31 
     32     def __lock(self, k):
     33         if self.__lock_path == None:
     34             return
     35         fp = os.path.join(self.__lock_path, k)
     36         f = None
     37         try:
     38             f = open(fp, 'x')
     39         except FileExistsError:
     40             pass
     41         if f == None:
     42             raise StateLockedKey(k)
     43         f.close()
     44 
     45 
     46     def __unlock(self, k):
     47         if self.__lock_path == None:
     48             return
     49         fp = os.path.join(self.__lock_path, k)
     50         try:
     51             os.unlink(fp)
     52         except FileNotFoundError:
     53             pass
     54         
     55 
     56     def put(self, k, contents=None):
     57         """Add a new key and optional contents 
     58 
     59         :param k: Content key to add 
     60         :type k: str
     61         :param contents: Optional contents to assign for content key
     62         :type contents: any
     63         """
     64         self.__lock(k)
     65         fp = os.path.join(self.__path, k)
     66         if contents == None:
     67             if self.__m[1] == 'wb':
     68                 contents = b''
     69             else:
     70                 contents = ''
     71 
     72         f = open(fp, self.__m[1])
     73         f.write(contents)
     74         f.close()
     75         self.__unlock(k)
     76 
     77 
     78     def remove(self, k):
     79         """Remove a content key from a state.
     80 
     81         :param k: Content key to remove from the state
     82         :type k: str
     83         :raises FileNotFoundError: Content key does not exist in the state
     84         """
     85         self.__lock(k)
     86         fp = os.path.join(self.__path, k)
     87         os.unlink(fp)
     88         self.__unlock(k)
     89 
     90     
     91     def get(self, k):
     92         """Retrieve the content for the given content key.
     93 
     94         :param k: Content key to retrieve content for
     95         :type k: str
     96         :raises FileNotFoundError: Content key does not exist for the state
     97         :rtype: any
     98         :return: Contents
     99         """
    100         self.__lock(k)
    101         fp = os.path.join(self.__path, k)
    102         f = open(fp, self.__m[0])
    103         r = f.read()
    104         f.close()
    105         self.__unlock(k)
    106         return r
    107 
    108 
    109     def list(self):
    110         """List all content keys persisted for the state.
    111 
    112         :rtype: list of str
    113         :return: Content keys in state
    114         """
    115         self.__lock('.list')
    116         files = []
    117         for p in os.listdir(self.__path):
    118             fp = os.path.join(self.__path, p)
    119             f = None
    120             try:
    121                 f = open(fp, self.__m[0])
    122             except FileNotFoundError:
    123                 continue
    124             r = f.read()
    125             f.close()
    126             if len(r) == 0:
    127                 r = None
    128             files.append((p, r,))
    129         self.__unlock('.list')
    130         return files
    131 
    132 
    133     def path(self, k=None):
    134         """Return filesystem path for persisted state or state item.
    135 
    136         :param k: If given, will return filesystem path to specified content key
    137         :type k: str
    138         :rtype: str
    139         :return: File path
    140         """
    141         if k == None:
    142             return self.__path
    143         return os.path.join(self.__path, k)
    144 
    145 
    146     def replace(self, k, contents):
    147         """Replace persisted content for persisted content key.
    148 
    149         :param k: Content key to replace contents for
    150         :type k: str
    151         :param contents: Contents
    152         :type contents: any
    153         """
    154         self.__lock(k)
    155         fp = os.path.join(self.__path, k)
    156         os.stat(fp)
    157         f = open(fp, self.__m[1])
    158         r = f.write(contents)
    159         f.close()
    160         self.__unlock(k)
    161 
    162 
    163     def modified(self, k):
    164         self.__lock(k)
    165         path = self.path(k)
    166         st = os.stat(path)
    167         self.__unlock(k)
    168         return st.st_ctime
    169 
    170 
    171     def register_modify(self, k):
    172         pass
    173 
    174 
    175 class SimpleFileStoreFactory(StoreFactory):
    176     """Provide a method to instantiate SimpleFileStore instances that provide persistence for individual states.
    177 
    178     :param path: Filesystem path as base path for states
    179     :type path: str
    180     """
    181     def __init__(self, path, binary=False, use_lock=False):
    182         self.__path = path
    183         self.__binary = binary
    184         self.__use_lock = use_lock
    185 
    186 
    187     def add(self, k):
    188         """Create a new SimpleFileStore for a state.
    189 
    190         :param k: Identifier for the state
    191         :type k: str
    192         :rtype: SimpleFileStore
    193         :return: A filesystem persistence instance with the given identifier as subdirectory
    194         """
    195         lock_path = None
    196         if self.__use_lock:
    197             lock_path = os.path.join(self.__path, '.lock')
    198 
    199         k = str(k)
    200         store_path = os.path.join(self.__path, k)
    201         return SimpleFileStore(store_path, binary=self.__binary, lock_path=lock_path)
    202 
    203 
    204     def ls(self):
    205         r = []
    206         for v in os.listdir(self.__path):
    207             if re.match(re_processedname, v):
    208                 fp = os.path.join(self.__path, v)
    209                 st = os.stat(fp)
    210                 if stat.S_ISDIR(st.st_mode):
    211                     r.append(v)
    212         return r
    213 
    214 
    215     def have(self, k):
    216         lock_path = None
    217         if self.__use_lock:
    218             lock_path = os.path.join(self.__path, '.lock')
    219         for d in self.ls():
    220             p = os.path.join(self.__path, d)
    221             s = SimpleFileStore(p, binary=self.__binary, lock_path=lock_path)
    222             try:
    223                 s.get(k)
    224             except:
    225                 return False
    226             return True
    227 
    228 
    229     def close(self):
    230         pass