shep/shep/store/file.py

213 lines
5.3 KiB
Python

# standard imports
import os
import re
# local imports
from .base import (
re_processedname,
StoreFactory,
)
from shep.error import StateLockedKey
class SimpleFileStore:
"""Filesystem store of contents for state, with one directory per state.
:param path: Filesystem base path for all state directory
:type path: str
"""
def __init__(self, path, binary=False, lock_path=None):
self.__path = path
os.makedirs(self.__path, exist_ok=True)
if binary:
self.__m = ['rb', 'wb']
else:
self.__m = ['r', 'w']
self.__lock_path = lock_path
if self.__lock_path != None:
os.makedirs(lock_path, exist_ok=True)
def __lock(self, k):
if self.__lock_path == None:
return
fp = os.path.join(self.__lock_path, k)
f = None
try:
f = open(fp, 'x')
except FileExistsError:
pass
if f == None:
raise StateLockedKey(k)
f.close()
def __unlock(self, k):
if self.__lock_path == None:
return
fp = os.path.join(self.__lock_path, k)
try:
os.unlink(fp)
except FileNotFoundError:
pass
def put(self, k, contents=None):
"""Add a new key and optional contents
:param k: Content key to add
:type k: str
:param contents: Optional contents to assign for content key
:type contents: any
"""
self.__lock(k)
fp = os.path.join(self.__path, k)
if contents == None:
if self.__m[1] == 'wb':
contents = b''
else:
contents = ''
f = open(fp, self.__m[1])
f.write(contents)
f.close()
self.__unlock(k)
def remove(self, k):
"""Remove a content key from a state.
:param k: Content key to remove from the state
:type k: str
:raises FileNotFoundError: Content key does not exist in the state
"""
self.__lock(k)
fp = os.path.join(self.__path, k)
os.unlink(fp)
self.__unlock(k)
def get(self, k):
"""Retrieve the content for the given content key.
:param k: Content key to retrieve content for
:type k: str
:raises FileNotFoundError: Content key does not exist for the state
:rtype: any
:return: Contents
"""
self.__lock(k)
fp = os.path.join(self.__path, k)
f = open(fp, self.__m[0])
r = f.read()
f.close()
self.__unlock(k)
return r
def list(self):
"""List all content keys persisted for the state.
:rtype: list of str
:return: Content keys in state
"""
self.__lock('.list')
files = []
for p in os.listdir(self.__path):
fp = os.path.join(self.__path, p)
f = None
try:
f = open(fp, self.__m[0])
except FileNotFoundError:
continue
r = f.read()
f.close()
if len(r) == 0:
r = None
files.append((p, r,))
self.__unlock('.list')
return files
def path(self, k=None):
"""Return filesystem path for persisted state or state item.
:param k: If given, will return filesystem path to specified content key
:type k: str
:rtype: str
:return: File path
"""
if k == None:
return self.__path
return os.path.join(self.__path, k)
def replace(self, k, contents):
"""Replace persisted content for persisted content key.
:param k: Content key to replace contents for
:type k: str
:param contents: Contents
:type contents: any
"""
self.__lock(k)
fp = os.path.join(self.__path, k)
os.stat(fp)
f = open(fp, self.__m[1])
r = f.write(contents)
f.close()
self.__unlock(k)
def modified(self, k):
self.__lock(k)
path = self.path(k)
st = os.stat(path)
self.__unlock(k)
return st.st_ctime
def register_modify(self, k):
pass
class SimpleFileStoreFactory(StoreFactory):
"""Provide a method to instantiate SimpleFileStore instances that provide persistence for individual states.
:param path: Filesystem path as base path for states
:type path: str
"""
def __init__(self, path, binary=False, use_lock=False):
self.__path = path
self.__binary = binary
self.__use_lock = use_lock
def add(self, k):
"""Create a new SimpleFileStore for a state.
:param k: Identifier for the state
:type k: str
:rtype: SimpleFileStore
:return: A filesystem persistence instance with the given identifier as subdirectory
"""
lock_path = None
if self.__use_lock:
lock_path = os.path.join(self.__path, '.lock')
k = str(k)
store_path = os.path.join(self.__path, k)
return SimpleFileStore(store_path, binary=self.__binary, lock_path=lock_path)
def ls(self):
r = []
for v in os.listdir(self.__path):
if re.match(re_processedname, v):
r.append(v)
return r
def close(self):
pass