class ConfigHeader(QObject): loginCookiesFolder = 'cookies/headers/loginInfor.cks' allCookiesFolder = [loginCookiesFolder] def __init__(self, header): super(ConfigHeader, self).__init__() self.header = header self.loginThread = RequestThread(self, None, self.loginFinished) self.loginThread.breakSignal.connect(self.emitWarning) self.header.loginBox.connectLogin(self.login) self.loginInfor = {} self.result = None self.songsDetail = None # 用于确定登陆状态。 self.code = 200 # 用于确定是否最大化. self.isMax = False self.bindConnect() self.loadCookies() def bindConnect(self): self.header.closeButton.clicked.connect(self.header.parent.close) self.header.showminButton.clicked.connect(self.header.parent.showMinimized) self.header.showmaxButton.clicked.connect(self.showMaxiOrRevert) self.header.loginButton.clicked.connect(self.showLoginBox) self.header.prevButton.clicked.connect(self.header.parent.config.prevTab) self.header.nextButton.clicked.connect(self.header.parent.config.nextTab) self.header.searchLine.setButtonSlot(lambda: self.search()) def showMaxiOrRevert(self): if self.isMax: self.header.parent.showNormal() self.isMax = False else: self.header.parent.showMaximized() self.isMax = True @toTask def search(self): text = self.header.searchLine.text() future = aAsync(netease.search, text) self.result = yield from future if not self.result['songCount']: songsIds = [] self.result['songs'] = [] else: songsIds = [i['id'] for i in self.result['songs']] self.songsDetail = {j:'http{0}'.format(i) for i, j in enumerate(songsIds)} # 进行重新编辑方便索引。 songs = self.result['songs'] self.result['songs'] = [{'name':i['name'], 'artists': i['ar'], 'picUrl': i['al']['picUrl'], 'mp3Url': self.songsDetail[i['id']], 'duration': i['dt'], 'music_id':i['id']} for i in songs] songsCount = self.result['songCount'] # 总数是0即没有找到。 if not songsCount: songs = [] else: songs = self.result['songs'] self.header.parent.searchArea.setText(text) self.header.parent.searchArea.config.setSingsData(songs) self.header.parent.config.setTabIndex(5) def showLoginBox(self): self.header.loginBox.open() def login(self): informations = self.header.loginBox.checkAndGetLoginInformation() if not informations: return self.loginThread.setTarget(self.loadLoginInformations) self.loginThread.setArgs(informations) self.loginThread.start() def loadLoginInformations(self, informations:tuple): result = netease.login(*informations) # 网络不通或其他问题。 if not result: self.loginThread.breakSignal.emit('请检查网络后重试~.') self.code = 500 return code = result.get('code') if str(code) != '200': self.loginThread.breakSignal.emit(str(result.get('msg'))) self.code = 500 return self.loginInfor = result self.code = 200 def loginFinished(self): if str(self.code) == '200': self.header.loginBox.accept() self.setUserData() @toTask def setUserData(self): profile = self.loginInfor['profile'] avatarUrl = profile['avatarUrl'] self.header.userPix.setSrc(avatarUrl) # 加载该账户创建及喜欢的歌单。 userId = profile['userId'] future = aAsync(netease.user_playlist, userId) data = yield from future nickname = profile['nickname'] self.header.loginButton.setText(nickname) self.header.loginButton.clicked.disconnect() self.header.loginButton.clicked.connect(self.exitLogin) self.header.parent.navigation.config.setPlaylists(data) def emitWarning(self, warningStr): self.header.loginBox.setWarningAndShowIt(warningStr) def exitLogin(self): self.loginInfor = {} self.header.loginButton.setText('未登录 ▼') self.header.loginButton.clicked.connect(self.showLoginBox) self.header.userPix.setSrc('resource/nouser.png') self.header.parent.navigation.config.clearPlaylists() @checkFolder(allCookiesFolder) def saveCookies(self): with open(self.loginCookiesFolder, 'wb') as f: pickle.dump(self.loginInfor, f) @checkFolder(allCookiesFolder) def loadCookies(self): with open(self.loginCookiesFolder, 'rb') as f: self.loginInfor = pickle.load(f) self.setUserData() self.header.loginButton.clicked.disconnect() self.header.loginButton.clicked.connect(self.exitLogin)
class ConfigNetEase(QObject): def __init__(self, parent=None): super(ConfigNetEase, self).__init__() self.netEase = parent self.netEaseParent = self.netEase.parent # window - > detailSings. self.detailFrame = self.netEaseParent.parent.detailSings self.mainContents = self.netEaseParent.parent # ThreadPool方式。 # 线程池方法说明: # PyQt的线程池由此创建,最好指定下最大连接数。 # QThreadPool需要一个QRunnable对象作为目标。 # 如下所创建的_PicThreadTask,需要重写run函数。 # 然后在需要使用时创建: # task = _PicThreadTask # 并交由QThreadPool开始,self.picThreadPool(task) # 循环就可以。 # 由于QRunnable不是由QObject继承来的,所以无法享受到信号槽机制。 # 而PyQt中跨线程最好是不要进行界面操作。否则会有非常多意想不到的后果。 self.picThreadPool = QThreadPool() self.picThreadPool.setMaxThreadCount(5) # QueueObject定义在base中,是一个由Queue与QObject组成的对象。 # 一方面Queue线程安全,另一方面QObject带有信号槽机制。 # 那只要QRunnable线程中请求完了内容,将内容添加到QueueObject中, # 由QueueObject发出信号通知主线程进行界面操作就可以安全的完成。 # 这里是图片的操作。 self.queue = QueueObject() self.queue.add.connect(self._setStyleCodesByThreadPool) # 连接滑轮到底的信号槽。 # 同时连接图片下载的线程全部完成的信号槽。 # 若一轮图片下载完成并且滑到底部则进行下一次线程,否则将不会。 self.netEase.scrollDown.connect(self.sliderDownEvent) # self.picManager.allFinished.connect(self.picManagerFinishedEvent) # 用于存储结果。 self.result = [] # 歌单请求后的内容和缓存。 self.reqResult = None self.cache = None self.singsUrls = None self.picName = None # 歌单的索引。 self.singsFrames = [] # 歌单显示名的url。 self.singPicUrls = [] # 歌单名称。 self.singNames = [] # 歌单id。 self.playlistIds = [] # 歌曲ids。 self.singsIds = [] # 一个是否滑到底部的flag。 self.sliderDown = False # 布局用row。 self.gridRow = 0 # 布局用column。 self.gridColumn = 0 self.offset = 0 self.myHeight = 0 self.api = netease # self.initThread() def initThread(self): # 一个线程,初始化用于请求歌单的全部内容。 self.netThread = RequestThread(self, self.getSings) self.netThread.finished.connect(self.threadSetSings) self.netThread.setFlag(True) self.netThread.start() self.singsThread = RequestThread(self) self.singsThread.setTarget(self.requestsDetail) self.singsThread.finished.connect(self.setRequestsDetail) def getSings(self): """请求一波歌单,一次30个。设置offset会设置请求量。""" result = self.api.all_playlist(offset=self.offset) if not result: return for i in result: self.result.append(i) self.singNames.append(i['name']) self.singPicUrls.append(i['coverImgUrl']) self.playlistIds.append(i['id']) """测试线程池方法。""" def threadSetSings(self): if not self.result: return for i in range(30): i += self.offset picName = makeMd5(self.singPicUrls[i]) frame = OneSing(self.gridRow, self.gridColumn, self.playlistIds[i], self, picName) frame.clicked.connect(self.startRequest) frame.nameLabel.setText(self.singNames[i]) self.netEase.mainLayout.addWidget(frame, self.gridRow, self.gridColumn) # 建立起索引,一是防止垃圾回收了,二是可以找到他的地址。 self.singsFrames.append(frame) # 用于布局,一行4个。 if self.gridColumn == 3: self.gridColumn = 0 self.gridRow += 1 else: self.gridColumn += 1 try: cacheList = os.listdir('cache') except: os.mkdir('cache') cacheList = os.listdir('cache') url = self.singPicUrls[i] # names = str(url[url.rfind('/')+1:]) names = makeMd5(url) if names in cacheList: frame.setStyleSheets("QLabel#picLabel{border-image: url(cache/%s)}"%(names)) else: task = _PicThreadTask(self.queue, frame, url) self.picThreadPool.start(task) def _setStyleCodesByThreadPool(self): # data是线程池的请求完成后的对象。 # 0下标处是widget,1是style代码。 data = self.queue.get() if not data: return else: data[0].setStyleSheets(data[1]) def startRequest(self, ids, picName): self.picName = picName self.singsThread.setArgs(ids) self.singsThread.start() def requestsDetail(self, ids): reqResult = self.api.details_playlist(ids) self.reqResult = reqResult # 网易云此处不再返回歌曲地址,由之后播放时单独获取。 self.singsIds = [i['id'] for i in reqResult['tracks']] self.singsUrls = ['http{0}'.format(i) for i in enumerate(self.singsIds)] def setRequestsDetail(self): result = self.reqResult self.detailFrame.config.setupDetailFrames(result, self.singsUrls, self.singsIds) self.detailFrame.picLabel.setSrc('cache/{0}'.format(self.picName)) self.detailFrame.picLabel.setStyleSheet('''QLabel {padding: 10px;}''') # 隐藏原来的区域,显示现在的区域。 self.mainContents.mainContents.setCurrentIndex(1) # 事件。 def sliderDownEvent(self): """滑轮到底的事件。""" if self.netEase.isHidden() == False: # toDo, 多个 self.offset += 30 # 判断是否在工作,免得多次start。 if self.netThread.isRunning(): return else: self.netThread.start()
class ConfigNavigation(QObject): def __init__(self, navigation): super(ConfigNavigation, self).__init__() self.navigation = navigation self.detailFrame = self.navigation.parent.detailSings # window self.mainContents = self.navigation.parent self.nativeListFunction = lambda: self.mainContents.mainContents.setCurrentIndex( 2) self.singsFunction = self.none self.playlists = [] self.playlistThread = RequestThread(self) self.playlistThread.setTarget(self.requestsDetail) self.playlistThread.finished.connect(self.setDetail) self.result = None self.singsUrls = None self.coverImgUrl = None self.api = netEase self.bindConnect() def bindConnect(self): self.navigation.navigationList.itemPressed.connect( self.navigationListItemClickEvent) self.navigation.nativeList.itemPressed.connect( self.nativeListItemClickEvent) def navigationListItemClickEvent(self): """用户处理导航栏的点击事件。""" # 处理其他组件取消选中。 for i in self.playlists: if i.isChecked(): i.setCheckable(False) i.setCheckable(True) break self.navigation.nativeList.setCurrentRow(-1) """处理事件。""" self.navigationListFunction() def nativeListItemClickEvent(self): """本地功能的点击事件。""" for i in self.playlists: if i.isChecked(): i.setCheckable(False) i.setCheckable(True) break self.navigation.navigationList.setCurrentRow(-1) """处理事件。""" self.nativeListFunction() def singsButtonClickEvent(self): """歌单的点击事件。""" self.navigation.navigationList.setCurrentRow(-1) self.navigation.nativeList.setCurrentRow(-1) """处理事件。""" self.singsFunction() def setPlaylists(self, datas): # 布局原因,需要在最后加一个stretch才可以正常布局。 # 所以这边先将最后一个stretch删去,将所有的内容添加完成后在加上。 self.navigation.mainLayout.takeAt(self.navigation.mainLayout.count() - 1) for i in datas: button = PlaylistButton(self, i['id'], i['coverImgUrl'], QIcon('resource/notes.png'), i['name']) button.hasClicked.connect(self.startRequest) self.playlists.append(button) self.navigation.mainLayout.addWidget(button) self.navigation.mainLayout.addStretch(1) def clearPlaylists(self): for i in self.playlists: i.deleteLater() self.playlists = [] for i in range(11, self.navigation.mainLayout.count()): self.navigation.mainLayout.takeAt(i) self.navigation.mainLayout.addStretch(1) def startRequest(self, ids, coverImgUrl): self.coverImgUrl = coverImgUrl self.playlistThread.setArgs(ids) self.playlistThread.start() self.singsButtonClickEvent() def requestsDetail(self, ids): result = self.api.details_playlist(ids) self.result = result # 由于旧API不在直接返回歌曲地址,需要获取歌曲号后再次进行请求。 singsIds = [i['id'] for i in result['tracks']] # 此处还有些问题。 # 由于是两次url请求,稍微变得有点慢。 self.singsUrls = { i['id']: i['url'] for i in self.api.singsUrl(singsIds) } self.singsUrls = [self.singsUrls[i] for i in singsIds] def setDetail(self): # 方便书写。 result = self.result self.detailFrame.config.setupDetailFrames(result, self.singsUrls) self.detailFrame.picLabel.setSrc(self.coverImgUrl) self.detailFrame.picLabel.setStyleSheet('''QLabel {padding: 10px;}''') # 隐藏原来的区域,显示现在的区域。 self.mainContents.mainContents.setCurrentIndex(1) def navigationListFunction(self): isVisible = self.navigation.parent.mainContent.tab.isVisible() if self.navigation.navigationList.currentRow() == 0: # 发现音乐。 self.navigation.parent.mainContents.setCurrentIndex(0) def none(self): pass
class ConfigHeader(QObject): loginCookiesFolder = 'cookies/headers/loginInfor.cks' allCookiesFolder = [loginCookiesFolder] def __init__(self, header): super(ConfigHeader, self).__init__() self.header = header self.loginThread = RequestThread(self, None, self.loginFinished) self.loginThread.breakSignal.connect(self.emitWarning) self.header.loginBox.connectLogin(self.login) self.loginInfor = {} self.result = None self.songsDetail = None # 用于确定登陆状态。 self.code = 200 # 用于确定是否最大化. self.isMax = False self.bindConnect() self.loadCookies() def bindConnect(self): self.header.closeButton.clicked.connect(self.header.parent.close) self.header.showminButton.clicked.connect( self.header.parent.showMinimized) self.header.showmaxButton.clicked.connect(self.showMaxiOrRevert) self.header.loginButton.clicked.connect(self.showLoginBox) self.header.prevButton.clicked.connect( self.header.parent.config.prevTab) self.header.nextButton.clicked.connect( self.header.parent.config.nextTab) self.header.searchLine.setButtonSlot(lambda: self.search()) def showMaxiOrRevert(self): if self.isMax: self.header.parent.showNormal() self.isMax = False else: self.header.parent.showMaximized() self.isMax = True @toTask def search(self): text = self.header.searchLine.text() future = aAsync(netease.search, text) self.result = yield from future if not self.result['songCount']: songsIds = [] self.result['songs'] = [] else: songsIds = [i['id'] for i in self.result['songs']] self.songsDetail = { j: 'http{0}'.format(i) for i, j in enumerate(songsIds) } # 进行重新编辑方便索引。 songs = self.result['songs'] self.result['songs'] = [{ 'name': i['name'], 'artists': i['ar'], 'picUrl': i['al']['picUrl'], 'mp3Url': self.songsDetail[i['id']], 'duration': i['dt'], 'music_id': i['id'] } for i in songs] songsCount = self.result['songCount'] # 总数是0即没有找到。 if not songsCount: songs = [] else: songs = self.result['songs'] self.header.parent.searchArea.setText(text) self.header.parent.searchArea.config.setSingsData(songs) self.header.parent.config.setTabIndex(3) def showLoginBox(self): self.header.loginBox.open() def login(self): informations = self.header.loginBox.checkAndGetLoginInformation() if not informations: return self.loginThread.setTarget(self.loadLoginInformations) self.loginThread.setArgs(informations) self.loginThread.start() def loadLoginInformations(self, informations: tuple): result = netease.login(*informations) # 网络不通或其他问题。 if not result: self.loginThread.breakSignal.emit('请检查网络后重试~.') self.code = 500 return code = result.get('code') if str(code) != '200': self.loginThread.breakSignal.emit(str(result.get('msg'))) self.code = 500 return self.loginInfor = result self.code = 200 def loginFinished(self): if str(self.code) == '200': self.header.loginBox.accept() self.setUserData() @toTask def setUserData(self): profile = self.loginInfor['profile'] avatarUrl = profile['avatarUrl'] self.header.userPix.setSrc(avatarUrl) # 加载该账户创建及喜欢的歌单。 userId = profile['userId'] future = aAsync(netease.user_playlist, userId) data = yield from future nickname = profile['nickname'] self.header.loginButton.setText(nickname) self.header.loginButton.clicked.disconnect() self.header.loginButton.clicked.connect(self.exitLogin) self.header.parent.navigation.config.setPlaylists(data) def emitWarning(self, warningStr): self.header.loginBox.setWarningAndShowIt(warningStr) def exitLogin(self): self.loginInfor = {} self.header.loginButton.setText('未登录 ▼') self.header.loginButton.clicked.connect(self.showLoginBox) self.header.userPix.setSrc('resource/nouser.png') self.header.parent.navigation.config.clearPlaylists() @checkFolder(allCookiesFolder) def saveCookies(self): with open(self.loginCookiesFolder, 'wb') as f: pickle.dump(self.loginInfor, f) @checkFolder(allCookiesFolder) def loadCookies(self): with open(self.loginCookiesFolder, 'rb') as f: self.loginInfor = pickle.load(f) self.setUserData() self.header.loginButton.clicked.disconnect() self.header.loginButton.clicked.connect(self.exitLogin)
class ConfigNetEase(QObject): def __init__(self, parent=None): super(ConfigNetEase, self).__init__() self.netEase = parent self.netEaseParent = self.netEase.parent # window - > detailSings. self.detailFrame = self.netEaseParent.parent.detailSings self.mainContents = self.netEaseParent.parent # ThreadPool方式。 # 线程池方法说明: # PyQt的线程池由此创建,最好指定下最大连接数。 # QThreadPool需要一个QRunnable对象作为目标。 # 如下所创建的_PicThreadTask,需要重写run函数。 # 然后在需要使用时创建: # task = _PicThreadTask # 并交由QThreadPool开始,self.picThreadPool(task) # 循环就可以。 # 由于QRunnable不是由QObject继承来的,所以无法享受到信号槽机制。 # 而PyQt中跨线程最好是不要进行界面操作。否则会有非常多意想不到的后果。 self.picThreadPool = QThreadPool() self.picThreadPool.setMaxThreadCount(5) # QueueObject定义在base中,是一个由Queue与QObject组成的对象。 # 一方面Queue线程安全,另一方面QObject带有信号槽机制。 # 那只要QRunnable线程中请求完了内容,将内容添加到QueueObject中, # 由QueueObject发出信号通知主线程进行界面操作就可以安全的完成。 # 这里是图片的操作。 self.queue = QueueObject() self.queue.add.connect(self._setStyleCodesByThreadPool) # 连接滑轮到底的信号槽。 # 同时连接图片下载的线程全部完成的信号槽。 # 若一轮图片下载完成并且滑到底部则进行下一次线程,否则将不会。 self.netEase.scrollDown.connect(self.sliderDownEvent) # self.picManager.allFinished.connect(self.picManagerFinishedEvent) # 用于存储结果。 self.result = [] # 歌单请求后的内容和缓存。 self.reqResult = None self.cache = None self.singsUrls = None self.picName = None # 歌单的索引。 self.singsFrames = [] # 歌单显示名的url。 self.singPicUrls = [] # 歌单名称。 self.singNames = [] # 歌单id。 self.playlistIds = [] # 歌曲ids。 self.singsIds = [] # 一个是否滑到底部的flag。 self.sliderDown = False # 布局用row。 self.gridRow = 0 # 布局用column。 self.gridColumn = 0 self.offset = 0 # 用于不足时的补足。 self.offsetComplement = 30 self.myHeight = 0 self.api = netease # self.initThread() def initThread(self): # 一个线程,初始化用于请求歌单的全部内容。 self.netThread = RequestThread(self, self.getSings) self.netThread.finished.connect(self.threadSetSings) self.netThread.setFlag(True) self.netThread.start() self.singsThread = RequestThread(self) self.singsThread.setTarget(self.requestsDetail) self.singsThread.finished.connect(self.setRequestsDetail) def getSings(self): """请求一波歌单,一次30个。设置offset会设置请求量。""" result = self.api.all_playlist(offset=self.offset) if not result: return for i in result: self.result.append(i) self.singNames.append(i['name']) self.singPicUrls.append(i['coverImgUrl']) self.playlistIds.append(i['id']) """测试线程池方法。""" def threadSetSings(self): if not self.result: return length = len(self.singPicUrls) for i in range(30): i += self.offset # 根本原因是不足30个。 if i >= length: self.offsetComplement = length % 30 break picName = makeMd5(self.singPicUrls[i]) frame = OneSing(self.gridRow, self.gridColumn, self.playlistIds[i], self, picName) frame.clicked.connect(self.startRequest) frame.nameLabel.setText(self.singNames[i]) self.netEase.mainLayout.addWidget(frame, self.gridRow, self.gridColumn) # 建立起索引,一是防止垃圾回收了,二是可以找到他的地址。 self.singsFrames.append(frame) # 用于布局,一行4个。 if self.gridColumn == 3: self.gridColumn = 0 self.gridRow += 1 else: self.gridColumn += 1 try: cacheList = os.listdir('cache') except: os.mkdir('cache') cacheList = os.listdir('cache') url = self.singPicUrls[i] # names = str(url[url.rfind('/')+1:]) names = makeMd5(url) if names in cacheList: frame.setStyleSheets("QLabel#picLabel{border-image: url(cache/%s)}"%(names)) else: task = _PicThreadTask(self.queue, frame, url) self.picThreadPool.start(task) else: # 如果顺利进行会将offsetComplement变成原30。 self.offsetComplement = 30 def _setStyleCodesByThreadPool(self): # data是线程池的请求完成后的对象。 # 0下标处是widget,1是style代码。 data = self.queue.get() if not data: return else: data[0].setStyleSheets(data[1]) def startRequest(self, ids, picName): self.picName = picName self.singsThread.setArgs(ids) self.singsThread.start() def requestsDetail(self, ids): reqResult = self.api.details_playlist(ids) self.reqResult = reqResult # 网易云此处不再返回歌曲地址,由之后播放时单独获取。 self.singsIds = [i['id'] for i in reqResult['tracks']] self.singsUrls = ['http{0}'.format(i) for i in enumerate(self.singsIds)] def setRequestsDetail(self): result = self.reqResult self.detailFrame.config.setupDetailFrames(result, self.singsUrls, self.singsIds) self.detailFrame.picLabel.setSrc('cache/{0}'.format(self.picName)) self.detailFrame.picLabel.setStyleSheet('''QLabel {padding: 10px;}''') # 隐藏原来的区域,显示现在的区域。 self.mainContents.mainContents.setCurrentIndex(1) # 事件。 def sliderDownEvent(self): """滑轮到底的事件。""" if self.netEase.isHidden() == False: # toDo, 多个 self.offset += self.offsetComplement # 判断是否在工作,免得多次start。 if self.netThread.isRunning(): return else: self.netThread.start()