import os import asyncio import logging from typing import Optional, Dict from lbry.db import Database from lbry.blockchain.ledger import Ledger from .wallet import Wallet log = logging.getLogger(__name__) class WalletManager: def __init__(self, ledger: Ledger, db: Database): self.ledger = ledger self.db = db self.wallets: Dict[str, Wallet] = {} def __getitem__(self, wallet_id: str) -> Wallet: try: return self.wallets[wallet_id] except KeyError: raise ValueError(f"Couldn't find wallet: {wallet_id}.") @property def default(self) -> Optional[Wallet]: for wallet in self.wallets.values(): return wallet def get_or_default(self, wallet_id: Optional[str]) -> Wallet: if wallet_id: return self[wallet_id] wallet = self.default if not wallet: raise ValueError("No wallets available.") return wallet def get_or_default_for_spending(self, wallet_id: Optional[str]) -> Wallet: wallet = self.get_or_default(wallet_id) if wallet.is_locked: raise ValueError("Cannot spend funds with locked wallet, unlock first.") return wallet @property def path(self): return os.path.join(self.ledger.conf.wallet_dir, 'wallets') def sync_ensure_path_exists(self): if not os.path.exists(self.path): os.mkdir(self.path) async def ensure_path_exists(self): await asyncio.get_running_loop().run_in_executor( None, self.sync_ensure_path_exists ) async def load(self): wallets_directory = self.path for wallet_id in self.ledger.conf.wallets: if wallet_id in self.wallets: log.warning(f"Ignoring duplicate wallet_id in config: {wallet_id}") continue wallet_path = os.path.join(wallets_directory, wallet_id) if not os.path.exists(wallet_path): if not wallet_id == "default_wallet": # we'll probably generate this wallet, don't show error log.error(f"Could not load wallet, file does not exist: {wallet_path}") continue wallet = await Wallet.from_path(self.ledger, self.db, wallet_path) self.add(wallet) default_wallet = self.default if default_wallet is None: if self.ledger.conf.create_default_wallet: assert self.ledger.conf.wallets[0] == "default_wallet", ( "Requesting to generate the default wallet but the 'wallets' " "config setting does not include 'default_wallet' as the first wallet." ) await self.create( self.ledger.conf.wallets[0], 'Wallet', create_account=self.ledger.conf.create_default_account ) elif not default_wallet.has_accounts and self.ledger.conf.create_default_account: await default_wallet.accounts.generate() def add(self, wallet: Wallet) -> Wallet: self.wallets[wallet.id] = wallet return wallet async def add_from_path(self, wallet_path) -> Wallet: wallet_id = os.path.basename(wallet_path) if wallet_id in self.wallets: existing = self.wallets.get(wallet_id) if existing.storage.path == wallet_path: raise Exception(f"Wallet '{wallet_id}' is already loaded.") raise Exception( f"Wallet '{wallet_id}' is already loaded from '{existing.storage.path}'" f" and cannot be loaded from '{wallet_path}'. Consider changing the wallet" f" filename to be unique in order to avoid conflicts." ) wallet = await Wallet.from_path(self.ledger, self.db, wallet_path) return self.add(wallet) async def create( self, wallet_id: str, name: str, create_account=False, language='en', single_key=False) -> Wallet: if wallet_id in self.wallets: raise Exception(f"Wallet with id '{wallet_id}' is already loaded and cannot be created.") wallet_path = os.path.join(self.path, wallet_id) if os.path.exists(wallet_path): raise Exception(f"Wallet at path '{wallet_path}' already exists, use 'wallet_add' to load wallet.") wallet = await Wallet.create( self.ledger, self.db, wallet_path, name, create_account, language, single_key ) return self.add(wallet)