开源代码学习之persepolis【二】
?
一、下載界面
?
1、主界面下載顯示
MainWindow首先對(duì)aria進(jìn)行初始化,啟動(dòng)aria2。啟動(dòng)方法在download.py中。
?# start aria2
? ? ? ? start_aria = StartAria2Thread()
? ? ? ? self.threadPool.append(start_aria)
? ? ? ? self.threadPool[0].start()
? ? ? ? self.threadPool[0].ARIA2RESPONDSIGNAL.connect(self.startAriaMessage)
然后定義添加下載鏈接界面,AddLinkWindow可以有多個(gè),都存放在self.addlinkwindows_list中。
調(diào)用的時(shí)候有三個(gè)參數(shù)(self, self.callBack, self.persepolis_setting),下載添加界面通過(guò)回調(diào)函數(shù)傳遞參數(shù)給主界面的callBack函數(shù)。callBack獲取下載信息后,添加到線程池中。
new_download = DownloadLink(gid, self)
? ? ? ? ? ? self.threadPool.append(new_download)
? ? ? ? ? ? self.threadPool[len(self.threadPool) - 1].start()
? ? ? ? ? ? self.threadPool[len(self.threadPool) -
? ? ? ? ? ? ? ? ? ? ? ? ? ? 1].ARIA2NOTRESPOND.connect(self.aria2NotRespond)
注意主界面的addLinkSpiderCallBack函數(shù),該函數(shù)調(diào)用順序?yàn)?#xff1a;
1、下載添加界面獲取下載鏈接改變(linkLineChanged)信息
2、下載添加界面開(kāi)啟線程AddLinkSpiderThread嘗試獲取鏈接文件大小,通過(guò)parent將該線程添加到主界面線程池中。并將AddLinkSpiderThread的信號(hào)連接到主線程的addLinkSpiderCallBack函數(shù),同時(shí)將下載添加界面的指針child添加到槽函數(shù)的參數(shù)中,這樣主界面可以通過(guò)child訪問(wèn)下載添加界面。
self.parent.threadPool[len(self.parent.threadPool) - 1].ADDLINKSPIDERSIGNAL.connect(
? ? ? ? ? ? ? ? partial(self.parent.addLinkSpiderCallBack, child=self))
3、AddLinkSpiderThread線程將結(jié)果ADDLINKSPIDERSIGNAL信號(hào)發(fā)送給主界面addLinkSpiderCallBack函數(shù),注意這里發(fā)射的時(shí)候,只有dict參數(shù),連接的時(shí)候有兩個(gè)參數(shù)。
self.ADDLINKSPIDERSIGNAL.emit(spider_dict)
4、主界面addLinkSpiderCallBack函數(shù)通過(guò)child調(diào)用下載添加界面,設(shè)置文件名稱和大小的顯示。
這樣就是下載鏈接界面新增線程到主界面,然后主界面線程執(zhí)行完成后控制子界面更新,為什么不是下載鏈接添加界面自己開(kāi)啟一個(gè)線程獲取文件大小,然然后根據(jù)獲取結(jié)果自己改變下載鏈接界面呢?
?
mainwindow.py:
class DownloadLink(QThread):ARIA2NOTRESPOND = pyqtSignal()def __init__(self, gid, parent):QThread.__init__(self)self.gid = gidself.parent = parentdef run(self):# add gid of download to the active gids in temp_db# or update data base , if it was existed beforetry:self.parent.temp_db.insertInSingleTable(self.gid)except:# release lockself.parent.temp_db.lock = Falsedictionary = {'gid': self.gid, 'status': 'active'}self.parent.temp_db.updateSingleTable(dictionary)# if request is not successful then persepolis is checking rpc# connection with download.aria2Version() functionanswer = download.downloadAria(self.gid, self.parent)if answer == False:version_answer = download.aria2Version()if version_answer == 'did not respond':self.ARIA2NOTRESPOND.emit()class MainWindow(MainWindow_Ui):def __init__(self, start_in_tray, persepolis_main, persepolis_setting):super().__init__(persepolis_setting)self.persepolis_setting = persepolis_settingself.persepolis_main = persepolis_main# list of threadsself.threadPool = []# start aria2start_aria = StartAria2Thread()self.threadPool.append(start_aria)self.threadPool[0].start()self.threadPool[0].ARIA2RESPONDSIGNAL.connect(self.startAriaMessage)def addLinkButtonPressed(self, button=None):addlinkwindow = AddLinkWindow(self, self.callBack, self.persepolis_setting)self.addlinkwindows_list.append(addlinkwindow)self.addlinkwindows_list[len(self.addlinkwindows_list) - 1].show()# callback of AddLinkWindowdef callBack(self, add_link_dictionary, download_later, category):# write information in data_baseself.persepolis_db.insertInDownloadTable([dict])self.persepolis_db.insertInAddLinkTable([add_link_dictionary])# if user didn't press download_later_pushButton in add_link window# then create new qthread for new download!if not(download_later):new_download = DownloadLink(gid, self)self.threadPool.append(new_download)self.threadPool[len(self.threadPool) - 1].start()self.threadPool[len(self.threadPool) -1].ARIA2NOTRESPOND.connect(self.aria2NotRespond)# open progress window for download.self.progressBarOpen(gid)# notify user# check that download scheduled or notif not(add_link_dictionary['start_time']):message = QCoreApplication.translate("mainwindow_src_ui_tr", "Download Starts")else:new_spider = SpiderThread(add_link_dictionary, self)self.threadPool.append(new_spider)self.threadPool[len(self.threadPool) - 1].start()self.threadPool[len(self.threadPool) - 1].SPIDERSIGNAL.connect(self.spiderUpdate)message = QCoreApplication.translate("mainwindow_src_ui_tr", "Download Scheduled")notifySend(message, '', 10000, 'no', parent=self)# see addlink.py filedef addLinkSpiderCallBack(self, spider_dict, child):# get file_name and file_sizefile_name = spider_dict['file_name']file_size = spider_dict['file_size']if file_size:file_size = 'Size: ' + str(file_size)child.size_label.setText(file_size)if file_name and not(child.change_name_checkBox.isChecked()):child.change_name_lineEdit.setText(file_name)child.change_name_checkBox.setChecked(True)?
2、下載添加界面
下載添加界面AddLinkWindow將第一個(gè)參數(shù)self初始化為parent,后續(xù)通過(guò)該參數(shù)對(duì)主界面進(jìn)行訪問(wèn),第二個(gè)參數(shù)為回調(diào)函數(shù),用于傳遞參數(shù)給主界面,第三個(gè)參數(shù)將系統(tǒng)設(shè)置傳遞給下載添加界面。
在下載鏈接改變時(shí),將AddLinkSpiderThread加入到主界面的threadPool中,并將ADDLINKSPIDERSIGNAL連接到主界面的addLinkSpiderCallBack。
new_spider = AddLinkSpiderThread(dict)
self.parent.threadPool.append(new_spider)
self.parent.threadPool[len(self.parent.threadPool) - 1].ADDLINKSPIDERSIGNAL.connect(
? ? ? ? ? ? ? ? partial(self.parent.addLinkSpiderCallBack, child=self))
AddLinkSpiderThread通過(guò)spider.addLinkSpider獲取到文件大小和名稱信息,發(fā)送給主界面的addLinkSpiderCallBack函數(shù),注意這里發(fā)射的時(shí)候,只有dict參數(shù),連接的時(shí)候有兩個(gè)參數(shù)。
self.ADDLINKSPIDERSIGNAL.emit(spider_dict)
在按下確定按鈕后,通過(guò)callback回調(diào)函數(shù)調(diào)用傳遞參數(shù)給主界面。
addlink.py:
class AddLinkSpiderThread(QThread):ADDLINKSPIDERSIGNAL = pyqtSignal(dict)def __init__(self, add_link_dictionary):QThread.__init__(self)self.add_link_dictionary = add_link_dictionarydef run(self):try:# get file name and file sizefile_name, file_size = spider.addLinkSpider(self.add_link_dictionary)spider_dict = {'file_size': file_size, 'file_name': file_name}# emit resultsself.ADDLINKSPIDERSIGNAL.emit(spider_dict)class AddLinkWindow(AddLinkWindow_Ui):def __init__(self, parent, callback, persepolis_setting, plugin_add_link_dictionary={}):super().__init__(persepolis_setting)self.callback = callbackself.plugin_add_link_dictionary = plugin_add_link_dictionaryself.persepolis_setting = persepolis_settingself.parent = parent self.link_lineEdit.textChanged.connect(self.linkLineChanged)self.ok_pushButton.clicked.connect(partial(self.okButtonPressed, download_later=False))self.download_later_pushButton.clicked.connect(partial(self.okButtonPressed, download_later=True))# enable when link_lineEdit is not empty and find size of file.def linkLineChanged(self, lineEdit):if str(self.link_lineEdit.text()) == '':self.ok_pushButton.setEnabled(False)self.download_later_pushButton.setEnabled(False)else: # find file sizedict = {'link': str(self.link_lineEdit.text())}# spider is finding file sizenew_spider = AddLinkSpiderThread(dict)self.parent.threadPool.append(new_spider)self.parent.threadPool[len(self.parent.threadPool) - 1].start()self.parent.threadPool[len(self.parent.threadPool) - 1].ADDLINKSPIDERSIGNAL.connect(partial(self.parent.addLinkSpiderCallBack, child=self))self.ok_pushButton.setEnabled(True)self.download_later_pushButton.setEnabled(True)def okButtonPressed(self, button, download_later):# user submitted information by pressing ok_pushButton, so get information# from AddLinkWindow and return them to the mainwindow with callback!# save information in a dictionary(add_link_dictionary).self.add_link_dictionary = {'referer': referer, 'header': header, 'user_agent': user_agent, 'load_cookies': load_cookies,'out': out, 'start_time': start_time, 'end_time': end_time, 'link': link, 'ip': ip,'port': port, 'proxy_user': proxy_user, 'proxy_passwd': proxy_passwd,'download_user': download_user, 'download_passwd': download_passwd,'connections': connections, 'limit_value': limit, 'download_path': download_path}# get category of downloadcategory = str(self.add_queue_comboBox.currentText())del self.plugin_add_link_dictionary# return information to mainwindowself.callback(self.add_link_dictionary, download_later, category)# close windowself.close()3、總結(jié)
1、線程間傳遞參數(shù)可以通過(guò)回調(diào)函數(shù)傳遞,也可以通過(guò)信號(hào)和槽傳遞。
2、主從線程之間,主線程將self傳遞給從線程,從線程可以對(duì)主線程的函數(shù)進(jìn)行調(diào)用。從線程也可以將self傳遞給主線程,由主線程對(duì)從線程進(jìn)行函數(shù)調(diào)用
?
二、下載文件
?
啟動(dòng)aria2的服務(wù)是通過(guò)subprocess.Popen啟動(dòng)的,每個(gè)選項(xiàng)的意義在aria2接口文檔都有介紹。
subprocess?模塊允許你生成新的進(jìn)程,連接它們的輸入、輸出、錯(cuò)誤管道,并且獲取它們的返回碼。此模塊打算代替一些老舊的模塊與功能os.system, os.popen*, os.spawn.
https://docs.python.org/zh-cn/3/library/subprocess.html#subprocess.Popen.communicate
https://blog.csdn.net/qq_34355232/article/details/87709418
subprocess.Popen([aria2d, '--no-conf', '--enable-rpc', '--rpc-listen-port=' + str(port), '--rpc-max-request-size=2M', '--rpc-listen-all', '--quiet=true'], stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=False, creationflags=NO_WINDOW)
?
添加下載鏈接是通過(guò)XML-RPC遠(yuǎn)程調(diào)用完成的:
server = xmlrpc.client.ServerProxy(server_uri, allow_none=True)
aria2的RPC接口介紹如下,支持JSON-RPC和XML-RPC。
https://aria2.github.io/manual/en/html/aria2c.html#rpc-interface
python的XML-RPC庫(kù)介紹文檔很多,找了兩個(gè)如下:
https://www.jianshu.com/p/9987913cf734
https://developer.51cto.com/art/201906/597963.htm
GID:aria2通過(guò)GID索引管理每個(gè)下載,GID為64位二進(jìn)制數(shù)。RPC訪問(wèn)時(shí),表示為長(zhǎng)度16個(gè)字符的十六進(jìn)制字符串。通常aria2為每個(gè)下載鏈接產(chǎn)生衣蛾GID,用戶也可以通過(guò)GID選項(xiàng)指定。
通過(guò)XML-RPC訪問(wèn)aria2
aria2.addUri([secret,?]uris[,?options[,?position]])?
添加下載的鏈接,URIS是下載鏈接數(shù)組,option,positon是一個(gè)整數(shù),表示插在下載隊(duì)列的位置,0表示第一個(gè)。如果沒(méi)有提供position參數(shù)或者position比隊(duì)列的長(zhǎng)度長(zhǎng),則添加的下載在下載隊(duì)列的最后。該方法返回新注冊(cè)下載的GID。
aria2.tellStatus([secret,?]gid[,?keys])
該方法返回指定下載GID的進(jìn)展,keys是一個(gè)字符串?dāng)?shù)組,指定了需要查詢哪些項(xiàng)目。如果keys為空或者省略,則包含所有的項(xiàng)目。常用的項(xiàng)目有g(shù)id、status、totalLength、completedLength、downloadSpeed、uploadSpeed、numSeeders、connections、dir、files。
aria2.tellActive([secret][,?keys])
該方法查詢激活下載的狀態(tài),查詢的項(xiàng)目與aria2.tellStatus類似。
aria2.removeDownloadResult([secret,?]gid)
根據(jù)GID從存儲(chǔ)中移除下載完成/下載錯(cuò)誤/刪除的下載,如果成功返回OK
aria2.remove([secret,?]gid)
根據(jù)GID刪除下載,如果下載正在進(jìn)行先停止該下載。該下載鏈接的狀態(tài)變?yōu)閞emoved狀態(tài)。返回刪除狀態(tài)的GID。
aria2.pause([secret,?]gid)
暫停指定GID的下載鏈接,下載鏈接的狀態(tài)變?yōu)閜aused。如果下載是激活的,則該下載鏈接放置在等待隊(duì)列的最前面。要想將狀態(tài)變?yōu)閣aiting,需要用aria2.unpause方法。
download.py
# get port from persepolis_setting port = int(persepolis_setting.value('settings/rpc-port'))# get aria2_path aria2_path = persepolis_setting.value('settings/aria2_path')# xml rpc SERVER_URI_FORMAT = 'http://{}:{:d}/rpc' server_uri = SERVER_URI_FORMAT.format(host, port) server = xmlrpc.client.ServerProxy(server_uri, allow_none=True)# start aria2 with RPCdef startAria():# in Windowselif os_type == OS.WINDOWS:if aria2_path == "" or aria2_path == None or os.path.isfile(str(aria2_path)) == False:cwd = sys.argv[0]current_directory = os.path.dirname(cwd)aria2d = os.path.join(current_directory, "aria2c.exe") # aria2c.exe pathelse:aria2d = aria2_path# NO_WINDOW option avoids opening additional CMD window in MS Windows.NO_WINDOW = 0x08000000if not os.path.exists(aria2d):logger.sendToLog("Aria2 does not exist in the current path!", "ERROR")return None# aria2 command in windowssubprocess.Popen([aria2d, '--no-conf', '--enable-rpc', '--rpc-listen-port=' + str(port),'--rpc-max-request-size=2M', '--rpc-listen-all', '--quiet=true'],stderr=subprocess.PIPE,stdout=subprocess.PIPE,stdin=subprocess.PIPE,shell=False,creationflags=NO_WINDOW)time.sleep(2)# check that starting is successful or not!answer = aria2Version()# return resultreturn answer# check aria2 release version . Persepolis uses this function to # check that aria2 RPC connection is available or not.def aria2Version():try:answer = server.aria2.getVersion()except:# write ERROR messages in terminal and loglogger.sendToLog("Aria2 didn't respond!", "ERROR")answer = "did not respond"return answerdef downloadAria(gid, parent):# add_link_dictionary is a dictionary that contains user download request# information.# get information from data_baseadd_link_dictionary = parent.persepolis_db.searchGidInAddLinkTable(gid)answer = server.aria2.addUri([link], aria_dict)三、數(shù)據(jù)庫(kù)
使用sqlite3數(shù)據(jù)庫(kù)教程:https://docs.python.org/zh-cn/3/library/sqlite3.html
有三個(gè)數(shù)據(jù)庫(kù)TempDB在內(nèi)存中,放置實(shí)時(shí)數(shù)據(jù),PluginsDB放置瀏覽器插件傳來(lái)的新鏈接數(shù)據(jù),PersepolisDB是主要的數(shù)據(jù)庫(kù),存放下載信息。
TempDB有兩個(gè)表,single_db_table存放下載中的GID,queue_db_table存放下載隊(duì)列的GID信息。
PersepolisDB有四個(gè)表:
category_db_table存放類型信息,包括'All Downloads'、'Single Downloads'和'Scheduled Downloads'類型。
download_db_table存放主界面顯示的下載狀態(tài)表。
addlink_db_table存放下載添加界面添加的下載鏈接。
video_finder_db_table存放下載添加界面添加下載的信息。
# This class manages TempDB # TempDB contains gid of active downloads in every session. class TempDB():def __init__(self):# temp_db saves in RAM# temp_db_connectionself.temp_db_connection = sqlite3.connect(':memory:', check_same_thread=False)def createTables(self):# lock data baseself.lockCursor()self.temp_db_cursor.execute("""CREATE TABLE IF NOT EXISTS single_db_table(self.temp_db_cursor.execute("""CREATE TABLE IF NOT EXISTS queue_db_table(# persepolis main data base contains downloads information # This class is managing persepolis.db class PersepolisDB():def __init__(self):# persepolis.db file pathpersepolis_db_path = os.path.join(config_folder, 'persepolis.db')# persepolis_db_connectionself.persepolis_db_connection = sqlite3.connect(persepolis_db_path, check_same_thread=False)# queues_list contains name of categories and category settingsdef createTables(self):# lock data baseself.lockCursor()# Create category_db_table and add 'All Downloads' and 'Single Downloads' to itself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS category_db_table(# download table contains download table download items informationself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS download_db_table(# addlink_db_table contains addlink window download informationself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS addlink_db_table(# video_finder_db_table contains addlink window download informationself.persepolis_db_cursor.execute("""CREATE TABLE IF NOT EXISTS video_finder_db_table(sqlite3?模塊支持兩種占位符:問(wèn)號(hào)(qmark風(fēng)格)和命名占位符(命名風(fēng)格)。
# This is the qmark style:
cur.execute("insert into people values (?, ?)", (who, age))
# And this is the named style:
cur.execute("select * from people where name_last=:who and age=:age", {"who": who, "age": age})
coalesce函數(shù)返回其參數(shù)中第一個(gè)非空表達(dá)式的值,也即提供了參數(shù)則用新參數(shù),未提供新參數(shù)則用原值。
self.temp_db_cursor.execute("""UPDATE single_db_table SET shutdown = coalesce(:shutdown, shutdown),status = coalesce(:status, status)WHERE gid = :gid""", dict)MainWindow在初始化時(shí)創(chuàng)建CheckDownloadInfoThread線程,輪詢每一個(gè)下載中的鏈接,并將結(jié)果返回給主界面的checkDownloadInfo函數(shù)進(jìn)行下載狀態(tài)更新。
# CheckDownloadInfoThreadcheck_download_info = CheckDownloadInfoThread(self)self.threadPool.append(check_download_info)self.threadPool[1].start()self.threadPool[1].DOWNLOAD_INFO_SIGNAL.connect(self.checkDownloadInfo)self.threadPool[1].RECONNECTARIASIGNAL.connect(self.reconnectAria)?
?
總結(jié)
以上是生活随笔為你收集整理的开源代码学习之persepolis【二】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: js为li列表添加点击事件
- 下一篇: React回调函数两种常用方式