Пример #1
0
 def _goto(self, model=None, uri=None):
     """真正的跳转逻辑"""
     if model is None:
         try:
             model = resolve(uri)
         except ResolveFailed:
             model = None
     else:
         uri = reverse(model)
     if not uri.startswith('fuo://'):
         uri = 'fuo://' + uri
     with self._app.create_action('-> {}'.format(uri)) as action:
         if model is not None:
             self._render_model(model)
         else:
             try:
                 x = self.router.dispatch(uri, {'app': self._app})
                 if inspect.iscoroutine(x):
                     aio.create_task(x)
             except NotFound:
                 action.failed('not found.'.format(uri))
                 return
     self._last_uri = self.current_uri
     if model is not None:
         self.current_uri = reverse(model)
     else:
         self.current_uri = uri
Пример #2
0
    async def render(self):
        artist = self.artist

        self.songs_table.show()

        # bind signal first
        # we only show album that implements create_albums_g
        if artist.meta.allow_create_albums_g:
            self.meta_widget.toolbar.show_albums_needed.connect(
                lambda: self.show_albums(self.artist.create_albums_g()))
            self.albums_table.show_album_needed.connect(self.show_model)

        # fetch and render metadata
        desc = await async_run(lambda: artist.desc)
        self.meta_widget.title = artist.name
        self.meta_widget.desc = desc
        cover = await async_run(lambda: artist.cover)
        self.meta_widget.toolbar.artist_mode()

        # fetch and render songs
        songs = songs_g = None
        if artist.meta.allow_create_songs_g:
            songs_g = artist.create_songs_g()
        else:
            songs = await async_run(lambda: artist.songs)
        self.show_songs(songs_g=songs_g, songs=songs, show_count=True)
        self.meta_widget.toolbar.show_songs_needed.connect(
            lambda: self.show_songs(songs_g=songs_g, songs=songs, show_count=True))

        # render cover
        if cover:
            aio.create_task(self.show_cover(cover))
Пример #3
0
    def fetchMore(self, _=QModelIndex()):
        expect_len = 10
        try:
            items = list(itertools.islice(self.reader, expect_len))
        except ProviderIOError:
            logger.exception('fetch more items failed')
            return

        acture_len = len(items)
        colors = [
            random.choice(list(COLORS.values())) for _ in range(0, acture_len)
        ]
        if acture_len < expect_len:
            self._maybe_has_more = False
        begin = len(self.items)
        self.beginInsertRows(QModelIndex(), begin, begin + acture_len - 1)
        self.items.extend(items)
        self.colors.extend(colors)
        self.endInsertRows()

        for item in items:
            aio.create_task(
                self.fetch_image(item,
                                 self._fetch_image_callback(item),
                                 uid=reverse(item) + '/cover'))
Пример #4
0
    async def render(self):
        playlist = self.playlist

        # show playlist title
        self.meta_widget.show()
        self.meta_widget.title = playlist.name

        # show playlist song list
        songs = songs_g = None
        with suppress(ProviderIOError):
            if playlist.meta.allow_create_songs_g:
                songs_g = GeneratorProxy.wrap(playlist.create_songs_g())
            else:
                songs = await async_run(lambda: playlist.songs)
            self.show_songs(songs=songs, songs_g=songs_g, show_count=True)

        # show playlist cover
        if playlist.cover:
            aio.create_task(
                self.show_cover(playlist.cover, reverse(playlist, '/cover')))

        def remove_song(song):
            playlist.remove(song.identifier)

        self.songs_table.remove_song_func = remove_song
        self.tabbar.show_desc_needed.connect(
            lambda: aio.create_task(self._show_desc()))
Пример #5
0
        def _show_pure_videos_coll(coll):
            from feeluown.containers.table import VideosRenderer

            self.collection_container.hide()
            self.scrollarea.show()
            reader = RandomSequentialReader.from_list(coll.models)
            renderer = VideosRenderer(reader)
            aio.create_task(self.table_container.set_renderer(renderer))
