#!/usr/bin/python3
#
# SoundConverter - GNOME application for converting between audio formats.
# Copyright 2004 Lars Wirzenius
# Copyright 2005-2025 Gautier Portet
# Copyright 2020-2025 Sezanzeb
#
# 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; version 3 of the License.
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

"""Test filename handling."""

import os
import unittest
import urllib.error
import urllib.parse

from gi.repository import Gio
from util import reset_settings

from soundconverter.interface.batch import prepare_files_list
from soundconverter.util.fileoperations import (
    beautify_uri,
    filename_to_uri,
    unquote_filename,
)
from soundconverter.util.namegenerator import (
    TargetNameGenerator,
    custom_patterns,
    get_basename_pattern,
    get_subfolder_pattern,
    process_custom_pattern,
)
from soundconverter.util.settings import get_gio_settings, settings
from soundconverter.util.soundfile import SoundFile


def quote(ss):
    if isinstance(ss, str):
        ss = ss.encode("utf-8")
    return urllib.parse.quote(ss)


class FilenameToUriTest(unittest.TestCase):
    def test(self):
        for path in ("foo", "/foo", "foo/bar", "/foo/bar"):
            uri = filename_to_uri(path)
            self.assertTrue(uri.startswith("file://"))
            self.assertTrue(Gio.file_parse_name(path).get_uri() in uri)

        for already_uri in (
            "http://example.com/foo",
            "file:///tmp/foo",
        ):
            uri = filename_to_uri(already_uri)
            self.assertEqual(uri, already_uri)


class PrepareFilesList(unittest.TestCase):
    def tearDown(self):
        reset_settings()

    def test_non_recursive_directory(self):
        test = ["tests/test data/empty/"]
        # it should not find anything, as test is a directory
        expectation = ([], [])
        self.assertEqual(prepare_files_list(test), expectation)

    def test_recursive_directory(self):
        settings["recursive"] = True
        test = ["tests/test data/empty/", "tests/test data/empty/b"]
        expectation = (
            [
                filename_to_uri(test[0] + "a"),
                filename_to_uri(test[0] + "b/c"),
                filename_to_uri(test[1] + "/c"),
            ],
            ["empty/", "empty/b/", "b/"],
        )
        result = prepare_files_list(test)
        for path in result[0]:
            self.assertTrue(path.startswith("file://"))
        self.assertEqual(result, expectation)

    def test_file(self):
        test = ["tests/test data/empty/a"]
        # it should not detect the name of the parent directory as
        # it's only a single file
        expectation = ([filename_to_uri(test[0])], [""])
        result = prepare_files_list(test)
        self.assertTrue(result[0][0].startswith("file://"))
        self.assertEqual(result, expectation)


class Patterns(unittest.TestCase):
    def test_subfolder_pattern(self):
        gio_settings = get_gio_settings()
        gio_settings.set_int("subfolder-pattern-index", 1)
        pattern = get_subfolder_pattern()
        self.assertEqual(pattern, "{album-artist}-{album}")

    def test_basename_pattern(self):
        gio_settings = get_gio_settings()

        gio_settings.set_int("name-pattern-index", 2)
        pattern = get_basename_pattern()
        self.assertEqual(pattern, "{track-number:02}-{title}")

        generator = TargetNameGenerator()
        sound_file = SoundFile("file:///foo.bar")
        sound_file.tags.update({"track-number": 3, "title": "foo"})
        filled = generator.fill_pattern(sound_file, pattern)
        self.assertEqual(filled, "03-foo")

        gio_settings.set_int("name-pattern-index", 5)
        gio_settings.set_string("custom-filename-pattern", "test")
        pattern = get_basename_pattern()
        self.assertEqual(pattern, "test")

    def test_process_custom_pattern(self):
        pattern_in = "{Artist}/{Album} foo/{Track}"
        pattern_out = process_custom_pattern(pattern_in)
        self.assertEqual(pattern_out, "{artist}/{album} foo/{track-number:02}")

    def test_custom_patterns_mapping(self):
        self.assertEqual(custom_patterns["{Artist}"], "{artist}")


