2
0
mirror of https://github.com/ComradCollective/Comrad synced 2024-11-17 21:25:37 +00:00
Comrad/p2p/kademlia/storage.py

285 lines
7.7 KiB
Python
Raw Normal View History

2020-08-19 13:01:37 +00:00
import time
from itertools import takewhile
import operator
from collections import OrderedDict
from abc import abstractmethod, ABC
2020-08-20 21:45:30 +00:00
import asyncio
2020-08-21 08:18:20 +00:00
from kademlia.utils import digest
2020-08-22 08:56:33 +00:00
#BSEP_ST = b'||||'
2020-08-20 19:45:10 +00:00
2020-08-20 21:10:59 +00:00
import base64,json
2020-08-20 18:54:31 +00:00
def xprint(*xx):
raise Exception('\n'.join(str(x) for x in xx))
2020-08-20 21:10:59 +00:00
import logging
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger = logging.getLogger(__file__)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
log=logger.info
2020-08-19 13:01:37 +00:00
class IStorage(ABC):
"""
Local storage for this node.
IStorage implementations of get must return the same type as put in by set
"""
@abstractmethod
def __setitem__(self, key, value):
"""
Set a key to the given value.
"""
@abstractmethod
def __getitem__(self, key):
"""
Get the given key. If item doesn't exist, raises C{KeyError}
"""
@abstractmethod
def get(self, key, default=None):
"""
Get given key. If not found, return default.
"""
@abstractmethod
def iter_older_than(self, seconds_old):
"""
Return the an iterator over (key, value) tuples for items older
than the given secondsOld.
"""
@abstractmethod
def __iter__(self):
"""
Get the iterator for this storage, should yield tuple of (key, value)
"""
2020-08-20 21:10:59 +00:00
# class ForgetfulStorage(IStorage):
# def __init__(self, ttl=604800):
# """
# By default, max age is a week.
# """
# self.data = OrderedDict()
# self.ttl = ttl
# def __setitem__(self, key, value):
# if key in self.data:
# del self.data[key]
# self.data[key] = (time.monotonic(), value)
# self.cull()
# def cull(self):
# for _, _ in self.iter_older_than(self.ttl):
# self.data.popitem(last=False)
# def get(self, key, default=None):
# self.cull()
# if key in self.data:
# return self[key]
# return default
# def __getitem__(self, key):
# self.cull()
# return self.data[key][1]
# def __repr__(self):
# self.cull()
# return repr(self.data)
# def iter_older_than(self, seconds_old):
# min_birthday = time.monotonic() - seconds_old
# zipped = self._triple_iter()
# matches = takewhile(lambda r: min_birthday >= r[1], zipped)
# return list(map(operator.itemgetter(0, 2), matches))
# def _triple_iter(self):
# ikeys = self.data.keys()
# ibirthday = map(operator.itemgetter(0), self.data.values())
# ivalues = map(operator.itemgetter(1), self.data.values())
# return zip(ikeys, ibirthday, ivalues)
# def __iter__(self):
# self.cull()
# ikeys = self.data.keys()
# ivalues = map(operator.itemgetter(1), self.data.values())
# return zip(ikeys, ivalues)
2020-08-20 21:45:30 +00:00
import pickle,os
2020-08-20 21:10:59 +00:00
class HalfForgetfulStorage(IStorage):
def __init__(self, fn='dbm.pickle', ttl=604800, log=None):
2020-08-19 13:01:37 +00:00
"""
By default, max age is a week.
"""
2020-08-20 21:10:59 +00:00
self.fn = fn
2020-08-19 13:01:37 +00:00
self.ttl = ttl
2020-08-20 21:45:30 +00:00
self.log = logger.info
2020-08-21 13:49:42 +00:00
self.data = self.load()
2020-08-20 21:45:30 +00:00
# import pickledb
# self.data = pickledb.load(self.fn,auto_dump=True)
#import shelve
#self.data = shelve.open(self.fn,flag='cs')
2020-08-19 13:01:37 +00:00
2020-08-21 08:18:20 +00:00
def dump(self,show_keys=100):
2020-08-20 21:45:30 +00:00
async def do():
2020-08-21 08:18:20 +00:00
msg='[async!!] dumping %s keys...' % len(self.keys())
2020-08-20 21:45:30 +00:00
with open(self.fn,'wb') as of:
pickle.dump(self.data, of)
asyncio.create_task(do())
def load(self):
2020-08-21 13:49:42 +00:00
if not os.path.exists(self.fn): return OrderedDict()
2020-08-20 21:45:30 +00:00
self.log('loading pickle...')
with open(self.fn,'rb') as of:
2020-08-21 13:33:01 +00:00
res=pickle.load(of)
self.log(f'>> found {len(res)} keys in pickle...')
2020-08-21 13:33:49 +00:00
return res
2020-08-20 21:10:59 +00:00
2020-08-19 13:01:37 +00:00
def __setitem__(self, key, value):
2020-08-20 21:10:59 +00:00
self.set(key,value)
def keys(self): return self.data.keys()
2020-08-20 21:45:30 +00:00
def items(self): return [(k,v) for k,v in zip(self.keys(),self.values())]
def values(self): return [self.data[k] for k in self.keys()]
2020-08-20 21:10:59 +00:00
2020-08-21 13:49:42 +00:00
def set(self,dkey,value):
2020-08-20 22:07:09 +00:00
# log(f'HFS.set({key}) -> {value}')
2020-08-21 08:18:20 +00:00
newval = (time.monotonic(), value)
2020-08-20 21:10:59 +00:00
# store
2020-08-21 08:18:20 +00:00
if dkey in self.data:
del self.data[dkey]
self.data[dkey]=newval
2020-08-20 21:10:59 +00:00
# save and prune
2020-08-21 13:25:46 +00:00
self.dump()
2020-08-20 21:45:30 +00:00
# self.cull()
2020-08-19 13:01:37 +00:00
2020-08-20 21:10:59 +00:00
2020-08-19 13:01:37 +00:00
def cull(self):
for _, _ in self.iter_older_than(self.ttl):
self.data.popitem(last=False)
2020-08-20 21:10:59 +00:00
def get(self, key, default=None, incl_time=False):
#self.cull()
2020-08-20 22:07:09 +00:00
# log(f'HFS.get({key}) -> ?')
2020-08-20 21:10:59 +00:00
try:
val=self.data[key]
2020-08-20 21:45:30 +00:00
# val=self.data.get(key)
2020-08-20 22:07:09 +00:00
# log(f'HFS.get({key}) -> {val}')
2020-08-20 21:45:30 +00:00
if val is False: raise KeyError
if val and not incl_time: val=val[1]
2020-08-20 21:10:59 +00:00
return val
except (KeyError,IndexError) as e:
pass
2020-08-19 13:01:37 +00:00
return default
def __getitem__(self, key):
2020-08-20 21:10:59 +00:00
#self.cull()
return self.get(key)
2020-08-19 13:01:37 +00:00
2020-08-21 13:49:42 +00:00
def __repr__(self,lim_eg=5):
2020-08-20 21:10:59 +00:00
#self.cull()
2020-08-21 08:18:20 +00:00
#return repr(self.data)
2020-08-22 14:26:21 +00:00
#eg = list(sorted(self.data.keys()))[:lim_eg]
msg=f"""HFS() # keys = {len(self.data)}"""
2020-08-21 08:18:20 +00:00
return msg
2020-08-19 13:01:37 +00:00
def iter_older_than(self, seconds_old):
min_birthday = time.monotonic() - seconds_old
zipped = self._triple_iter()
matches = takewhile(lambda r: min_birthday >= r[1], zipped)
return list(map(operator.itemgetter(0, 2), matches))
def _triple_iter(self):
2020-08-20 21:45:30 +00:00
ikeys = self.keys()
ibirthday = map(operator.itemgetter(0), self.values())
ivalues = map(operator.itemgetter(1), self.values())
2020-08-19 13:01:37 +00:00
return zip(ikeys, ibirthday, ivalues)
def __iter__(self):
self.cull()
2020-08-20 21:45:30 +00:00
ikeys = self.keys()
ivalues = map(operator.itemgetter(1), self.values())
2020-08-19 13:01:37 +00:00
return zip(ikeys, ivalues)
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# class HalfForgetfulStorage(ForgetfulStorage):
# def __init__(self, fn='dbm', ttl=604800, log=print):
# """
# By default, max age is a week.
# """
# self.fn=fn
# self.log=log
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# import pickledb
# # self.data = pickledb.load(self.fn,False)
# import dbm
# self.data = dbm.open(self.fn,flag='cs')
# # import shelve
# # self.data = shelve.open(self.fn, flag='cs')
# # from kivy.storage.jsonstore import JsonStore
# # self.
# self.ttl = ttl
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# self.log('have %s keys' % len(self))
2020-08-20 20:01:00 +00:00
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# def keys(self):
# # return self.data.getall()
# return self.data.keys()
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# def __len__(self):
# return len(self.keys())
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# def __setitem__(self, key, value):
# self.set(key,value)
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# def set(self, key,value):# try:
# #self.log(f'key: {key},\nvalue:{value}')
# #if type(value)==list and len(value)==2:
# # time,val_b = value
# # value = str(time).encode() + BSEP_ST + val_b
# #self.log('newdat =',value)
2020-08-20 20:16:18 +00:00
2020-08-20 21:10:59 +00:00
# self.data[key]=value
# # return True
# def get(self, key, default=None):
# # print(f'??!?\n{key}\n{self.data[key]}')
# # return self.data[key][1]
# # (skip time part of tuple)
# # val=self.data[key] if key in self.data else None
# # self.log('VALLLL',val)
# # if val is None: return None
# # time_b,val_b = val.split(BSEP_ST)
# # rval = (float(time_b.decode()), val_b)
# # self.log('rvalll',rval)
# # return rval
# return self.data.get(key,None)
# def __getitem__(self, key):
# return self.get(key)
2020-08-20 18:54:31 +00:00
2020-08-20 21:10:59 +00:00
# #return data_list