Skip to content

Commit 9a7e488

Browse files
authored
*: support create/delete playlist (#731)
1 parent 1bfb7a7 commit 9a7e488

File tree

4 files changed

+142
-25
lines changed

4 files changed

+142
-25
lines changed

feeluown/gui/provider_ui.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@
99
from feeluown.app.gui_app import GuiApp
1010

1111

12-
@runtime_checkable
13-
class UISupportsCreatePlaylist(Protocol):
14-
15-
@abstractmethod
16-
def popup_create_playlist_dialog(self):
17-
...
18-
19-
2012
@runtime_checkable
2113
class UISupportsLoginOrGoHome(Protocol):
2214

feeluown/gui/uimain/sidebar.py

Lines changed: 88 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,19 @@
55
from PyQt5.QtWidgets import QFrame, QLabel, QVBoxLayout, QSizePolicy, QScrollArea, \
66
QHBoxLayout, QFormLayout, QDialog, QLineEdit, QDialogButtonBox, QMessageBox
77

8+
from feeluown.excs import ProviderIOError, NoUserLoggedIn
9+
from feeluown.library import (
10+
SupportsPlaylistDelete,
11+
SupportsPlaylistCreateByName,
12+
SupportsCurrentUser,
13+
)
814
from feeluown.collection import CollectionAlreadyExists, CollectionType
15+
from feeluown.utils import aio
916
from feeluown.gui.widgets import (
10-
DiscoveryButton, HomeButton, PlusButton, TriagleButton,
17+
DiscoveryButton,
18+
HomeButton,
19+
PlusButton,
20+
TriagleButton,
1121
)
1222
from feeluown.gui.widgets.playlists import PlaylistsView
1323
from feeluown.gui.components import CollectionListView
@@ -64,6 +74,7 @@ def toggle_view(self):
6474

6575

6676
class LeftPanel(QScrollArea):
77+
6778
def __init__(self, app: 'GuiApp', parent=None):
6879
super().__init__(parent)
6980
self._app = app
@@ -96,26 +107,23 @@ def __init__(self, app: 'GuiApp', parent=None):
96107
self.home_btn = HomeButton(height=30, parent=self)
97108
self.discovery_btn = DiscoveryButton(height=30, padding=0.2, parent=self)
98109
self.collections_header = QLabel('本地收藏集', self)
99-
self.collections_header.setToolTip(
100-
'我们可以在本地建立『收藏集』来收藏自己喜欢的音乐资源\n\n'
101-
'每个收藏集都以一个独立 .fuo 文件的存在,'
102-
'将鼠标悬浮在收藏集上,可以查看文件所在路径。\n'
103-
'新建 fuo 文件,则可以新建收藏集,文件名即是收藏集的名字。\n\n'
104-
'手动编辑 fuo 文件即可编辑收藏集中的音乐资源,也可以在界面上拖拽来增删歌曲。'
105-
)
110+
self.collections_header.setToolTip('我们可以在本地建立『收藏集』来收藏自己喜欢的音乐资源\n\n'
111+
'每个收藏集都以一个独立 .fuo 文件的存在,'
112+
'将鼠标悬浮在收藏集上,可以查看文件所在路径。\n'
113+
'新建 fuo 文件,则可以新建收藏集,文件名即是收藏集的名字。\n\n'
114+
'手动编辑 fuo 文件即可编辑收藏集中的音乐资源,也可以在界面上拖拽来增删歌曲。')
106115
self.playlists_header = QLabel('歌单列表', self)
107116
self.my_music_header = QLabel('我的音乐', self)
108117

109118
self.playlists_view = PlaylistsView(self)
110119
self.my_music_view = MyMusicView(self)
111120
self.collections_view = CollectionListView(self._app)
112121

113-
self.collections_con = ListViewContainer(
114-
self.collections_header, self.collections_view)
115-
self.playlists_con = ListViewContainer(
116-
self.playlists_header, self.playlists_view)
117-
self.my_music_con = ListViewContainer(
118-
self.my_music_header, self.my_music_view)
122+
self.collections_con = ListViewContainer(self.collections_header,
123+
self.collections_view)
124+
self.playlists_con = ListViewContainer(self.playlists_header,
125+
self.playlists_view)
126+
self.my_music_con = ListViewContainer(self.my_music_header, self.my_music_view)
119127

120128
self.playlists_view.setModel(self._app.pl_uimgr.model)
121129
self.my_music_view.setModel(self._app.mymusic_uimgr.model)
@@ -153,9 +161,11 @@ def __init__(self, app: 'GuiApp', parent=None):
153161
lambda pl: self._app.browser.goto(model=pl))
154162
self.collections_view.show_collection.connect(
155163
lambda coll: self._app.browser.goto(page=f'/colls/{coll.identifier}'))
156-
self.collections_view.remove_collection.connect(self.remove_coll)
164+
self.collections_view.remove_collection.connect(self._remove_coll)
165+
self.playlists_view.remove_playlist.connect(self._remove_playlist)
157166
self.collections_con.create_btn.clicked.connect(
158167
self.popup_collection_adding_dialog)
168+
self.playlists_con.create_btn.clicked.connect(self._create_playlist)
159169

