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