Пример #6
0
    def autologin(self):
        """Try to load user cookies and login with it

        Generally, you can call this method after dialog is shown.
        """
        cookies = self.load_user_cookies()
        if cookies is not None:
            self.show_hint('正在尝试加载已有用户...', color='green')
            self.cookies_text_edit.setText(json.dumps(cookies, indent=2))
            aio.create_task(self.login_with_cookies(cookies))
Пример #7
0
def show_provider(req):
    if hasattr(req, 'ctx'):
        app = req.ctx['app']
    else:
        app = req  # 兼容老版本
    app.pl_uimgr.clear()
    # app.playlists.add(provider.playlists)

    app.ui.left_panel.my_music_con.hide()
    app.ui.left_panel.playlists_con.hide()

    aio.create_task(
        app.ui.table_container.set_renderer(
            LibraryRenderer(provider.songs, provider.albums,
                            provider.artists)))
Пример #8
0
    async def render(self):
        artist = self.artist

        # bind signal first
        # we only show album that implements create_albums_g
        if artist.meta.allow_create_albums_g:
            self.toolbar.filter_albums_needed.connect(
                lambda types: self.albums_table.model().filter_by_types(types))
            self.tabbar.show_albums_needed.connect(
                lambda: self.show_albums(self.artist.create_albums_g()))
            self.albums_table.show_album_needed.connect(self.show_model)
        if hasattr(artist, 'contributed_albums') and artist.contributed_albums:
            # show contributed_album list
            self.tabbar.show_contributed_albums_needed.connect(
                lambda: self.show_albums(self.artist.
                                         create_contributed_albums_g()))

        # fetch and render basic metadata
        self.meta_widget.title = artist.name
        self.meta_widget.show()
        self.tabbar.show()
        self.tabbar.artist_mode()

        # fetch and render songs
        songs = songs_g = None
        if artist.meta.allow_create_songs_g:
            songs_g = wrap(artist.create_songs_g())
            self.tabbar.show_songs_needed.connect(
                lambda: self.show_songs(songs_g=wrap(artist.create_songs_g()),
                                        songs=songs,
                                        show_count=True))
        else:
            songs = await async_run(lambda: artist.songs)
            self.tabbar.show_songs_needed.connect(lambda: self.show_songs(
                songs_g=None, songs=songs, show_count=True))
        self.show_songs(songs_g=songs_g, songs=songs, show_count=True)

        # finally, we render cover and description
        cover = await async_run(lambda: artist.cover)
        if cover:

            aio.create_task(
                self.show_cover(cover,
                                reverse(artist, '/cover'),
                                as_background=True))

        self.tabbar.show_desc_needed.connect(
            lambda: aio.create_task(self._show_desc()))
Пример #9
0
    def current_song(self, song):
        """如果歌曲 url 无效,则尝试从其它平台找一个替代品"""
        if song is None or song.url:
            _Playlist.current_song.fset(self, song)
            return
        self.mark_as_bad(song)

        logger.info('song:%s is invalid, try to get standby', song)
        if self._task is not None:
            logger.info('try to cancel another find-song-standby task')
            self._task.cancel()
            self._task = None

        self._task = aio.create_task(
            self._app.library.a_list_song_standby(song))

        def _current_song_setter(task):
            nonlocal song
            try:
                songs = task.result()
            except asyncio.CancelledError:
                logger.debug('badsong-autoreplace task is cancelled')
            else:
                if songs:
                    # DOUBT: how Python closures works?
                    song = songs[0]
                _Playlist.current_song.fset(self, song)
            finally:
                self._task = None

        self._task.add_done_callback(_current_song_setter)
Пример #10
0
    def contextMenuEvent(self, e):
        song = self._app.playlist.current_song
        if song is None:
            return

        menu = QMenu()
        menu.hovered.connect(self.on_action_hovered)
        artist_menu = menu.addMenu('查看歌手')
        album_action = menu.addAction('查看专辑')
        artist_menu.menuAction().setData({'artists': None, 'song': song})
        album_action.setData({'song': song})
        artist_menu.menuAction().triggered.connect(
            lambda: aio.create_task(self._goto_artists(song)))
        album_action.triggered.connect(
            lambda: aio.create_task(self._goto_album(song)))
        menu.exec(e.globalPos())
