import asyncio import logging import os import traceback import typing from binascii import hexlify, unhexlify from lbrynet.extras.wallet.dewies import dewies_to_lbc, lbc_to_dewies from lbrynet.conf import Config from lbrynet.schema.claim import ClaimDict from lbrynet.schema.decode import smart_decode from lbrynet.blob.CryptBlob import CryptBlobInfo from lbrynet.dht.constants import dataExpireTimeout from torba.client.basedatabase import SQLiteMixin log = logging.getLogger(__name__) def calculate_effective_amount(amount: str, supports: typing.Optional[typing.List[typing.Dict]] = None) -> str: return dewies_to_lbc( lbc_to_dewies(amount) + sum([lbc_to_dewies(support['amount']) for support in supports]) ) def _get_next_available_file_name(download_directory, file_name): base_name, ext = os.path.splitext(file_name) i = 0 while os.path.isfile(os.path.join(download_directory, file_name)): i += 1 file_name = "%s_%i%s" % (base_name, i, ext) return os.path.join(download_directory, file_name) def _open_file_for_writing(download_directory, suggested_file_name): file_path = _get_next_available_file_name(download_directory, suggested_file_name) try: file_handle = open(file_path, 'wb') file_handle.close() except IOError: log.error(traceback.format_exc()) raise ValueError( "Failed to open %s. Make sure you have permission to save files to that location." % file_path ) return os.path.basename(file_path) async def open_file_for_writing(download_directory: str, suggested_file_name: str) -> str: """ Used to touch the path of a file to be downloaded. """ return await asyncio.get_event_loop().run_in_executor( None, _open_file_for_writing, download_directory, suggested_file_name ) async def looping_call(interval, fun): while True: try: await fun() except Exception as e: log.exception('Looping call experienced exception:', exc_info=e) await asyncio.sleep(interval) class SQLiteStorage(SQLiteMixin): CREATE_TABLES_QUERY = """ pragma foreign_keys=on; pragma journal_mode=WAL; create table if not exists blob ( blob_hash char(96) primary key not null, blob_length integer not null, next_announce_time integer not null, should_announce integer not null default 0, status text not null, last_announced_time integer, single_announce integer ); create table if not exists stream ( stream_hash char(96) not null primary key, sd_hash char(96) not null references blob, stream_key text not null, stream_name text not null, suggested_filename text not null ); create table if not exists stream_blob ( stream_hash char(96) not null references stream, blob_hash char(96) references blob, position integer not null, iv char(32) not null, primary key (stream_hash, blob_hash) ); create table if not exists claim ( claim_outpoint text not null primary key, claim_id char(40) not null, claim_name text not null, amount integer not null, height integer not null, serialized_metadata blob not null, channel_claim_id text, address text not null, claim_sequence integer not null ); create table if not exists file ( stream_hash text primary key not null references stream, file_name text not null, download_directory text not null, blob_data_rate real not null, status text not null ); create table if not exists content_claim ( stream_hash text unique not null references file, claim_outpoint text not null references claim, primary key (stream_hash, claim_outpoint) ); create table if not exists support ( support_outpoint text not null primary key, claim_id text not null, amount integer not null, address text not null ); create table if not exists reflected_stream ( sd_hash text not null, reflector_address text not null, timestamp integer, primary key (sd_hash, reflector_address) ); """ def __init__(self, conf: Config, path, loop=None): super().__init__(path) self.conf = conf self.content_claim_callbacks = {} self.check_should_announce_lc = None self.loop = loop or asyncio.get_event_loop() async def open(self): await super().open() if 'reflector' not in self.conf.components_to_skip: self.check_should_announce_lc = looping_call( 600, self.verify_will_announce_all_head_and_sd_blobs ) async def close(self): if self.check_should_announce_lc is not None: self.check_should_announce_lc.close() await super().close() async def run_and_return_one_or_none(self, query, *args): for row in await self.db.execute_fetchall(query, args): if len(row) == 1: return row[0] return row async def run_and_return_list(self, query, *args): rows = list(await self.db.execute_fetchall(query, args)) return [col[0] for col in rows] if rows else [] async def run_and_return_id(self, query, *args): return (await self.db.execute(query, args)).lastrowid # # # # # # # # # blob functions # # # # # # # # # def add_completed_blob(self, blob_hash, length, next_announce_time, should_announce, status="finished"): log.debug("Adding a completed blob. blob_hash=%s, length=%i", blob_hash, length) values = (blob_hash, length, next_announce_time or 0, int(bool(should_announce)), status, 0, 0) return self.db.execute("insert or replace into blob values (?, ?, ?, ?, ?, ?, ?)", values) def set_should_announce(self, blob_hash, next_announce_time, should_announce): return self.db.execute( "update blob set next_announce_time=?, should_announce=? where blob_hash=?", (next_announce_time or 0, int(bool(should_announce)), blob_hash) ) def get_blob_status(self, blob_hash): return self.run_and_return_one_or_none( "select status from blob where blob_hash=?", blob_hash ) def add_known_blob(self, blob_hash, length): return self.db.execute( "insert or ignore into blob values (?, ?, ?, ?, ?, ?, ?)", (blob_hash, length, 0, 0, "pending", 0, 0) ) def should_announce(self, blob_hash): return self.run_and_return_one_or_none( "select should_announce from blob where blob_hash=?", blob_hash ) def count_should_announce_blobs(self): return self.run_and_return_one_or_none( "select count(*) from blob where should_announce=1 and status='finished'" ) def get_all_should_announce_blobs(self): return self.run_and_return_list( "select blob_hash from blob where should_announce=1 and status='finished'" ) async def get_all_finished_blobs(self): blob_hashes = await self.run_and_return_list( "select blob_hash from blob where status='finished'" ) return [unhexlify(blob_hash) for blob_hash in blob_hashes] def count_finished_blobs(self): return self.run_and_return_one_or_none( "select count(*) from blob where status='finished'" ) def update_last_announced_blob(self, blob_hash, last_announced): return self.db.execute( "update blob set next_announce_time=?, last_announced_time=?, single_announce=0 where blob_hash=?", (int(last_announced + (dataExpireTimeout / 2)), int(last_announced), blob_hash) ) def should_single_announce_blobs(self, blob_hashes, immediate=False): def set_single_announce(transaction): now = self.loop.time() for blob_hash in blob_hashes: if immediate: transaction.execute( "update blob set single_announce=1, next_announce_time=? " "where blob_hash=? and status='finished'", (int(now), blob_hash) ) else: transaction.execute( "update blob set single_announce=1 where blob_hash=? and status='finished'", (blob_hash, ) ) return self.db.run(set_single_announce) def get_blobs_to_announce(self): def get_and_update(transaction): timestamp = self.loop.time() if self.conf.announce_head_blobs_only: r = transaction.execute( "select blob_hash from blob " "where blob_hash is not null and " "(should_announce=1 or single_announce=1) and next_announce_time