160170
def popup_collection_adding_dialog(self):
161171
dialog = QDialog(self)
@@ -184,6 +194,50 @@ def create_collection_and_reload():
184194
dialog.accepted.connect(create_collection_and_reload)
185195
dialog.open()
186196

197+
def _create_playlist(self):
198+
provider_ui = self._app.current_pvd_ui_mgr.get()
199+
if provider_ui is None:
200+
self._app.show_msg('当前的资源提供方未注册其 UI')
201+
return
202+
provider = provider_ui.provider
203+
if not isinstance(provider, SupportsPlaylistCreateByName) \
204+
or not isinstance(provider, SupportsCurrentUser) \
205+
or not provider.has_current_user():
206+
self._app.show_msg('当前的资源提供方不支持创建歌单')
207+
return
208+
209+
dialog = QDialog(self)
210+
# Set WA_DeleteOnClose so that the dialog can be deleted (from self.children).
211+
dialog.setAttribute(Qt.WA_DeleteOnClose)
212+
layout = QFormLayout(dialog)
213+
title_edit = QLineEdit(dialog)
214+
layout.addRow('歌单名', title_edit)
215+
button_box = QDialogButtonBox(QDialogButtonBox.Cancel | QDialogButtonBox.Yes)
216+
layout.addRow('', button_box)
217+
button_box.accepted.connect(dialog.accept)
218+
button_box.rejected.connect(dialog.reject)
219+
220+
def create_playlist_and_reload():
221+
title = title_edit.text()
222+
223+
async def do():
224+
try:
225+
playlist = await aio.run_fn(provider.playlist_create_by_name, title)
226+
except (ProviderIOError, NoUserLoggedIn) as e:
227+
QMessageBox.warning(self._app, '错误', f"创建歌单 '{title}' 失败: {e}")
228+
else:
229+
# Add playlist to pl_uimgr is a workaround, which may cause bug.
230+
# For example, the order of the newly created playlist should be
231+
# in the top for some providers.
232+
# TODO: re-fetch user's playlists and fill the UI.
233+
self._app.pl_uimgr.add(playlist, is_fav=False)
234+
self._app.show_msg(f"创建歌单 '{title}' 成功")
235+
236+
aio.run_afn(do)
237+
238+
dialog.accepted.connect(create_playlist_and_reload)
239+
dialog.open()
240+
187241
def show_library(self):
188242
coll_library = self._app.coll_mgr.get_coll_library()
189243
self._app.browser.goto(page=f'/colls/{coll_library.identifier}')
@@ -192,7 +246,25 @@ def show_pool(self):
192246
coll = self._app.coll_mgr.get(CollectionType.sys_pool)
193247
self._app.browser.goto(page=f'/colls/{coll.identifier}')
194248

195-
def remove_coll(self, coll):
249+
def _remove_playlist(self, playlist):
250+
251+
async def do():
252+
provider = self._app.library.get_or_raise(playlist.source)
253+
if isinstance(provider, SupportsPlaylistDelete):
254+
ok = await aio.run_fn(provider.playlist_delete, playlist.identifier)
255+
self._app.show_msg(f"删除歌单 {playlist.name} {'成功' if ok else '失败'}")
256+
if ok is True:
257+
self._app.pl_uimgr.model.remove(playlist)
258+
else:
259+
self._app.show_msg(f'资源提供方({provider.identifier})不支持删除歌单')
260+
261+
box = QMessageBox(QMessageBox.Warning, '提示', f"确认删除歌单 '{playlist.name}' 吗?",
262+
QMessageBox.Yes | QMessageBox.No, self)
263+
box.accepted.connect(lambda: aio.run_afn(do))
264+
box.open()
265+
266+
def _remove_coll(self, coll):
267+
196268
def do():
197269
self._app.coll_mgr.remove(coll)
198270
self._app.coll_mgr.refresh()