Пример #11
0
 async def show_album(self, album):
     meta_widget = self.collection_body.meta_widget
     meta_widget.clear()
     meta_widget.title = album.name_display
     meta_widget.creator = album.artists_name_display
     songs = await async_run(lambda: album.songs)
     meta_widget.songs_count = len(songs)
     reader = RandomSequentialReader.from_list(songs)
     model = SongListModel(reader)
     self.collection_body.song_list_view.show()
     self.collection_body.song_list_view.setModel(model)
     meta_widget.desc = await async_run(lambda: album.desc)
     meta_widget.title = await async_run(lambda: album.name)
     meta_widget.creator = await async_run(lambda: album.artists_name)
     cover = await async_run(lambda: album.cover)
     if cover:
         aio.create_task(self.show_cover(cover, reverse(album, '/cover')))
Пример #12
0
    async def render(self):
        artist = self.artist

        self.songs_table.show()

        # bind signal first
        # we only show album that implements create_albums_g
        if artist.meta.allow_create_albums_g:
            # show album detail
            self.albums_table.show_album_needed.connect(self.show_model)

            # show album list
            self.meta_widget.toolbar.show_albums_needed.connect(
                lambda: self.show_albums(self.artist.create_albums_g()))
            # album list fitlers
            self.meta_widget.toolbar.filter_albums_contributed_needed.connect(
                self.filter_albums_contributed)
            self.meta_widget.toolbar.filter_albums_mini_needed.connect(
                self.filter_albums_mini)
            self.meta_widget.toolbar.filter_albums_all_needed.connect(
                self.filter_albums_all)
            self.meta_widget.toolbar.filter_albums_live_needed.connect(
                self.filter_albums_live)

        # fetch and render metadata
        desc = await async_run(lambda: artist.desc)
        self.meta_widget.title = artist.name
        self.meta_widget.desc = desc
        cover = await async_run(lambda: artist.cover)

        # fetch and render songs
        songs = songs_g = None
        if artist.meta.allow_create_songs_g:
            songs_g = artist.create_songs_g()
        else:
            songs = await async_run(lambda: artist.songs)
        self.show_songs(songs_g=songs_g, songs=songs, show_count=True)
        self.meta_widget.toolbar.show_songs_needed.connect(
            lambda: self.show_songs(
                songs_g=songs_g, songs=songs, show_count=True))

        # render cover
        if cover:
            aio.create_task(self.show_cover(cover, reverse(artist, '/cover')))

        self.meta_widget.toolbar.artist_mode()
Пример #13
0
    def _on_song_changed(self, song):
        async def func(song):
            if song is None:
                self.cur_song_dl_btn.setEnabled(False)
                return

            title = await async_run(lambda: song.title)
            artists_name = await async_run(lambda: song.artists_name)

            filename = cook_filename(title, artists_name)
            is_downloaded = self._mgr.is_file_downloaded(filename)
            if is_downloaded:
                self.cur_song_dl_btn.setEnabled(False)
                self.cur_song_dl_btn.setChecked(True)
            else:
                self.cur_song_dl_btn.setEnabled(True)
                self.cur_song_dl_btn.setChecked(False)

        aio.create_task(func(song))
Пример #14
0
    async def render(self):
        album = self.album

        songs = await async_run(lambda: album.songs)
        self.show_songs(songs)

        self.meta_widget.title = album.name_display
        self.meta_widget.songs_count = len(songs)
        self.meta_widget.creator = album.artists_name_display
        self.meta_widget.show()

        # fetch cover and description
        cover = await async_run(lambda: album.cover)
        if cover:
            aio.create_task(self.show_cover(cover, reverse(album, '/cover')))

        self.tabbar.show()
        self.tabbar.album_mode()
        self.tabbar.show_desc_needed.connect(lambda: aio.create_task(self._show_desc()))
        self.tabbar.show_songs_needed.connect(lambda: self.show_songs(songs))
