-
Notifications
You must be signed in to change notification settings - Fork 219
/
delete.py
307 lines (238 loc) · 8.54 KB
/
delete.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# Copyright 2005 Joe Wreschnig, Michael Urman
# 2013-2017 Nick Boultbee
# 2013,2014 Christoph Reiter
#
# 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.
"""
Functions for deleting files and songs with user interaction.
Only use trash_files() or trash_songs() and TrashMenuItem().
"""
import os
from gi.repository import Gtk
from senf import fsn2text
from quodlibet import _
from quodlibet import print_w
from quodlibet.util import trash
from quodlibet.qltk import get_top_parent
from quodlibet.qltk import Icons
from quodlibet.qltk.msg import ErrorMessage, WarningMessage
from quodlibet.qltk.wlw import WaitLoadWindow
from quodlibet.qltk.x import MenuItem, Align
from quodlibet.util.i18n import numeric_phrase
from quodlibet.util.path import unexpand
class FileListExpander(Gtk.Expander):
"""A widget for showing a static list of file paths"""
def __init__(self, paths):
super().__init__(label=_("Files:"))
self.set_resize_toplevel(True)
paths = [fsn2text(unexpand(p)) for p in paths]
lab = Gtk.Label(label="\n".join(paths))
lab.set_alignment(0.0, 0.0)
lab.set_selectable(True)
win = Gtk.ScrolledWindow()
win.add_with_viewport(Align(lab, border=6))
win.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
win.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
win.set_size_request(-1, 100)
self.add(win)
win.show_all()
class DeleteDialog(WarningMessage):
RESPONSE_DELETE = 1
"""Return value of DeleteDialog.run() in case the passed files
should be deleted"""
@classmethod
def for_songs(cls, parent, songs):
"""Create a delete dialog for deleting songs"""
description = _("The selected songs will be removed from the "
"library and their files deleted from disk.")
paths = [s("~filename") for s in songs]
return cls(parent, paths, description)
@classmethod
def for_files(cls, parent, paths):
"""Create a delete dialog for deleting files"""
description = _("The selected files will be deleted from disk.")
return cls(parent, paths, description)
def __init__(self, parent, paths, description):
title = numeric_phrase("Delete %(file_count)d file permanently?",
"Delete %(file_count)d files permanently?",
len(paths), "file_count")
super().__init__(
get_top_parent(parent),
title, description,
buttons=Gtk.ButtonsType.NONE)
area = self.get_message_area()
exp = FileListExpander(paths)
exp.show()
area.pack_start(exp, False, True, 0)
self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
self.add_icon_button(_("_Delete Files"), Icons.EDIT_DELETE,
self.RESPONSE_DELETE)
self.set_default_response(Gtk.ResponseType.CANCEL)
class TrashDialog(WarningMessage):
RESPONSE_TRASH = 1
"""Return value of TrashDialog.run() in case the passed files
should be moved to the trash"""
@classmethod
def for_songs(cls, parent, songs):
"""Create a trash dialog for trashing songs"""
description = _("The selected songs will be removed from the "
"library and their files moved to the trash.")
paths = [s("~filename") for s in songs]
return cls(parent, paths, description)
@classmethod
def for_files(cls, parent, paths):
"""Create a trash dialog for trashing files"""
description = _("The selected files will be moved to the trash.")
return cls(parent, paths, description)
def __init__(self, parent, paths, description):
title = numeric_phrase("Move %(file_count)d file to the trash?",
"Move %(file_count)d files to the trash?",
len(paths), "file_count")
super().__init__(
get_top_parent(parent),
title, description,
buttons=Gtk.ButtonsType.NONE)
area = self.get_message_area()
exp = FileListExpander(paths)
exp.show()
area.pack_start(exp, False, True, 0)
self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
self.add_icon_button(_("_Move to Trash"), Icons.USER_TRASH,
self.RESPONSE_TRASH)
self.set_default_response(Gtk.ResponseType.CANCEL)
def TrashMenuItem():
if trash.use_trash():
return MenuItem(_("_Move to Trash"), Icons.USER_TRASH)
else:
return MenuItem(_("_Delete"), Icons.EDIT_DELETE)
def _do_trash_songs(parent, songs, librarian):
dialog = TrashDialog.for_songs(parent, songs)
resp = dialog.run()
if resp != TrashDialog.RESPONSE_TRASH:
return
window_title = _("Moving %(current)d/%(total)d.")
w = WaitLoadWindow(parent, len(songs), window_title)
w.show()
ok = []
failed = []
for song in songs:
filename = song("~filename")
try:
trash.trash(filename)
except trash.TrashError as e:
print_w("Couldn't trash file (%s)" % e)
failed.append(song)
else:
ok.append(song)
w.step()
w.destroy()
if failed:
ErrorMessage(parent,
_("Unable to move to trash"),
_("Moving one or more files to the trash failed.")
).run()
if ok:
librarian.remove(ok)
def _do_trash_files(parent, paths):
dialog = TrashDialog.for_files(parent, paths)
resp = dialog.run()
if resp != TrashDialog.RESPONSE_TRASH:
return
window_title = _("Moving %(current)d/%(total)d.")
w = WaitLoadWindow(parent, len(paths), window_title)
w.show()
ok = []
failed = []
for path in paths:
try:
trash.trash(path)
except trash.TrashError:
failed.append(path)
else:
ok.append(path)
w.step()
w.destroy()
if failed:
ErrorMessage(parent,
_("Unable to move to trash"),
_("Moving one or more files to the trash failed.")
).run()
def _do_delete_songs(parent, songs, librarian):
dialog = DeleteDialog.for_songs(parent, songs)
resp = dialog.run()
if resp != DeleteDialog.RESPONSE_DELETE:
return
window_title = _("Deleting %(current)d/%(total)d.")
w = WaitLoadWindow(parent, len(songs), window_title)
w.show()
ok = []
failed = []
for song in songs:
filename = song("~filename")
try:
os.unlink(filename)
except OSError:
failed.append(song)
else:
ok.append(song)
w.step()
w.destroy()
if failed:
ErrorMessage(parent,
_("Unable to delete files"),
_("Deleting one or more files failed.")
).run()
if ok:
librarian.remove(ok)
def _do_delete_files(parent, paths):
dialog = DeleteDialog.for_files(parent, paths)
resp = dialog.run()
if resp != DeleteDialog.RESPONSE_DELETE:
return
window_title = _("Deleting %(current)d/%(total)d.")
w = WaitLoadWindow(parent, len(paths), window_title)
w.show()
ok = []
failed = []
for path in paths:
try:
os.unlink(path)
except OSError:
failed.append(path)
else:
ok.append(path)
w.step()
w.destroy()
if failed:
ErrorMessage(parent,
_("Unable to delete files"),
_("Deleting one or more files failed.")
).run()
def trash_files(parent, paths):
"""Will try to move the files to the trash,
or if not possible, delete them permanently.
Will ask for confirmation in each case.
"""
if not paths:
return
# depends on the platform if we can
if trash.use_trash():
_do_trash_files(parent, paths)
else:
_do_delete_files(parent, paths)
def trash_songs(parent, songs, librarian):
"""Will try to move the files associated with the songs to the trash,
or if not possible, delete them permanently.
Will ask for confirmation in each case.
The deleted songs will be removed from the librarian.
"""
if not songs:
return
# depends on the platform if we can
if trash.use_trash():
_do_trash_songs(parent, songs, librarian)
else:
_do_delete_songs(parent, songs, librarian)