#!/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 [ DAYS | WEEKS | MONTHS | YEARS ] · [ DAYS | WEEKS | MONTHS | YEARS ] AGO 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: · widthheight - only if file has width and height properties · heightwidth - 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 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 ") 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)