Files
BagheeraSearch/bagheerasearch.py
Ignacio Serantes 3fb55ee4f3 First commit
2026-03-22 18:13:22 +01:00

270 lines
9.0 KiB
Python
Executable File

#!/usr/bin/env python3
# flake8: noqa: E501
"""
Bagheera Search Tool - CLI Client
"""
__appname__ = "BagheeraSearch"
__version__ = "1.0"
__author__ = "Ignacio Serantes"
__email__ = "kde@aynoa.net"
__license__ = "LGPL"
__status__ = "Production"
# "Prototype, Development, Alpha, Beta, Production, Stable, Deprecated"
import argparse
import json
import signal
import sys
from pathlib import Path
# from baloo_tools import get_resolution
# from date_query_parser import parse_date
from bagheera_search_lib import BagheeraSearcher
# --- CONFIGURATION ---
PROG_NAME = "Bagheera Search Tool"
PROG_ID = "bagheerasearch"
PROG_VERSION = "1.0"
PROG_BY = "Ignacio Serantes"
PROG_DATE = "2026-03-19"
CONFIG_DIR = Path.home() / ".config" / PROG_ID
CONFIG_FILE = CONFIG_DIR / "config.json"
def load_config() -> dict:
"""Loads user configuration from disk."""
if CONFIG_FILE.exists():
try:
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, OSError) as e:
print(f"Warning: Could not load config file: {e}")
return {}
def save_config(config: dict) -> None:
"""Saves user configuration to disk."""
try:
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(config, f, indent=4)
except OSError as e:
print(f"Warning: Could not save config file: {e}")
def print_help_query() -> None:
"""Prints the detailed help for query syntax."""
help_query = f"""Help updated to 2025-01-01.
Baloo offers a rich syntax for searching through your files. Certain attributes of a file can be searched through.
For example 'type' can be used to filter for files based on their general type:
type:Audio or type:Document
The following comparison operators are supported, but note that 'not equal' operator is not available.
· : - contains (only for text comparison)
· = - equal
· > - greater than
· >= - greater than or equal to
· < - less than
· <= - less than or equal to
Currently the following types are supported:
· Archive
· Folder
· Audio
· Video
· Image
· Document
· Spreadsheet
· Presentation
· Text
These expressions can be combined using AND or OR and additional parenthesis, but note that 'NOT' logical operator is not available.
[... omitted for brevity, but includes the full list of searchable properties as in your original script ...]
{PROG_NAME} recognizes some natural language sentences in English, as long as they are capitalized, and transforms them into queries that can be interpreted by the search engine.
Supported natural language sentences and patterns for queries are:
· MODIFIED TODAY
· MODIFIED YESTERDAY
· MODIFIED THIS [ DAY | WEEK | MONTH | YEAR ]
· LAST <NUMBER> [ DAYS | WEEKS | MONTHS | YEARS ]
· <NUMBER> [ DAYS | WEEKS | MONTHS | YEARS ] AGO
<NUMBER> can be any number or a number text from ONE to TWENTY.
Remarks: LAST DAY, if used, is interpreted as YESTERDAY.
Supported expressions for --exclude and --recursive-exclude are:
· width<CMP_OP>height - only if file has width and height properties
· height<CMP_OP>width - only if file has width and height properties
· PORTRAIT - only if file width is greater or equal to height
· LANDSCAPE - only if file height is greater or equal to width
· SQUARE - only if file width equals to height
<CMP_OP> can be: != | >= | <= | = | > | <"""
print(help_query)
def print_version() -> None:
"""Prints version information."""
print(f"{PROG_NAME} v{PROG_VERSION} - {PROG_DATE}")
print(
f"Copyright (C) {PROG_DATE[:4]} by {PROG_BY} and, mostly, "
"the good people at KDE"
)
def signal_handler(sig, frame) -> None:
"""Handles Ctrl+C gracefully."""
print("\nSearch canceled at user request.")
sys.exit(0)
def main():
parser = argparse.ArgumentParser(
description="An improved search tool for Baloo"
)
parser.add_argument("query", nargs="?", help="list of words to query for")
parser.add_argument("-d", "--directory", help="limit search to specified directory")
parser.add_argument("-e", "--exclude", help="Search exclude pattern")
parser.add_argument("-i", "--id", action="store_true", help="show document IDs")
parser.add_argument("-k", "--konsole", action="store_true", help="show files using file:/ and quotes")
parser.add_argument("-l", "--limit", type=int, help="the maximum number of results")
parser.add_argument("-o", "--offset", type=int, help="offset from which to start the search")
parser.add_argument("-r", "--recursive", nargs="?", const="", default=None, help="enable recurse with or without a query")
parser.add_argument("-n", "--recursive-indent", help="recursive indent character")
parser.add_argument("-x", "--recursive-exclude", help="recursion exclude pattern")
parser.add_argument("-s", "--sort", help="sorting criteria <auto|none>")
parser.add_argument("-t", "--type", help="type of Baloo data to be searched")
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode")
parser.add_argument("--day", type=int, help="day fixed filter, --month is required")
parser.add_argument("--month", type=int, help="month fixed filter, --year is required")
parser.add_argument("--year", type=int, help="year filter fixed filter")
parser.add_argument("--help-query", action="store_true", help="show query syntax help")
parser.add_argument("--version", action="store_true", help="show version information")
args, unknown_args = parser.parse_known_args()
query_parts = [args.query] if args.query else []
if unknown_args:
query_parts.extend(unknown_args)
query_text = " ".join(query_parts)
if args.day is not None and args.month is None:
raise ValueError("Missing --month (required when --day is used)")
if args.month is not None and args.year is None:
raise ValueError("Missing --year (requered when --month is used)")
if args.help_query:
print_help_query()
return
if args.version:
print_version()
return
if not query_text and not args.recursive and not args.type and not args.directory:
parser.print_help()
return
# Configuration and Sort restoring
user_config = load_config()
if args.sort:
user_config["last_sort_order"] = args.sort
save_config(user_config)
elif "last_sort_order" in user_config:
args.sort = user_config["last_sort_order"]
# Build options dictionary
main_options = {}
if args.recursive is not None:
main_options["type"] = "folder"
else:
if args.limit is not None:
main_options["limit"] = args.limit
if args.offset is not None:
main_options["offset"] = args.offset
if args.type:
main_options["type"] = args.type
if args.directory:
main_options["directory"] = args.directory
if args.year is not None:
main_options["year"] = args.year
if args.month is not None:
main_options["month"] = args.month
if args.day is not None:
main_options["day"] = args.day
if args.sort:
main_options["sort"] = args.sort
other_options = {
"exclude": args.exclude,
"id": args.id,
"konsole": args.konsole,
"limit": args.limit if args.limit and args.recursive is not None else 99999999999,
"offset": args.offset if args.offset and args.recursive is not None else 0,
"recursive": args.recursive,
"recursive_indent": args.recursive_indent or "",
"recursive_exclude": args.recursive_exclude,
"sort": args.sort,
"type": args.type if args.recursive is not None else None,
"verbose": args.verbose,
}
if other_options["verbose"]:
print(f"Query: '{query_text}'")
print(f"Main Options: {main_options}")
print(f"Other Options: {other_options}")
print("-" * 30)
try:
searcher = BagheeraSearcher()
files_count = 0
# Consumir el generador de la librería
for item in searcher.search(query_text, main_options, other_options):
if other_options["konsole"]:
output = f"file:/'{item['path']}'"
else:
output = item["path"]
if other_options["id"]:
output += f" [ID: {item['id']}]"
print(output)
files_count += 1
if other_options["verbose"]:
if files_count == 0:
print("No results found.")
else:
print(f"Total: {files_count} files found.")
except FileNotFoundError as e:
print(e)
sys.exit(1)
except Exception as e:
print(f"Error executing search: {e}")
sys.exit(1)
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal_handler)
try:
main()
except Exception as e:
print(f"Critical error: {e}")
sys.exit(1)