Пример #15
0
    def fetchMore(self, _=QModelIndex()):
        expect_len = 10
        albums = list(itertools.islice(self.albums_g, expect_len))
        acture_len = len(albums)
        colors = [
            random.choice(list(COLORS.values())) for _ in range(0, acture_len)
        ]
        if acture_len < expect_len:
            self._maybe_has_more = False
        begin = len(self.albums)
        self.beginInsertRows(QModelIndex(), begin, begin + acture_len - 1)
        self.albums.extend(albums)
        self.colors.extend(colors)
        self.endInsertRows()

        for album in albums:
            aio.create_task(
                self.fetch_image(album,
                                 self._fetch_image_callback(album),
                                 uid=reverse(album) + '/cover'))
Пример #16
0
    def _on_media_changed(self, media):
        async def func(media):
            # FIXME: current media may be unrelated to current song
            song = self._app.playlist.current_song
            if song is None or media is None:
                self.cur_song_dl_btn.setEnabled(False)
                return

            title = await async_run(lambda: song.title)
            artists_name = await async_run(lambda: song.artists_name)

            ext = guess_media_url_ext(media.url)
            filename = cook_filename(title, artists_name, ext)
            is_downloaded = self._mgr.is_file_downloaded(filename)
            if is_downloaded:
                self.cur_song_dl_btn.setEnabled(False)
                self.cur_song_dl_btn.setChecked(True)
            else:
                self.cur_song_dl_btn.setEnabled(True)
                self.cur_song_dl_btn.setChecked(False)

        aio.create_task(func(media))
Пример #17
0
    def bind_coro(self, coro):
        """run the coroutine and bind the task

        it will cancel the previous task if exists

        :return: :class:`asyncio.Task`
        """
        self._before_bind()
        if is_in_loop_thread():
            self._task = aio.create_task(coro)
        else:
            self._task = asyncio.run_coroutine_threadsafe(coro,
                                                          loop=self._mgr.loop)
        return self._task
Пример #18
0
    def __init__(self, app, parent=None):
        super().__init__(parent)

        self._app = app
        self._renderer = None
        self._table = None  # current visible table
        self._tables = []

        self._extra = None
        self.toolbar = SongsTableToolbar()
        self.tabbar = TableTabBarV2()
        self.meta_widget = TableMetaWidget(parent=self)
        self.songs_table = SongsTableView(parent=self)
        self.albums_table = AlbumListView(parent=self)
        self.artists_table = ArtistListView(parent=self)
        self.videos_table = VideoListView(parent=self)
        self.playlists_table = PlaylistListView(parent=self)
        self.desc_widget = DescLabel(parent=self)

        self._tables.append(self.songs_table)
        self._tables.append(self.albums_table)
        self._tables.append(self.artists_table)
        self._tables.append(self.playlists_table)
        self._tables.append(self.videos_table)

        self.songs_table.play_song_needed.connect(
            lambda song: asyncio.ensure_future(self.play_song(song)))
        self.videos_table.play_video_needed.connect(
            lambda video: aio.create_task(self.play_video(video)))

        def goto_model(model):
            self._app.browser.goto(model=model)

        for signal in [
                self.songs_table.show_artist_needed,
                self.songs_table.show_album_needed,
                self.albums_table.show_album_needed,
                self.artists_table.show_artist_needed,
                self.playlists_table.show_playlist_needed,
        ]:
            signal.connect(goto_model)

        self.toolbar.play_all_needed.connect(self.play_all)
        self.songs_table.add_to_playlist_needed.connect(
            self._add_songs_to_playlist)

        self._setup_ui()
Пример #19
0
    def __init__(self, app, parent=None):
        super().__init__(parent=parent)
        self._app = app

        self._splitter = QSplitter(self)
        self.collection_toc = CollectionTOCView(self._app, self._splitter)
        self.collection_body = CollectionBody(self._app, self._splitter)

        self.collection_toc.show_album_needed.connect(
            lambda album: aio.create_task(self.show_album(album)))
        self.collection_toc.play_song_needed.connect(
            self._app.player.play_song)
        self.collection_body.song_list_view.play_song_needed.connect(
            self._app.player.play_song)

        self._layout = QHBoxLayout(self)

        self._setup_ui()
