1
2from gi.repository import Gio
3from gi.repository import GLib
4from gi.repository import Gtk
5
6from meld.conf import _
7from meld.misc import get_modal_parent, modal_dialog
8from meld.ui.filechooser import MeldFileChooserDialog
9
10
11def trash_or_confirm(gfile: Gio.File):
12    """Trash or delete the given Gio.File
13
14    Files and folders will be moved to the system Trash location
15    without confirmation. If they can't be trashed, then the user is
16    prompted for an irreversible deletion.
17
18    :rtype: bool
19    :returns: whether the file was deleted
20    """
21
22    try:
23        gfile.trash(None)
24        return True
25    except GLib.GError as e:
26        # Handle not-supported, as that's due to the trashing target
27        # being a (probably network) mount-point, not an underlying
28        # problem. We also have to handle the generic FAILED code
29        # because that's what we get with NFS mounts.
30        expected_error = (
31            e.code == Gio.IOErrorEnum.NOT_SUPPORTED or
32            e.code == Gio.IOErrorEnum.FAILED
33        )
34        if not expected_error:
35            raise RuntimeError(str(e))
36
37    file_type = gfile.query_file_type(
38        Gio.FileQueryInfoFlags.NONE, None)
39
40    if file_type == Gio.FileType.DIRECTORY:
41        raise RuntimeError(_("Deleting remote folders is not supported"))
42    elif file_type != Gio.FileType.REGULAR:
43        raise RuntimeError(_("Not a file or directory"))
44
45    delete_permanently = modal_dialog(
46        primary=_(
47            "“{}” can’t be put in the trash. Do you want to "
48            "delete it immediately?".format(
49                GLib.markup_escape_text(gfile.get_parse_name()))
50        ),
51        secondary=_(
52            "This remote location does not support sending items "
53            "to the trash."
54        ),
55        buttons=[
56            (_("_Cancel"), Gtk.ResponseType.CANCEL),
57            (_("_Delete Permanently"), Gtk.ResponseType.OK),
58        ],
59    )
60
61    if delete_permanently != Gtk.ResponseType.OK:
62        return False
63
64    try:
65        gfile.delete(None)
66        # TODO: Deleting remote folders involves reimplementing
67        # shutil.rmtree for gio, and then calling
68        # self.recursively_update().
69    except Exception as e:
70        raise RuntimeError(str(e))
71
72
73def prompt_save_filename(title: str, parent: Gtk.Widget = None) -> Gio.File:
74    dialog = MeldFileChooserDialog(
75        title,
76        transient_for=get_modal_parent(parent),
77        action=Gtk.FileChooserAction.SAVE,
78    )
79    dialog.set_default_response(Gtk.ResponseType.ACCEPT)
80    response = dialog.run()
81    gfile = dialog.get_file()
82    dialog.destroy()
83
84    if response != Gtk.ResponseType.ACCEPT or not gfile:
85        return
86
87    try:
88        file_info = gfile.query_info(
89            'standard::name,standard::display-name', 0, None)
90    except GLib.Error as err:
91        if err.code == Gio.IOErrorEnum.NOT_FOUND:
92            return gfile
93        raise
94
95    # The selected file exists, so we need to prompt for overwrite.
96    parent_name = gfile.get_parent().get_parse_name()
97    file_name = file_info.get_display_name()
98
99    replace = modal_dialog(
100        primary=_("Replace file “%s”?") % file_name,
101        secondary=_(
102            "A file with this name already exists in “%s”.\n"
103            "If you replace the existing file, its contents "
104            "will be lost.") % parent_name,
105        buttons=[
106            (_("_Cancel"), Gtk.ResponseType.CANCEL),
107            (_("_Replace"), Gtk.ResponseType.OK),
108        ],
109        messagetype=Gtk.MessageType.WARNING,
110    )
111    if replace != Gtk.ResponseType.OK:
112        return
113
114    return gfile
115