class TargetNameGeneratorTestCases(unittest.TestCase):
    def setUp(self):
        gio_settings = get_gio_settings()
        gio_settings.set_int("name-pattern-index", 0)
        gio_settings.set_int("subfolder-pattern-index", 0)
        gio_settings.set_boolean("same-folder-as-input", False)
        gio_settings.set_string("selected-folder", "file:///test/")
        gio_settings.set_boolean("create-subfolders", True)
        gio_settings.set_boolean("replace-messy-chars", True)
        self.g = TargetNameGenerator()

        self.s = SoundFile("file:///path/to/file.flac")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )

    def tearDown(self):
        self.g = None
        self.s = None

    def test_unquote_filename(self):
        self.assertEqual(unquote_filename("file://baz%20qux"), "file://baz qux")

    def test_beautify_uri(self):
        self.assertEqual(beautify_uri("file://hostname/"), "/")
        self.assertEqual(beautify_uri("file://hostname/foo"), "/foo")
        self.assertEqual(beautify_uri("file:///baz%20qux"), "/baz qux")
        self.assertEqual(beautify_uri("foo/bar"), "foo/bar")

    def test_safe_uri(self):
        # the "test data" folder has the space in it on purpose for this spec
        cwd_uri = "file://" + os.getcwd()

        # 1. path doesn't exist at all
        self.assertEqual(
            self.g.safe_uri("file:///", "/b äz/quズx/foo.mp3"),
            "file:///b_az/qux/foo.mp3",
        )
        self.assertEqual(
            self.g.safe_uri("file:///test/", "/baズz/qux"), "file:///test/baz/qux"
        )
        self.assertEqual(self.g.safe_uri("file:///", "/ズfoo.mp3"), "file:///foo.mp3")
        self.assertEqual(
            self.g.safe_uri("ftp://hostname/", "ズblä"), "ftp://hostname/bla"
        )
        self.assertEqual(
            self.g.safe_uri("ftp://hostname/foo", "/ズ blä"), "ftp://hostname/foo/_bla"
        )

        # 2. the outer dir exists
        self.assertEqual(
            self.g.safe_uri(cwd_uri, "/home/qфux/foo.mp3"),
            cwd_uri + "/home/qux/foo.mp3",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri, "tests/asdf/fooф.mp3"),
            cwd_uri + "/tests/asdf/foo.mp3",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri, "/tests/asdf/fooф.mp3"),
            cwd_uri + "/tests/asdf/foo.mp3",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/", "tests/asdf/fooф.mp3"),
            cwd_uri + "/tests/asdf/foo.mp3",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests", "/asdf/fooф.mp3"),
            cwd_uri + "/tests/asdf/foo.mp3",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests/", "asdf/fooф.mp3"),
            cwd_uri + "/tests/asdf/foo.mp3",
        )

        # 3. all dirs exist (space of 'test data' will be kept)
        original_name = os.getcwd() + "/tests/test data/audio/fâoo.mp3"
        self.assertEqual(
            self.g.safe_uri("file:///", original_name),
            cwd_uri + "/tests/test%20data/audio/faoo.mp3",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri, "tests/test%20data/fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri, "/tests/test%20data/fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/", "tests/test%20data/fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests", "/test%20data/fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests/", "test%20data/fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests/test%20data", "/fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests/test data", "fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests/test%20data/", "/fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests/test data/", "fфズ oo.mp3â"),
            cwd_uri + "/tests/test%20data/f_oo.mp3a",
        )

        # 4. the complete path exists
        original_name = os.getcwd() + "/tests/test%20data/audio/a.wav"
        self.assertEqual(
            self.g.safe_uri("file:///", original_name),
            cwd_uri + "/tests/test%20data/audio/a.wav",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri, "/tests/test data/"),
            cwd_uri + "/tests/test%20data/",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri, "tests/test data/"),
            cwd_uri + "/tests/test%20data/",
        )

        # 5. paths with special chars can be transformed into existing paths.
        # Doesn't increment the filename. on_task_finished of gstreamer.py
        # does that later. To reuse paths that were generated from {artist}
        # tags with special characters
        original_name = os.getcwd() + "/tests/test%20data/âuズdio/â.wav"
        self.assertEqual(
            self.g.safe_uri("file:///", original_name),
            cwd_uri + "/tests/test%20data/audio/a.wav",
        )

        # 6. doesn't change %20 spaces in URIs into _20, but rather into _
        # and keeps the URI scheme.
        # test%20data exists (test data), so it keeps the %20, because the
        # output should be an uri as well, just like the input.
        original_name = os.getcwd() + "/tests/test%20data/fo%20o.mp3"
        self.assertEqual(
            self.g.safe_uri("file:///", original_name),
            cwd_uri + "/tests/test%20data/fo_o.mp3",
        )

        # 7. any path added as parent is not modified
        self.assertEqual(
            self.g.safe_uri("file:///ab cd", "fo o.mp3"), "file:///ab%20cd/fo_o.mp3"
        )
        self.assertEqual(
            self.g.safe_uri("file:///fфズ", "fo o.mp3"),
            "file:///f%D1%84%E3%82%BA/fo_o.mp3",
        )
        self.assertEqual(
            self.g.safe_uri(cwd_uri + "/tests/test data", "audio/a.wav"),
            cwd_uri + "/tests/test%20data/audio/a.wav",
        )

        # 8. some weird URI schema means that 'test data' is probably not our
        # existing folder in the filesystem. The URI scheme should be file://
        # for that. Hence replace the space.
        original_name = "tests/test%20data/fo%20o.mp3"
        self.assertEqual(
            self.g.safe_uri("foo://" + os.getcwd(), original_name),
            "foo://" + os.getcwd() + "/tests/test_data/fo_o.mp3",
        )
        # don't break uri authorities
        original_name = "tests/test%20data/fo%20o.mp3"
        self.assertEqual(
            self.g.safe_uri("foo://foo@bar:1234/", original_name),
            "foo://foo@bar:1234/tests/test_data/fo_o.mp3",
        )

    def test_suffix(self):
        get_gio_settings().set_string("output-mime-type", "audio/x-vorbis")
        # figures out the suffix when created
        self.g = TargetNameGenerator()
        self.g.same_folder_as_input = True
        self.g.create_subfolders = False
        self.assertEqual(self.g.generate_target_uri(self.s, True), "/path/to/file.ogg")

    def test_oga_suffix(self):
        get_gio_settings().set_string("output-mime-type", "audio/x-vorbis")
        # figures out the suffix when created
        self.g = TargetNameGenerator()
        self.g.suffix = "oga"
        self.g.same_folder_as_input = True
        self.g.create_subfolders = False
        self.assertEqual(self.g.generate_target_uri(self.s, True), "/path/to/file.oga")

    def test_no_extension(self):
        get_gio_settings().set_string("output-mime-type", "audio/x-m4a")
        # figures out the suffix when created
        self.g = TargetNameGenerator()
        self.s = SoundFile("file:///path/to/file")
        self.g.same_folder_as_input = True
        self.g.create_subfolders = False
        self.assertEqual(self.g.generate_target_uri(self.s, True), "/path/to/file.m4a")

    def test_basename(self):
        self.g.suffix = "ogg"
        self.g.basename_pattern = "{track-number:02}-{title}"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(
            self.g.generate_target_uri(self.s, True), "/path/to/01-Hi_Ho.ogg"
        )

    def test_location(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///music"
        self.g.subfolder_pattern = "{artist}/{album}"
        self.g.basename_pattern = "{track-number:02}-{title}"
        self.assertEqual(
            self.g.generate_target_uri(self.s, True),
            "/music/Foo_Bar/IS__TOO/01-Hi_Ho.ogg",
        )

    def test_location_escape(self):
        self.s = SoundFile("file:///path/to/file with spaces")
        self.g.replace_messy_chars = False
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///mu sic"
        self.g.create_subfolders = False
        self.assertEqual(
            self.g.generate_target_uri(self.s),
            "file:///mu%20sic/file%20with%20spaces.ogg",
        )

    def test_basename_with_spaces(self):
        uri = "file:///spa%20ce/sub%20folder/foo.mp3"
        self.s = SoundFile(uri, "file:///spa%20ce/")
        self.g.replace_messy_chars = False
        self.g.suffix = "ogg"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(
            self.g.generate_target_uri(self.s), "file:///spa%20ce/sub%20folder/foo.ogg"
        )

    def test_basename_with_spaces_messy(self):
        uri = "file:///spa%20ce/sub%20folder/foo.mp3"
        self.s = SoundFile(uri, "file:///spa%20ce/")
        self.g.replace_messy_chars = True
        self.g.suffix = "ogg"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(
            self.g.generate_target_uri(self.s), "file:///spa%20ce/sub_folder/foo.ogg"
        )

    def test_basename_with_spaces_existing(self):
        base_path = "file:///" + os.getcwd() + "/tests/test%20data"
        uri = base_path + "/audio/a.wav"
        self.s = SoundFile(uri, base_path)
        self.g.replace_messy_chars = False
        self.g.suffix = "ogg"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(self.g.generate_target_uri(self.s), base_path + "/audio/a.ogg")

    def test_basename_with_spaces_existing_messy(self):
        base_path = "file:///" + os.getcwd() + "/tests/test%20data"
        uri = base_path + "/audio/a.wav"
        self.s = SoundFile(uri, base_path)
        self.g.replace_messy_chars = True
        self.g.suffix = "ogg"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(self.g.generate_target_uri(self.s), base_path + "/audio/a.ogg")

    def test_uri(self):
        self.g.suffix = "ogg"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.g.replace_messy_chars = False

        self.s = SoundFile("ssh://user@server:port/path/to/%20file.flac")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.assertEqual(
            self.g.generate_target_uri(self.s),
            "ssh://user@server:port/path/to/%20file.ogg",
        )

    def testURILocalDestination(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///music"
        self.g.create_subfolders = False

        self.s = SoundFile("ssh://user@server:port/p%25ath/to/file.flac")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.assertEqual(self.g.generate_target_uri(self.s), "file:///music/file.ogg")

    def test_uri_distant_destination(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "ftp://user2@dest-server:another-port:/music/"
        self.g.create_subfolders = False

        self.s = SoundFile("ssh://user@server:port/path/to/file.flac")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.assertEqual(
            self.g.generate_target_uri(self.s),
            "ftp://user2@dest-server:another-port:/music/file.ogg",
        )

    def testUriLikePaths(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///music%20"
        self.g.same_folder_as_input = False
        self.s = SoundFile("file:///path/%2525")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.subfolder_pattern = "{artist}/{album}"
        self.g.create_subfolders = True
        self.g.replace_messy_chars = False
        self.assertEqual(
            self.g.generate_target_uri(self.s, True), "/music /Foo Bar/IS: TOO/%25.ogg"
        )

    def test_uri_like_paths_messy(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///music%20"
        self.g.same_folder_as_input = False
        self.s = SoundFile("file:///path/%2525")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.subfolder_pattern = "{artist}/{album}"
        self.g.create_subfolders = True
        self.g.replace_messy_chars = True
        self.assertEqual(
            self.g.generate_target_uri(self.s, True), "/music /Foo_Bar/IS__TOO/_25.ogg"
        )

    def test_uri_unicode(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "ftp://user2@dest-server:another-port:" + quote(
            "/mûsîc/"
        )
        self.g.replace_messy_chars = False
        self.g.create_subfolders = False

        self.s = SoundFile(
            "ssh://user@server:port" + quote("/path/to/file with \u041d chars.flac")
        )
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.assertEqual(
            self.g.generate_target_uri(self.s),
            "ftp://user2@dest-server:another-port:/m%C3%BBs%C3%AEc/file"
            "%20with%20%D0%9D%20chars.ogg",
        )

    def testURIUnicode_utf8(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "ftp://user2@dest-server:another-port:" + quote(
            "/mûsîc/"
        )
        self.g.replace_messy_chars = False

        self.s = SoundFile(
            "ssh://user@server:port" + quote("/path/to/file with strângë chàrs фズ.flac")
        )
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.create_subfolders = False
        self.g.same_folder_as_input = False
        self.assertEqual(
            self.g.generate_target_uri(self.s),
            "ftp://user2@dest-server:another-port:"
            + quote("/mûsîc/file with strângë chàrs фズ.ogg"),
        )

    def test_uri_unicode_messy(self):
        self.g.suffix = "ogg"
        self.g.selected_folder = "ftp://user2@dest-server:another-port:" + quote(
            "/mûsîc/"
        )

        self.s = SoundFile(
            "ssh://user@server:port" + quote("/path/to/file with strângë chàrs.flac")
        )
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.replace_messy_chars = True
        self.g.create_subfolders = False
        self.g.same_folder_as_input = False
        self.assertEqual(
            self.g.generate_target_uri(self.s),
            "ftp://user2@dest-server:another-port:/"
            + quote("mûsîc")
            + "/file_with_strange_chars.ogg",
        )

    def testDisplay(self):
        self.g.suffix = "ogg"

        self.s = SoundFile("ssh://user@server:port/path/to/file.flac")
        self.assertEqual(self.s.filename_for_display, "file.flac")
        self.s = SoundFile("ssh://user@server:port/path/to/fîlé.flac")
        self.assertEqual(self.s.filename_for_display, "fîlé.flac")
        self.s = SoundFile("ssh://user@server:port/path/to/fileфズ.flac")
        self.assertEqual(self.s.filename_for_display, "fileфズ.flac")

    def test8bits(self):
        self.s = SoundFile("file://" + quote("/path/to/file\xa0\xb0\xc0\xd0.flac"))
        self.g.suffix = "ogg"
        self.g.replace_messy_chars = False
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(
            self.g.generate_target_uri(self.s, False),
            "file://" + quote("/path/to/file\xa0\xb0\xc0\xd0.ogg"),
        )

    def test8bits_messy(self):
        self.s = SoundFile("file://" + quote("/path/to/file\xa0\xb0\xc0\xd0.flac"))
        self.g.suffix = "ogg"
        self.g.replace_messy_chars = True
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(
            self.g.generate_target_uri(self.s, False), "file:///path/to/file_A.ogg"
        )

    def test8bits_tags(self):
        self.g.replace_messy_chars = False
        self.s = SoundFile("file:///path/to/fileyop.flac")
        self.s.tags.update(
            {
                "artist": "\xa0\xb0\xc0\xd0",
                "title": "\xa1\xb1\xc1\xd1",
                "album": "\xa2\xb2\xc2\xd2",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///music"
        self.g.subfolder_pattern = "{artist}/{album}"
        self.g.basename_pattern = "{title}"
        self.assertEqual(
            self.g.generate_target_uri(self.s, False),
            "file://"
            + quote("/music/\xa0\xb0\xc0\xd0/\xa2\xb2\xc2\xd2/\xa1\xb1\xc1\xd1.ogg"),
        )

    def test_root(self):
        self.s = SoundFile("file:///path/to/file.flac", "file:///path/")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.suffix = "ogg"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True
        self.assertEqual(self.g.generate_target_uri(self.s, True), "/path/to/file.ogg")

    def test_root_path(self):
        self.s = SoundFile("file:///path/#to/file.flac", "file:///path/")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///music"

        self.g.replace_messy_chars = False
        self.g.create_subfolders = False
        self.g.same_folder_as_input = False

        self.assertEqual(
            self.g.generate_target_uri(self.s, False), "file:///music/%23to/file.ogg"
        )

    def test_root_custom_pattern(self):
        self.s = SoundFile("file:///path/#to/file.flac", "file:///path/")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.suffix = "ogg"
        self.g.basename_pattern = "{genre}/{title}"

        self.g.replace_messy_chars = True
        self.g.create_subfolders = False
        self.g.same_folder_as_input = True

        self.assertEqual(
            self.g.generate_target_uri(self.s, True),
            # basefolder handling is disabled when the pattern has a /
            "/path/Unknown_Genre/Hi_Ho.ogg",
        )

    def test_default_pattern_values(self):
        gio_settings = get_gio_settings()
        gio_settings.set_int("name-pattern-index", -1)
        # without timestamp because I'd like to avoid having a test that
        # fails randomly
        gio_settings.set_string(
            "custom-filename-pattern",
            (
                "{Artist}/{Album}/{Album-Artist}/{Title}/{Track}/{Total}/{Genre}"
                "/{Date}/{Year}/{DiscNumber}/{DiscTotal}/{Ext}/{inputname}/"
                "{filename}"
            ),
        )
        self.g = TargetNameGenerator()

        self.s = SoundFile("file:///file.flac", "file:///")
        self.g.suffix = "ogg"
        self.g.replace_messy_chars = False
        self.g.create_subfolders = False
        self.g.selected_folder = "file:///foo"
        self.assertEqual(
            self.g.generate_target_uri(self.s, True),
            "/foo/Unknown Artist/Unknown Album/Unknown Artist/file/00/00/"
            "Unknown Genre/Unknown Date/Unknown Year/0/0/ogg/file/file.ogg",
        )

    def test_find_format_string_tags(self):
        pattern = "{i}{a}b{c}{{d}}{e}c{{{g}}}{hhh}"
        variables = self.g.find_format_string_tags(pattern)
        mapping = {variable: 1 for variable in variables}
        formatted = pattern.format(**mapping)
        # if it didn't crash it's already working actually
        self.assertEqual(formatted, "11b1{d}1c{1}1")

    def test_pattern_unknown_tag(self):
        gio_settings = get_gio_settings()
        gio_settings.set_int("name-pattern-index", -1)
        # without timestamp because I'd like to avoid having a test that
        # fails randomly
        gio_settings.set_string("custom-filename-pattern", ("{venue}/{weather}"))
        self.g = TargetNameGenerator()

        self.s = SoundFile("file:///file.flac", "file:///")
        self.g.suffix = "ogg"
        self.g.replace_messy_chars = False
        self.g.create_subfolders = False
        self.g.selected_folder = "file:///foo"
        self.assertEqual(
            self.g.generate_target_uri(self.s, True),
            "/foo/Unknown Venue/Unknown Weather.ogg",
        )

    def test_leading_slash_pattern(self):
        self.s = SoundFile("file:///path/#to/file.flac", "file:///path/")
        self.s.tags.update({"title": "Hi Ho"})
        self.g.suffix = "ogg"
        # basename_patterns can be user defined in a text input. if it
        # contains slashes, the subfolder_pattern will be overwritten,
        # so that custom subfolder patterns can be used by typing into
        # the basename pattern.
        self.g.basename_pattern = "/home/foo/{title}"
        self.g.create_subfolders = True
        self.g.subfolder_pattern = "/bar"
        self.g.selected_folder = "/asdf"
        self.assertEqual(
            self.g.generate_target_uri(self.s, True), "/asdf/home/foo/Hi_Ho.ogg"
        )

    def test_leading_slash_subfolder_pattern(self):
        self.s = SoundFile("file:///path/#to/file.flac", "file:///path/")
        self.s.tags.update({"title": "Hi Ho"})
        self.g.suffix = "ogg"
        self.g.basename_pattern = "{title}"
        self.g.create_subfolders = True
        self.g.subfolder_pattern = "/bar"
        self.g.selected_folder = "/asdf"
        self.assertEqual(
            self.g.generate_target_uri(self.s, True), "/asdf/bar/Hi_Ho.ogg"
        )

    def test_root_path_custom_pattern(self):
        self.s = SoundFile("file:///path/to/file.flac", "file:///path/")
        self.s.tags.update(
            {
                "artist": "Foo Bar",
                "title": "Hi Ho",
                "album": "IS: TOO",
                "track-number": 1,
                "track-count": 11,
            }
        )
        self.g.suffix = "ogg"
        self.g.selected_folder = "file:///music"
        self.g.basename_pattern = "{title}"
        self.g.create_subfolders = False
        self.g.same_folder_as_input = False
        self.assertEqual(
            self.g.generate_target_uri(self.s, True), "/music/to/Hi_Ho.ogg"
        )

    def test_quote(self):
        self.s = SoundFile("file://" + quote("/path%'#/to/file%'#.flac"))
        self.s.tags.update(
            {
                "artist": "Foo%'#Bar",
                "title": "Hi%'#Ho",
            }
        )
        self.g.replace_messy_chars = False
        self.g.same_folder_as_input = True
        self.g.create_subfolders = False
        self.g.suffix = "ogg"
        self.assertEqual(
            self.g.generate_target_uri(self.s, False),
            "file://" + quote("/path%'#/to/file%'#.ogg"),
        )
        self.g.create_subfolders = True
        self.g.subfolder_pattern = "{artist}"
        self.g.basename_pattern = "{title}"
        self.assertEqual(
            self.g.generate_target_uri(self.s, False),
            "file://" + quote("/path%'#/to/Foo%'#Bar/Hi%'#Ho.ogg"),
        )

    # temporary filename generation

    def test_temporary1(self):
        self.s = SoundFile("file:///foo/bar.mp3")
        self.g.same_folder_as_input = False
        self.g.replace_messy_chars = True
        self.g.selected_folder = "file:///music"
        temp_path = self.g.generate_temp_path(self.s)
        expected = "file:///music/bar.mp3_"
        self.assertTrue(
            temp_path.startswith(expected),
            f"expected {temp_path} to start with {expected}",
        )
        self.assertTrue(temp_path.endswith("_SC_"))

    def test_temporary2(self):
        self.s = SoundFile("file:///foo/bar.mp3", "file:///")
        # base_path 'file:///' will be ignored
        self.g.same_folder_as_input = False
        self.g.replace_messy_chars = False
        self.g.selected_folder = "file:///music"
        temp_path = self.g.generate_temp_path(self.s)
        expected = "file:///music/bar.mp3~"
        self.assertTrue(
            temp_path.startswith(expected),
            f"expected {temp_path} to start with {expected}",
        )
        self.assertTrue(temp_path.endswith("~SC~"))

    def test_temporary3(self):
        self.s = SoundFile("file:///foo/bar.mp3")
        self.g.same_folder_as_input = True
        self.g.replace_messy_chars = True
        self.g.selected_folder = "file:///etfdzhdrudf"
        temp_path = self.g.generate_temp_path(self.s)
        expected = "file:///foo/bar.mp3_"
        self.assertTrue(
            temp_path.startswith(expected),
            f"expected {temp_path} to start with {expected}",
        )
        self.assertTrue(temp_path.endswith("_SC_"))

    def test_temporary4(self):
        self.s = SoundFile("file:///foo/bar.mp3")
        self.g.same_folder_as_input = True
        self.g.replace_messy_chars = False
        self.g.selected_folder = "file:///etfdzhdrudf"
        temp_path = self.g.generate_temp_path(self.s)
        expected = "file:///foo/bar.mp3~"
        self.assertTrue(
            temp_path.startswith(expected),
            f"expected {temp_path} to start with {expected}",
        )
        self.assertTrue(temp_path.endswith("~SC~"))

    def test_temporary5(self):
        self.s = SoundFile("file:///foo/test/bar.mp3", "file:///foo/")
        self.g.same_folder_as_input = True
        self.g.replace_messy_chars = False
        self.g.selected_folder = "file:///etfdzhdrudf"
        temp_path = self.g.generate_temp_path(self.s)
        expected = "file:///foo/bar.mp3~"
        self.assertTrue(
            temp_path.startswith(expected),
            f"expected {temp_path} to start with {expected}",
        )
        self.assertTrue(temp_path.endswith("~SC~"))


if __name__ == "__main__":
    unittest.main()