Пример #20
0
    def __init__(self, uri: str = None, required_cookies_fields=None):
        if has_webengine and uri and required_cookies_fields:
            use_webview = True
            flags = Qt.Window
        else:
            use_webview = False
            flags = Qt.Popup

        super().__init__(None, flags)
        self._use_webview = use_webview
        self._uri = uri
        self._required_cookies_fields = required_cookies_fields

        self.cookies_text_edit = QTextEdit(self)
        self.hint_label = QLabel(self)
        self.login_btn = QPushButton('登录', self)
        self.weblogin_btn = QPushButton('网页登录', self)

        self.hint_label.setTextFormat(Qt.RichText)

        self._layout = QVBoxLayout(self)
        self._layout.addWidget(self.cookies_text_edit)
        self._layout.addWidget(self.hint_label)
        self._layout.addWidget(self.login_btn)
        self._layout.addWidget(self.weblogin_btn)

        self.cookies_text_edit.setAcceptRichText(False)
        self.cookies_text_edit.setPlaceholderText(
            '请从浏览器中复制 Cookie:\n\n'
            'Chrome 复制的 cookie 格式类似:key1=value1; key2=value2\n\n'
            'Firefox 复制的 cookie 格式类似:{"key1": value1, "key1": value2}'
        )

        if self._use_webview is True:
            self.weblogin_btn.clicked.connect(self._start_web_login)
        else:
            # disable the button if feeluown does not support
            if uri and required_cookies_fields and not has_webengine:
                self.weblogin_btn.setEnabled(False)
            else:
                # hide the button if provider does not support
                self.weblogin_btn.hide()
        self.login_btn.clicked.connect(lambda: aio.create_task(self.login()))
        self.login_succeed.connect(self.hide)
Пример #21
0
    def __init__(self):
        super().__init__(None, Qt.Popup)

        self.cookies_text_edit = QTextEdit(self)
        self.hint_label = QLabel(self)
        self.login_btn = QPushButton('登录', self)

        self.hint_label.setTextFormat(Qt.RichText)

        self._layout = QVBoxLayout(self)
        self._layout.addWidget(self.cookies_text_edit)
        self._layout.addWidget(self.hint_label)
        self._layout.addWidget(self.login_btn)

        self.cookies_text_edit.setAcceptRichText(False)
        self.cookies_text_edit.setPlaceholderText(
            '请从浏览器中复制 Cookie:\n\n'
            'Chrome 复制的 cookie 类似:uid=111; userAction=222\n\n'
            'Firefox 复制的 cookie 类似:{"uid": 111, "userAction": 222}'
        )

        self.login_btn.clicked.connect(lambda: aio.create_task(self.login()))
        self.login_succeed.connect(self.hide)
Пример #22
0
 def show_model(self, model):
     aio.create_task(self.real_show_model(model))
Пример #23
0
 def show_player_playlist(self):
     aio.create_task(self.set_renderer(PlayerPlaylistRenderer()))
Пример #24
0
 def show_artists_coll(self, artists_g):
     aio.create_task(self.set_renderer(
         ArtistsCollectionRenderer(artists_g)))
Пример #25
0
 def show_albums_coll(self, albums_g):
     aio.create_task(self.set_renderer(AlbumsCollectionRenderer(albums_g)))
Пример #26
0
 def show_songs(self, songs=None, songs_g=None):
     """(DEPRECATED) provided only for backward compatibility"""
     renderer = Renderer()
     task = aio.create_task(self.set_renderer(renderer))
     task.add_done_callback(
         lambda _: renderer.show_songs(songs=songs, songs_g=songs_g))
Пример #27
0
 def show_collection(self, coll):
     renderer = SongsCollectionRenderer(coll)
     aio.create_task(self.set_renderer(renderer))
Пример #28
0
 def show_player_playlist(self):
     aio.create_task(self.set_delegate(PlayerPlaylistDelegate()))
Пример #29
0
 def show_albums(self, albums_g):
     delegate = Delegate()
     task = aio.create_task(self.set_delegate(delegate))
     task.add_done_callback(lambda _: delegate.show_albums(albums_g))
Пример #30
0
 def show_songs(self, songs=None, songs_g=None):
     """(DEPRECATED) provided only for backward compatibility"""
     delegate = Delegate()
     task = aio.create_task(self.set_delegate(delegate))
     task.add_done_callback(
         lambda _: delegate.show_songs(songs=songs, songs_g=songs_g))