Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ needles = [
]
haystack_file_path = './addr_list.txt'

addresses_found_list: List[str] = search_multiple_in_file(
addresses_found_list: list[str] = search_multiple_in_file(
haystack_file_path=haystack_file_path,
needles=needled,
)
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ used_addr_check = "used_addr_check.cli:main_cli"

[tool.pyright]
typeCheckingMode = "basic"

[tool.ruff.lint]
select = ["ALL"]
ignore = ["D", "S", "TD", "FIX", "COM812"]
27 changes: 20 additions & 7 deletions src/used_addr_check/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
__VERSION__ = "0.1.6"
__AUTHOR__ = "RecRanger"

from .index_create import ( # noqa F401
load_or_generate_index,
from .cli import main_cli
from .index_create import (
generate_index,
store_index_json,
load_index_json,
store_index_parquet,
load_index_parquet,
load_or_generate_index,
store_index_json,
store_index_parquet,
)
from .index_search import ( # noqa F401
from .index_search import (
search_in_file_with_index,
search_multiple_in_file, # <- main library function
)
from .index_types import IndexEntry # noqa F401
from .cli import main_cli # noqa F401
from .index_types import IndexEntry

__all__ = [
"IndexEntry",
"generate_index",
"load_index_json",
"load_index_parquet",
"load_or_generate_index",
"main_cli",
"search_in_file_with_index",
"search_multiple_in_file",
"store_index_json",
"store_index_parquet",
]
59 changes: 30 additions & 29 deletions src/used_addr_check/cli.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# from used_addr_check.download_list import download_list, BITCOIN_LIST_URL
import argparse
import sys
from pathlib import Path

from used_addr_check import __VERSION__
from used_addr_check.defaults import DEFAULT_INDEX_CHUNK_SIZE
from used_addr_check.download_list import BITCOIN_LIST_URL, download_list
from used_addr_check.index_create import load_or_generate_index
from used_addr_check.index_search import search_multiple_in_file
from used_addr_check.scan_file import scan_file_for_used_addresses
from used_addr_check.defaults import DEFAULT_INDEX_CHUNK_SIZE

import argparse
import sys
from pathlib import Path


def main_cli():
def main_cli() -> None:
parser = argparse.ArgumentParser(
description="CLI for file processing and searching"
)
Expand All @@ -32,23 +32,21 @@ def main_cli():
subparsers = parser.add_subparsers(dest="command")

# # Subparser for the 'download' command
# download_parser = subparsers.add_parser(
# "download", help="Download the file"
# )
# download_parser.add_argument(
# "-o",
# "--output",
# dest="output_path",
# required=True,
# help="Output file path (should end in .gz)",
# )
# download_parser.add_argument(
# "-u",
# "--url",
# dest="url",
# default=BITCOIN_LIST_URL,
# help="URL to download the file from",
# )
download_parser = subparsers.add_parser("download", help="Download the file")
download_parser.add_argument(
"-o",
"--output",
dest="output_path",
required=True,
help="Output file path (should end in .gz)",
)
download_parser.add_argument(
"-u",
"--url",
dest="url",
default=BITCOIN_LIST_URL,
help="URL to download the file from",
)

# Subparser for the 'version' command (subparser not really used)
subparsers.add_parser(
Expand All @@ -59,7 +57,10 @@ def main_cli():
# Subparser for the 'index' command
index_parser = subparsers.add_parser(
"index",
help="Index a haystack 'used addresses' file, save it to orig_name.txt.index.json", # noqa
help=(
"Index a haystack 'used addresses' file, "
"and save it to orig_name.txt.index.json"
),
)
index_parser.add_argument(
"-f",
Expand Down Expand Up @@ -90,7 +91,7 @@ def main_cli():
# Subparser for the 'scan_file' command
scan_file_parser = subparsers.add_parser(
"scan_file",
help="Scan a file for bitcoin addresses, and see which ones have been used.", # noqa
help="Scan a file for bitcoin addresses, and see which ones have been used.",
)
scan_file_parser.add_argument(
"-f",
Expand All @@ -111,7 +112,7 @@ def main_cli():
args = parser.parse_args()

if args.command == "version" or args.version:
print(f"used_addr_scan version v{__VERSION__}")
print(f"used_addr_scan version v{__VERSION__}") # noqa: T201
sys.exit(0)
elif args.command == "index":
load_or_generate_index(
Expand All @@ -125,8 +126,8 @@ def main_cli():
args.needles,
index_chunk_size=args.index_chunk_size,
)
# elif args.command == "download":
# download_list(Path(args.output_path))
elif args.command == "download":
download_list(Path(args.output_path))
elif args.command == "scan_file":
scan_file_for_used_addresses(
Path(args.haystack_file_path),
Expand Down
21 changes: 8 additions & 13 deletions src/used_addr_check/download_list.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from pathlib import Path

import backoff
from loguru import logger
import requests
from loguru import logger

# TODO: This download feature isn't really production-ready.
# TODO: Make the downloader as fast as wget.
# TODO: Create an extract function.


BITCOIN_LIST_URL = (
"http://alladdresses.loyce.club/all_Bitcoin_addresses_ever_used_sorted.txt.gz" # noqa
"http://alladdresses.loyce.club/all_Bitcoin_addresses_ever_used_sorted.txt.gz"
)


Expand All @@ -31,6 +31,7 @@ def _get_remote_file_size(url: str) -> int:
)
def _download_file(url: str, dest: Path) -> Path:
"""Download a file from a URL to a local destination, resuming if possible.

If the file already exists and is fully downloaded, do nothing.
If the file is partially downloaded, resume the download.
If the file does not exist, download it from scratch.
Expand All @@ -40,22 +41,16 @@ def _download_file(url: str, dest: Path) -> Path:
"""
total_size = _get_remote_file_size(url)

if dest.is_dir():
destination = dest / Path(url).name
else:
destination = dest
destination = (dest / Path(url).name) if dest.is_dir() else dest

# Check how much of the file has already been downloaded
if destination.exists():
current_size = destination.stat().st_size
else:
current_size = 0
# Check how much of the file has already been downloaded.
current_size = destination.stat().st_size if destination.exists() else 0

# If file is already fully downloaded
if current_size == total_size:
logger.info("File already downloaded.")
return destination
elif current_size > total_size:
if current_size > total_size:
logger.warning(
f"File size mismatch. Current size: {current_size:,} bytes, "
f"Total size: {total_size:,} bytes"
Expand All @@ -72,7 +67,7 @@ def _download_file(url: str, dest: Path) -> Path:
with requests.get(url, headers=headers, stream=True) as r:
r.raise_for_status()
# Open the file in append mode, create if does not exist
with open(destination, "ab") as f:
with destination.open("ab") as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
Expand Down
31 changes: 16 additions & 15 deletions src/used_addr_check/index_create.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
from pathlib import Path
from typing import List

from loguru import logger
import orjson
import polars as pl
from loguru import logger
from tqdm import tqdm

from used_addr_check.index_types import IndexEntry
from used_addr_check.defaults import DEFAULT_INDEX_CHUNK_SIZE
from used_addr_check.index_types import IndexEntry


def generate_index(
haystack_file_path: Path, index_chunk_size: int = DEFAULT_INDEX_CHUNK_SIZE
) -> List[IndexEntry]:
) -> list[IndexEntry]:
"""
Generates an index for a large sorted text file, storing every
`index_chunk_size` lines.
Expand All @@ -25,7 +24,7 @@ def generate_index(
- List[IndexEntry]: A list of tuples containing
line text, byte offset, and line number.
"""
index: List[IndexEntry] = []
index: list[IndexEntry] = []
haystack_file_size = haystack_file_path.stat().st_size
with (
tqdm(
Expand All @@ -35,7 +34,7 @@ def generate_index(
total=haystack_file_size,
desc="Indexing haystack file",
) as progress_bar,
open(haystack_file_path, "rb") as file,
haystack_file_path.open("rb") as file,
):
offset = 0
for line_number, line in enumerate(file):
Expand All @@ -56,7 +55,7 @@ def generate_index(
return index


def store_index_json(index: List[IndexEntry], index_json_file_path: Path) -> None:
def store_index_json(index: list[IndexEntry], index_json_file_path: Path) -> None:
"""
Stores the index in a file for later use.

Expand All @@ -65,11 +64,11 @@ def store_index_json(index: List[IndexEntry], index_json_file_path: Path) -> Non
- index_file_path (Path): The path to store the index.
"""

with open(index_json_file_path, "wb") as file:
with index_json_file_path.open("wb") as file:
file.write(orjson.dumps(index))


def load_index_json(index_json_file_path: Path) -> List[IndexEntry]:
def load_index_json(index_json_file_path: Path) -> list[IndexEntry]:
"""
Loads an index from a JSON file.

Expand All @@ -79,12 +78,12 @@ def load_index_json(index_json_file_path: Path) -> List[IndexEntry]:
Returns:
- List[IndexEntry]: The loaded index.
"""
with open(index_json_file_path, "rb") as file:
raw_read: List[dict] = orjson.loads(file.read())
with index_json_file_path.open("rb") as file:
raw_read: list[dict] = orjson.loads(file.read())
return [IndexEntry(**val) for val in raw_read]


def store_index_parquet(index: List[IndexEntry], index_parquet_file_path: Path) -> None:
def store_index_parquet(index: list[IndexEntry], index_parquet_file_path: Path) -> None:
"""
Stores the index in a parquet file for later use.

Expand All @@ -97,7 +96,7 @@ def store_index_parquet(index: List[IndexEntry], index_parquet_file_path: Path)
df.write_parquet(index_parquet_file_path)


def load_index_parquet(index_parquet_file_path: Path) -> List[IndexEntry]:
def load_index_parquet(index_parquet_file_path: Path) -> list[IndexEntry]:
"""
Loads an index from a parquet file.

Expand All @@ -114,8 +113,9 @@ def load_index_parquet(index_parquet_file_path: Path) -> List[IndexEntry]:
def load_or_generate_index(
haystack_file_path: Path,
index_chunk_size: int = DEFAULT_INDEX_CHUNK_SIZE,
*,
force_recreate: bool = False,
) -> List[IndexEntry]:
) -> list[IndexEntry]:
"""Attempts to load an index from a file, or generates one if it doesn't,
or if `force_recreate` is enabled.

Expand Down Expand Up @@ -153,4 +153,5 @@ def load_or_generate_index(
logger.info(f"Index loaded with {len(index):,} entries")
return index

raise Exception("This should be unreachable.")
msg = "This should be unreachable."
raise RuntimeError(msg)
Loading