URI: 
       tstorage: factor out 'JsonDB' - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 53130da6828de132f5611d8e137cf94b8eda5206
   DIR parent d2abaf54e80621d2a4076c7dd996e08038e9eade
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Mon, 23 Jul 2018 19:57:41 +0200
       
       storage: factor out 'JsonDB'
       
       Diffstat:
         M electrum/storage.py                 |     157 +++++++++++++++++--------------
       
       1 file changed, 86 insertions(+), 71 deletions(-)
       ---
   DIR diff --git a/electrum/storage.py b/electrum/storage.py
       t@@ -67,15 +67,84 @@ def get_derivation_used_for_hw_device_encryption():
        # storage encryption version
        STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW = range(0, 3)
        
       -class WalletStorage(PrintError):
        
       -    def __init__(self, path, manual_upgrades=False):
       -        self.print_error("wallet path", path)
       -        self.manual_upgrades = manual_upgrades
       -        self.lock = threading.RLock()
       +class JsonDB(PrintError):
       +
       +    def __init__(self, path):
       +        self.db_lock = threading.RLock()
                self.data = {}
                self.path = path
                self.modified = False
       +
       +    def get(self, key, default=None):
       +        with self.db_lock:
       +            v = self.data.get(key)
       +            if v is None:
       +                v = default
       +            else:
       +                v = copy.deepcopy(v)
       +        return v
       +
       +    def put(self, key, value):
       +        try:
       +            json.dumps(key, cls=util.MyEncoder)
       +            json.dumps(value, cls=util.MyEncoder)
       +        except:
       +            self.print_error("json error: cannot save", key)
       +            return
       +        with self.db_lock:
       +            if value is not None:
       +                if self.data.get(key) != value:
       +                    self.modified = True
       +                    self.data[key] = copy.deepcopy(value)
       +            elif key in self.data:
       +                self.modified = True
       +                self.data.pop(key)
       +
       +    @profiler
       +    def write(self):
       +        with self.db_lock:
       +            self._write()
       +
       +    def _write(self):
       +        if threading.currentThread().isDaemon():
       +            self.print_error('warning: daemon thread cannot write db')
       +            return
       +        if not self.modified:
       +            return
       +        s = json.dumps(self.data, indent=4, sort_keys=True, cls=util.MyEncoder)
       +        s = self.encrypt_before_writing(s)
       +
       +        temp_path = "%s.tmp.%s" % (self.path, os.getpid())
       +        with open(temp_path, "w", encoding='utf-8') as f:
       +            f.write(s)
       +            f.flush()
       +            os.fsync(f.fileno())
       +
       +        mode = os.stat(self.path).st_mode if os.path.exists(self.path) else stat.S_IREAD | stat.S_IWRITE
       +        # perform atomic write on POSIX systems
       +        try:
       +            os.rename(temp_path, self.path)
       +        except:
       +            os.remove(self.path)
       +            os.rename(temp_path, self.path)
       +        os.chmod(self.path, mode)
       +        self.print_error("saved", self.path)
       +        self.modified = False
       +
       +    def encrypt_before_writing(self, plaintext: str) -> str:
       +        return plaintext
       +
       +    def file_exists(self):
       +        return self.path and os.path.exists(self.path)
       +
       +
       +class WalletStorage(JsonDB):
       +
       +    def __init__(self, path, manual_upgrades=False):
       +        self.print_error("wallet path", path)
       +        JsonDB.__init__(self, path)
       +        self.manual_upgrades = manual_upgrades
                self.pubkey = None
                if self.file_exists():
                    with open(self.path, "r", encoding='utf-8') as f:
       t@@ -160,9 +229,6 @@ class WalletStorage(PrintError):
                except:
                    return STO_EV_PLAINTEXT
        
       -    def file_exists(self):
       -        return self.path and os.path.exists(self.path)
       -
            @staticmethod
            def get_eckey_from_password(password):
                secret = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'), b'', iterations=1024)
       t@@ -189,6 +255,17 @@ class WalletStorage(PrintError):
                s = s.decode('utf8')
                self.load_data(s)
        
       +    def encrypt_before_writing(self, plaintext: str) -> str:
       +        s = plaintext
       +        if self.pubkey:
       +            s = bytes(s, 'utf8')
       +            c = zlib.compress(s)
       +            enc_magic = self._get_encryption_magic()
       +            public_key = ecc.ECPubkey(bfh(self.pubkey))
       +            s = public_key.encrypt_message(c, enc_magic)
       +            s = s.decode('utf8')
       +        return s
       +
            def check_password(self, password):
                """Raises an InvalidPassword exception on invalid password"""
                if not self.is_encrypted():
       t@@ -211,71 +288,9 @@ class WalletStorage(PrintError):
                    self.pubkey = None
                    self._encryption_version = STO_EV_PLAINTEXT
                # make sure next storage.write() saves changes
       -        with self.lock:
       +        with self.db_lock:
                    self.modified = True
        
       -    def get(self, key, default=None):
       -        with self.lock:
       -            v = self.data.get(key)
       -            if v is None:
       -                v = default
       -            else:
       -                v = copy.deepcopy(v)
       -        return v
       -
       -    def put(self, key, value):
       -        try:
       -            json.dumps(key, cls=util.MyEncoder)
       -            json.dumps(value, cls=util.MyEncoder)
       -        except:
       -            self.print_error("json error: cannot save", key)
       -            return
       -        with self.lock:
       -            if value is not None:
       -                if self.data.get(key) != value:
       -                    self.modified = True
       -                    self.data[key] = copy.deepcopy(value)
       -            elif key in self.data:
       -                self.modified = True
       -                self.data.pop(key)
       -
       -    @profiler
       -    def write(self):
       -        with self.lock:
       -            self._write()
       -
       -    def _write(self):
       -        if threading.currentThread().isDaemon():
       -            self.print_error('warning: daemon thread cannot write wallet')
       -            return
       -        if not self.modified:
       -            return
       -        s = json.dumps(self.data, indent=4, sort_keys=True, cls=util.MyEncoder)
       -        if self.pubkey:
       -            s = bytes(s, 'utf8')
       -            c = zlib.compress(s)
       -            enc_magic = self._get_encryption_magic()
       -            public_key = ecc.ECPubkey(bfh(self.pubkey))
       -            s = public_key.encrypt_message(c, enc_magic)
       -            s = s.decode('utf8')
       -
       -        temp_path = "%s.tmp.%s" % (self.path, os.getpid())
       -        with open(temp_path, "w", encoding='utf-8') as f:
       -            f.write(s)
       -            f.flush()
       -            os.fsync(f.fileno())
       -
       -        mode = os.stat(self.path).st_mode if os.path.exists(self.path) else stat.S_IREAD | stat.S_IWRITE
       -        # perform atomic write on POSIX systems
       -        try:
       -            os.rename(temp_path, self.path)
       -        except:
       -            os.remove(self.path)
       -            os.rename(temp_path, self.path)
       -        os.chmod(self.path, mode)
       -        self.print_error("saved", self.path)
       -        self.modified = False
       -
            def requires_split(self):
                d = self.get('accounts', {})
                return len(d) > 1