feeluown/gui/widgets/playlists.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
QModelIndex,
77
)
88
from PyQt5.QtWidgets import (
9-
QAbstractItemView,
9+
QAbstractItemView, QMenu
1010
)
1111

1212
from .textlist import TextlistModel, TextlistView
@@ -42,6 +42,23 @@ def add(self, playlist, is_fav=False):
4242
playlists.extend(_playlists)
4343
self.endInsertRows()
4444

45+
def remove(self, playlist):
46+
for i, playlist_ in enumerate(self._playlists):
47+
if playlist_ == playlist:
48+
self.beginRemoveRows(QModelIndex(), i, i+1)
49+
self._playlists.remove(playlist)
50+
self.endRemoveRows()
51+
break
52+
53+
for i, playlist_ in enumerate(self._fav_playlists):
54+
if playlist_ == playlist:
55+
start = i+len(self._playlists)
56+
end = start + 1
57+
self.beginRemoveRows(QModelIndex(), start, end)
58+
self._fav_playlists.remove(playlist)
59+
self.endRemoveRows()
60+
break
61+
4562
def clear(self):
4663
total_length = len(self.items)
4764
self.beginRemoveRows(QModelIndex(), 0, total_length - 1)
@@ -77,6 +94,7 @@ class PlaylistsView(TextlistView):
7794
.. versiondeprecated:: 3.9
7895
"""
7996
show_playlist = pyqtSignal([object])
97+
remove_playlist = pyqtSignal([object])
8098

8199
def __init__(self, parent):
82100
super().__init__(parent)
@@ -88,6 +106,17 @@ def _on_clicked(self, index):
88106
playlist = index.data(role=Qt.UserRole)
89107
self.show_playlist.emit(playlist)
90108

109+
def contextMenuEvent(self, event):
110+
indexes = self.selectionModel().selectedIndexes()
111+
if len(indexes) != 1:
112+
return
113+
114+
playlist = self.model().data(indexes[0], Qt.UserRole)
115+
menu = QMenu()
116+
action = menu.addAction('删除此歌单')
117+
action.triggered.connect(lambda: self.remove_playlist.emit(playlist))
118+
menu.exec(event.globalPos())
119+
91120
def dropEvent(self, e):
92121
mimedata = e.mimeData()
93122
song = mimedata.model

feeluown/library/provider_protocol.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
'SupportsPlaylistAddSong',
2828
'SupportsPlaylistGet',
29+
'SupportsPlaylistCreateByName',
30+
'SupportsPlaylistDelete',
2931
'SupportsPlaylistRemoveSong',
3032
'SupportsPlaylistSongsReader',
3133

@@ -280,6 +282,28 @@ def playlist_get(self, identifier: ID) -> PlaylistModel:
280282
raise NotImplementedError
281283

282284

285+
@runtime_checkable
286+
class SupportsPlaylistCreateByName(Protocol):
287+
@abstractmethod
288+
def playlist_create_by_name(self, name) -> PlaylistModel:
289+
"""Create playlist for user logged in.
290+
291+
:raises NoUserLoggedIn:
292+
:raises ProviderIOError:
293+
"""
294+
295+
296+
@runtime_checkable
297+
class SupportsPlaylistDelete(Protocol):
298+
@abstractmethod
299+
def playlist_delete(self, identifier: ID) -> bool:
300+
"""
301+
:raises ModelNotFound: model not found by the identifier
302+
:raises ProviderIOError:
303+
"""
304+
raise NotImplementedError
305+
306+
283307
@eq(ModelType.playlist, PF.songs_rd)
284308
@runtime_checkable
285309
class SupportsPlaylistSongsReader(Protocol):

0 commit comments

Comments
 (0)