gdrive: Add find_file command

Change-Id: I466c2301a932eececb4dee14f51d222d59903806
diff --git a/gdrive_client/gdrive b/gdrive_client/gdrive
index d2b6719..cf54eb0 100755
--- a/gdrive_client/gdrive
+++ b/gdrive_client/gdrive
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 
-# Copyright 2022 Fairphone B.V.
+# Copyright 2022-2023 Fairphone B.V.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
 
 import argparse
 from io import FileIO
+import json
 import logging
 import os
 from pathlib import Path
@@ -549,6 +550,33 @@
     logger.info("Download done.")
 
 
+def cmd_find_file(args: argparse.Namespace, service: Resource) -> None:
+    """Command entry point for "find"."""
+    # Search includes trashed files by default.
+    query_base = (
+        f"mimeType != '{MIMETYPE_GOOGLE_DRIVE_FOLDER}' and trashed = false"
+    )
+    if args.filename:
+        query_filename = f"name = '{args.filename}'"
+    else:
+        query_filename = " and ".join(
+            f"name contains '{part}'" for part in args.filename_contains
+        )
+    query = f"{query_base} and {query_filename}"
+
+    # Google Drive search is fuzzy. Filter by actual search queries.
+    files = list_files_strict(
+        service=service,
+        drive_id=args.drive_id,
+        query=query,
+        filename_match=args.filename
+        if args.filename
+        else args.filename_contains,
+    )
+
+    print(json.dumps(files, ensure_ascii=False, indent=2))
+
+
 def parse_cmdline_arguments() -> argparse.Namespace:
     """Parse the command line arguments.
 
@@ -732,6 +760,42 @@
         required=True,
     )
 
+    parser_find_file = subparsers.add_parser(
+        "find_file",
+        help=(
+            "Find files by name on a Drive. Print attributes of matching files "
+            "as json."
+        ),
+    )
+    parser_find_file.set_defaults(func=cmd_find_file)
+    parser_find_file_filename_args = (
+        parser_find_file.add_mutually_exclusive_group(required=True)
+    )
+    parser_find_file_filename_args.add_argument(
+        "-n",
+        "--filename",
+        help="Find files by unique filename.",
+        type=str,
+    )
+    parser_find_file_filename_args.add_argument(
+        "-c",
+        "--filename-contains",
+        help=(
+            "Find file with containing a string. If set multiple times, all of "
+            'the "contains" parameters must match.'
+        ),
+        type=str,
+        action="append",
+    )
+    parser_find_file.add_argument(
+        "-d",
+        "--drive-id",
+        help=(
+            'ID of the Google Drive to search within. Defaults to "My Drive".'
+        ),
+        type=str,
+    )
+
     args = parser.parse_args()
     # Pass-through the parser object to sub commands for error handling.
     args.parser = parser