# Copyright (C) 2025, Red Hat Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#
# Author: Carlos Garnacho <carlosg@gnome.org>

"""
Test offline configuration changes
"""

import gi
from gi.repository import GLib

import os
import pathlib
import time
import configuration as cfg
import fixtures
from trackertestutils.dconf import DConfClient
from fixtures import MountFlags
from minerhelper import MinerFsHelper

class TestOfflineConfigChanges(fixtures.TrackerMinerTest):
    def setUp(self):
        os.makedirs(self.non_recursive_dir, exist_ok=True)
        super(TestOfflineConfigChanges, self).setUp()


    def test_ignored_files(self):
        filename1 = "test-monitored/file1.txt"
        path1 = self.path(filename1)
        uri1 = self.uri(filename1)
        with self.await_document_inserted(path1):
            with open(path1, "w") as f:
                f.write("Foo bar baz")

        self.assertResourceExists(uri1)
        resource_id = self.tracker.get_content_resource_id(uri1)

        dconf = self.sandbox.get_dconf_client()

        filename2 = "test-monitored/file2.txt"
        path2 = self.path(filename2)
        uri2 = self.uri(filename2)

        with self.tracker.await_delete(
            fixtures.DOCUMENTS_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'ignored-files', GLib.Variant.new_strv(['*.txt']))

            with open(path2, "w") as f:
                f.write("Foo bar baz")

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        # Newly created filtered file should not appear either
        self.assertResourceMissing(uri2)

        # Ensure that both files are back by removing the filter
        with self.await_document_inserted(path1):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'ignored-files', GLib.Variant.new_strv([]))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceExists(uri2)


    def test_ignored_directories(self):
        filename = "test-monitored/dir/file1.txt"
        f = pathlib.Path(self.path(filename))
        uri = self.uri(filename)
        dir_uri = self.uri("test-monitored/dir")
        with self.await_document_inserted(f):
            f.parent.mkdir(parents=True, exist_ok=True)
            f.write_text("Foo bar baz")

        self.assertResourceExists(uri)
        resource_id = self.tracker.get_content_resource_id(dir_uri)

        dconf = self.sandbox.get_dconf_client()

        # Set ignored-directories filter, directory and file should disappear
        with self.tracker.await_delete(
            fixtures.FILESYSTEM_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'ignored-directories', GLib.Variant.new_strv(['dir']))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceMissing(dir_uri)
        self.assertResourceMissing(uri)

        # Ensure the file/dir are back after removing the filter
        with self.await_document_inserted(f):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'ignored-directories', GLib.Variant.new_strv([]))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceExists(dir_uri)
        self.assertResourceExists(uri)


    def test_ignored_directories_with_content(self):
        filename = "test-monitored/dir/foo.txt"
        f = pathlib.Path(self.path(filename))
        f.parent.mkdir(parents=True, exist_ok=True)
        uri = self.uri(filename)
        dir_uri = self.uri("test-monitored/dir")
        hidden = pathlib.Path(self.path("test-monitored/dir/hide-me"))
        hidden.write_text("")
        with self.await_document_inserted(f):
            f.write_text("Foo bar baz")

        self.assertResourceExists(uri)
        resource_id = self.tracker.get_content_resource_id(uri)

        dconf = self.sandbox.get_dconf_client()

        # Set ignored-directories-with-content filter, dir and file should disappear
        with self.tracker.await_delete(
            fixtures.DOCUMENTS_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'ignored-directories-with-content', GLib.Variant.new_strv(['hide-me']))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceMissing(dir_uri)
        self.assertResourceMissing(uri)

        # Ensure the file/dir are back after removing the filter
        with self.await_document_inserted(f):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'ignored-directories-with-content', GLib.Variant.new_strv([]))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceExists(dir_uri)
        self.assertResourceExists(uri)


    def test_index_single_directories(self):
        filename = "test-non-recursive/file1.txt"
        path = self.path(filename)
        uri = self.uri(filename)
        dir_uri = self.uri("test-non-recursive")
        with self.await_document_inserted(path):
            with open(path, "w") as f:
                f.write("Foo bar baz")

        self.assertResourceExists(uri)
        self.assertResourceExists(dir_uri)
        resource_id = self.tracker.get_content_resource_id(dir_uri)

        dconf = self.sandbox.get_dconf_client()

        with self.tracker.await_delete(
            fixtures.DOCUMENTS_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-single-directories', GLib.Variant.new_strv([]))
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceMissing(dir_uri)
        self.assertResourceMissing(uri)

        with self.await_document_inserted(path):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-single-directories', GLib.Variant.new_strv([self.non_recursive_dir]))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceExists(dir_uri)
        self.assertResourceExists(uri)


    def test_index_recursive_directories(self):
        filename = "test-monitored/file1.txt"
        path = self.path(filename)
        uri = self.uri(filename)
        dir_uri = self.uri("test-monitored")
        with self.await_document_inserted(path):
            with open(path, "w") as f:
                f.write("Foo bar baz")

        self.assertResourceExists(uri)
        self.assertResourceExists(dir_uri)
        resource_id = self.tracker.get_content_resource_id(dir_uri)

        dconf = self.sandbox.get_dconf_client()

        # Remove directory from configuration, file should disappear
        with self.tracker.await_delete(
            fixtures.DOCUMENTS_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-recursive-directories', GLib.Variant.new_strv([]))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceMissing(dir_uri)
        self.assertResourceMissing(uri)

        # Ensure that dir and file are back after configuring it again
        with self.await_document_inserted(path):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-recursive-directories', GLib.Variant.new_strv([self.indexed_dir]))

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceExists(dir_uri)
        self.assertResourceExists(uri)


    def test_enable_monitors(self):
        dconf = self.sandbox.get_dconf_client()

        start_check = "test-monitored/start_check.txt"

        # Restart the indexer
        with self.await_document_inserted(self.path(start_check)):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'enable-monitors', GLib.Variant.new_boolean(False))

            with open(self.path(start_check), "w") as f:
                f.write("Foo bar baz")

            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        # Create file, test that the file is not detected
        filename1 = "test-monitored/file1.txt"
        path1 = self.path(filename1)
        uri1 = self.uri(filename1)
        with open(path1, "w") as f:
            f.write("Foo bar baz")

        time.sleep(2)
        self.assertResourceMissing(uri1)


class TestMakeHomedirRecursive(fixtures.TrackerMinerTest):
    def setUp(self):
        self.nested_recursive_dir = os.path.join(self.non_recursive_dir, 'recursive')
        os.makedirs(self.nested_recursive_dir, exist_ok=True)
        self.folder_in_nested_recursive_dir = os.path.join(self.nested_recursive_dir, 'folder1')
        os.makedirs(self.folder_in_nested_recursive_dir, exist_ok=True)
        self.nested_non_recursive_dir = os.path.join(self.non_recursive_dir, 'non-recursive')
        os.makedirs(self.nested_non_recursive_dir, exist_ok=True)
        self.folder_in_nested_non_recursive_dir = os.path.join(self.nested_non_recursive_dir, 'folder2')
        os.makedirs(self.folder_in_nested_non_recursive_dir, exist_ok=True)
        super(TestMakeHomedirRecursive, self).setUp()


    def config(self):
        settings = {
            "org.freedesktop.Tracker3.Miner.Files": {
                "index-recursive-directories": GLib.Variant.new_strv(
                    [self.nested_recursive_dir]
                ),
                "index-single-directories": GLib.Variant.new_strv(
                    [self.non_recursive_dir, self.nested_non_recursive_dir]
                ),
            }
        }
        return settings


    def __get_index_folder(self, filepath):
        result = self.tracker.query(
            """
          SELECT DISTINCT ?p WHERE {
              ?u a nfo:FileDataObject ;
                 nie:url \"%s\" ;
                 nie:dataSource/nie:isStoredAs ?p
          }
          """
            % (self.uri(filepath))
        )
        self.assertEqual(len(result), 1)
        return result[0][0]


    def __list_index_folders(self):
        return self.tracker.query(
            """
            SELECT DISTINCT ?u WHERE {
              ?root a tracker:IndexedFolder;
                nie:isStoredAs ?u .
            }
            """
        )


    def test_make_homedir_recursive(self):
        filename = "test-non-recursive/file1.txt"
        rec_filename = "test-non-recursive/recursive/folder1/file2.txt"
        rec_filename2 = "test-non-recursive/non-recursive/folder2/file3.txt"
        non_rec_filename = "test-non-recursive/non-recursive/file4.txt"

        with open(self.path(rec_filename2), "w") as f:
            f.write(rec_filename2)

        with self.await_document_inserted(self.path(filename)):
            with open(self.path(filename), "w") as f:
                f.write(filename)

        self.assertEqual(
            self.__get_index_folder(self.path(filename)),
            self.uri('test-non-recursive'))

        with self.await_document_inserted(self.path(rec_filename)):
            with open(self.path(rec_filename), "w") as f:
                f.write(rec_filename)

        self.assertEqual(
            self.__get_index_folder(self.path(rec_filename)),
            self.uri('test-non-recursive/recursive'))

        with self.await_document_inserted(self.path(non_rec_filename)):
            with open(self.path(non_rec_filename), "w") as f:
                f.write(non_rec_filename)

        self.assertEqual(
            self.__get_index_folder(self.path(non_rec_filename)),
            self.uri('test-non-recursive/non-recursive'))

        # This file should be initially not indexed
        self.assertResourceMissing(self.uri(rec_filename2))

        roots = self.__list_index_folders()
        self.assertEqual(len(roots), 3)

        # Make the base dir the only folder indexed, recursively
        dconf = self.sandbox.get_dconf_client()

        # The file that was not indexed previously, should be now
        with self.await_document_inserted(self.path(rec_filename2)):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-recursive-directories', GLib.Variant.new_strv([self.non_recursive_dir]))

            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-single-directories', GLib.Variant.new_strv([]))
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.ensure_document_inserted(self.path(filename))
        self.ensure_document_inserted(self.path(rec_filename))
        self.ensure_document_inserted(self.path(rec_filename2))
        self.ensure_document_inserted(self.path(non_rec_filename))

        self.assertEqual(
            self.__get_index_folder(self.path(filename)),
            self.uri('test-non-recursive'))
        self.assertEqual(
            self.__get_index_folder(self.path(rec_filename)),
            self.uri('test-non-recursive'))
        self.assertEqual(
            self.__get_index_folder(self.path(rec_filename2)),
            self.uri('test-non-recursive'))
        self.assertEqual(
            self.__get_index_folder(self.path(non_rec_filename)),
            self.uri('test-non-recursive'))

        roots = self.__list_index_folders()
        self.assertEqual(roots, [[self.uri(self.non_recursive_dir)]])
        self.assertEqual(len(roots), 1)


class TestOfflineConfigMount(fixtures.TrackerMinerRemovableMediaTest):
    """Test configuration with (non-)removable mounts"""

    def config(self):
        settings = super(TestOfflineConfigMount, self).config()
        settings["org.freedesktop.Tracker3.Miner.Files"][
            "index-removable-devices"
        ] = GLib.Variant.new_boolean(False)
        return settings

    def setUp(self):
        super(TestOfflineConfigMount, self).setUp()
        self.device_path = pathlib.Path(self.workdir).joinpath("mount-1")


    def test_non_removable_in_index_single_directories(self):
        self.device_path.mkdir()
        path = self.device_path.joinpath("file1.txt")
        path.write_text("Foo bar baz")
        self.add_removable_device(self.device_path, MountFlags.NON_REMOVABLE)

        dconf = self.sandbox.get_dconf_client()

        with self.await_document_inserted(path):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-single-directories', GLib.Variant.new_strv([str(self.device_path)]))
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceExists(path.as_uri())
        resource_id = self.tracker.get_content_resource_id(path.as_uri())

        with self.tracker.await_delete(
            fixtures.DOCUMENTS_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-single-directories', GLib.Variant.new_strv([]))
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceMissing(path.as_uri())


    def test_preconfigured_non_removable_in_index_single_directories(self):
        dconf = self.sandbox.get_dconf_client()

        with self.await_insert_dir(self.device_path):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-single-directories', GLib.Variant.new_strv([str(self.device_path)]))

            self.device_path.mkdir()
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()
            self.add_removable_device(self.device_path, MountFlags.NON_REMOVABLE)

        self.assertResourceExists(self.device_path.as_uri())
        resource_id = self.tracker.get_resource_id_by_uri(self.device_path.as_uri())

        with self.tracker.await_delete(
            fixtures.FILESYSTEM_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')

            # The new miner instance will be already unaware of the "mount"
            self.device_path.rmdir()
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceMissing(self.device_path.as_uri())


    def test_non_removable_in_index_recursive_directories(self):
        path = self.device_path.joinpath("dir/file1.txt")
        path.parent.mkdir(parents=True)
        path.write_text("Foo bar baz")
        self.add_removable_device(self.device_path, MountFlags.NON_REMOVABLE)

        dconf = self.sandbox.get_dconf_client()

        with self.await_document_inserted(path):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-recursive-directories', GLib.Variant.new_strv([self.indexed_dir, str(self.device_path)]))
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()

        self.assertResourceExists(path.as_uri())
        resource_id = self.tracker.get_content_resource_id(path.as_uri())

        with self.tracker.await_delete(
            fixtures.DOCUMENTS_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-recursive-directories', GLib.Variant.new_strv([self.indexed_dir]))
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()


    def test_preconfigured_non_removable_in_index_recursive_directories(self):
        dconf = self.sandbox.get_dconf_client()

        with self.await_insert_dir(self.device_path):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            dconf.write (
                'org.freedesktop.Tracker3.Miner.Files',
                'index-recursive-directories',
                GLib.Variant.new_strv([self.indexed_dir, str(self.device_path)]))

            self.device_path.mkdir()
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()
            self.add_removable_device(self.device_path, MountFlags.NON_REMOVABLE)

        self.assertResourceExists(self.device_path.as_uri())
        resource_id = self.tracker.get_content_resource_id(self.device_path.as_uri())

        with self.tracker.await_delete(
            fixtures.FILESYSTEM_GRAPH, resource_id, timeout=cfg.AWAIT_TIMEOUT
        ):
            self.sandbox.stop_daemon('org.freedesktop.LocalSearch3')
            self.device_path.rmdir()

            # The new miner instance will be already unaware of the "mount"
            self.miner_fs = MinerFsHelper(self.sandbox.get_session_bus_connection())
            self.miner_fs.start()


if __name__ == "__main__":
    fixtures.tracker_test_main()
